fsharp Documentation

The F# compiler, F# core library, F# language service, and F# tooling integration for Visual Studio

RAW Doc

Technical Documentation: dotnet/fsharp

โ„น๏ธ Provenance: Multi-Tier Ingestion ยท CodeWiki Reference

Overview (`README.md`)

The F# compiler, F# core library, and F# editor tools


You're invited to contribute to future releases of the F# compiler, core library, and tools. Development of this repository can be done on any OS supported by .NET.

You will also need .NET SDK installed from here, exact version can be found in the global.json file in the root of the repository.

Contributing

Quickstart on Windows

Build from the command line:

shell
build.cmd

The build depends on an installation of Visual Studio. To build the compiler without this dependency use:

shell
build.cmd -noVisualStudio

After it's finished, open either FSharp.slnx or VisualFSharp.slnx in your editor of choice. The latter solution is larger but includes the F# tools for Visual Studio and its associated infrastructure.

Quickstart on Linux or macOS

Build from the command line:

shell
./build.sh

After it's finished, open FSharp.slnx in your editor of choice.

Documentation for contributors

  • The Compiler Documentation is essential reading for any larger contributions to the F# compiler codebase and contains links to learning videos, architecture diagrams, and other resources.

  • The same docs are also published as The F# Compiler Guide. It also contains the public searchable docs for FSharp.Compiler.Service component.

  • See DEVGUIDE.md for more details on configurations for building the codebase. In practice, you only need to run build.cmd/build.sh.

  • See TESTGUIDE.md for information about the various test suites in this codebase and how to run them individually.

Documentation for F# community

  • The F# Documentation is the primary documentation for F#. The source for the content is here.

  • The F# Language Design Process is the fundamental design process for the language, from suggestions to completed RFCs. There are also tooling RFCs for some topics where cross-community co-operation and visibility are most useful.

  • The F# Language Specification is an in-depth description of the F# language. This is essential for understanding some behaviors of the F# compiler and some of the rules within the compiler codebase. For example, the order and way name resolution happens are specified here, which greatly impacts how the code in Name Resolutions works and why certain decisions are made.

No contribution is too small

Even if you find a single-character typo, we're happy to take the change! Although the codebase can feel daunting for beginners, we and other contributors are happy to help you along.

Not sure where to contribute?
Look at the curated list of issues asking for help. If you want to tackle any of those, use the comments section of the chosen issue to indicate interest and feel free to ask for initial guidance. We are happy to help with resolving outstanding issues while making a successful PR addressing the issue.

The issues in this repository can have big differences in the complexity for fixing them.
Are you getting started? We do have a label for good first issues as well.

Per-build NuGet packages

7.0.40x series

FSharp.Compiler.Service 43.7.400-preview

xml
<add key="fsharp-prerelease" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet7/nuget/v3/index.json" />

8.0.10x series

FSharp.Compiler.Service 43.8.100-preview

xml
<add key="fsharp-prerelease" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet8/nuget/v3/index.json" />

NOTE: Official NuGet releases of FCS and FSharp.Core are synched with SDK releases (on purpose - we want to be in sync). Nightly packages release to Azure feeds on every successful insertion.

Branches

These are the branches in use:

  • main

    • Almost all contributions go here.
    • Able to be built, installed and used in the latest public Visual Studio release.
    • May contain updated F# features and logic.
    • Used to build nightly VSIX (see above).
  • release/dev15.9

    • Long-term servicing branch for VS 2017 update 15.9.x. We do not expect to service that release, but if we do, that's where the changes will go.
  • release/dev17.x

    • Latest release branch for the particular point release of Visual Studio.
    • Incorporates features and fixes from main up to a particular branch point, then selective cherry-picks.
    • May contain new features that depend on new things or fixes in the corresponding forthcoming Visual Studio release.
    • Gets integrated back into main once the corresponding Visual Studio release is made.

F# language and core library evolution

Evolution of the F# language and core library follows a process spanning two additional repositories. The process is as follows:

  1. Use the F# language suggestions repo to search for ideas, vote on ones you like, submit new ideas, and discuss details with the F# community.
  2. Ideas that are "approved in principle" are eligible for a new RFC in the F# language design repo. This is where the technical specification and discussion of approved suggestions go.
  3. Implementations and testing of an RFC are submitted to this repository.

License

This project is subject to the MIT License. A copy of this license is in License.txt.

Code of Conduct

This project has adopted the Contributor Covenant code of conduct to clarify expected behavior in our community. You can read it at CODE_OF_CONDUCT.

Get In Touch

Members of the F# Software Foundation are invited to the FSSF Slack. You can find support from other contributors in the #compiler and #editor-support channels.

Additionally, you can use the #fsharp tag on Twitter if you have general F# questions, including about this repository. Chances are you'll get multiple responses.

About F\#

If you're curious about F# itself, check out these links:

Contributors โœจ

F# exists because of these wonderful people:

Made with contrib.rocks.


Overview (`tests/README.md`)

F# Testing proposal

Why do we test

  • To prevent regressions (behavioral, performance).
  • To have a quicker debug feedback (thus, find problems quicker).
  • To verify conformance to language spec (API contract testing).
  • To have IL verification (both read and write).
  • To have a quicker design feedback.
  • To document behavior.

Goals

  • Use one standardized testing framework across all test projects โœ…
  • Have tests restructured the way, that they are easy to discover.
  • Have tests building and running on all supported platforms (Windows, macOS and Linux) and different frameworks (with exceptions when this is not applicable).
  • Make it easy to run tests using standard .NET instruments (dotnet cli, test explorer, etc.).
  • Leverage standard .NET testing platform and use all its benefits, suck as live unit testing, code coverage collecting, dead code elimination, etc.

Framework for testing

The following test frameworks and libraries will be used for new test projects xUnit Test Framework and FsCheck when needed).

Justification:

  • xUnit is an extensible, TDD adherent, testing framework, which was successfully adopted by many .NET engineering teams, including Roslyn, AspNetCore, EFcore, etc, has a "cleaner" approach for writing test suites (i.e. class constructor for setup, implementing IDisposable for teardown, as oppose to custom attributes). More info here.

Alternatives: NUnit, MSBuild, Expecto

Tests categorization

New tests should be grouped based on two factors: test type (1) + test category and subcategory (2)

  1. Test type:
    Determines what type of test is it:
    • Functional tests:
      • Unit Tests: a lightweight testing for smaller modules, functions, etc.
        • Examples: Testing individual parts/functions of lexer, parser, syntax tree, standard library modules, etc.
        • Subgroups: there should be a separation between testing private and public parts of each module (i.e. compiler tests for private and public API should be in separate test projects).
      • Component Tests: testing for bigger parts of compiler.
        • Examples: Tests for the compiler components as whole, such as Code generation, IL Generation, Compiler optimizations, Type Checker, Type Providers, Conformance, etc.
      • Integration and End2End Tests: testing of F# compiler & tooling integration, as well as e2e experiences.
        • Examples: VS Integration, .NET Interactive integration, LSP integration. Integration with dotnet CLI, project creation, building, running.
    • Non-functional tests:
      • Load and Stress Tests: testing for high level modules/components to understand peak performance and potentially catch any performance regressions.
        • Examples: measuring compile, build, link times for the compiler, individual functions (i.e. data structures sorting, traversing, etc.).
  2. Test category and subcategory: Tests (sub)categories shall be determined by the project, library, module, and functionality tests are covering.

Examples

  • F# compiler component test which is verifying generated IL for computation expression will have category Compiler and subcategories EmittedIL and ComputationExpressions.
  • F# compiler service unit test which is testing F# tokenizer, will have category Compiler.Service and subcategory Tokenizer.

Please, refer to File and project structure for more information on how tests will be organized on the filesystem.

File and project structure

Naming schema

The proposed naming schema for test projects is: FSharp.Category.Subcategory.TestType, where
Category.Subcategory is either a corresponding source project, or a more generic component (e.g. Compiler, Compiler.Private or more granular Compiler.CodeGen, Compiler.CodeGen.EmittedIL if category or subcategory project becomes too big, etc.) and TestType is the type of the test (one of UnitTests, ComponentTests, IntegrationTests, LoadTests).

Projects organization

Please refer to the "Naming schema" section above for more information on the projects naming.

New test projects will be grouped by category and test type, all subcategories are just test folders/files in the test project.

  • Examples: Having test project organized like:

    tests/FSharp.Compiler.ComponentTests/CodeGen/EmittedIL/BasicTests.fs
    tests/FSharp.Compiler.ComponentTests/CodeGen/StringEncoding/StringTests.fs
    tests/FSharp.Compiler.ComponentTests/Optimizations/Inlining/InliningTests.fs

    Will result in one test dll "FSharp.Compiler.ComponentTests.dll" which will contain all the subcategories of tests.

  • Notes:

    • This will result in reduced fragmentation of tests, all the tests files are under one big category, easier to understand what each component/unit test suite covers, less confusion in test classification for new tests.
    • If some categories (or subcategories) will become big enough - they can be factored out to a separate project.

Test Utilities/Helpers

For all new and migrated tests, any common/helper functionality shall be factored out to a separate project - FSharp.Test.Utilities.

New tests

  • All new tests should be created in the new projects only.
  • All new tests should contain a brief docstring description of what is being tested, link to an issue if applicable.
  • All new tests should be categorized using xUnit's Trait, based on their Category and Subcategories.

Migrating existing tests

All legacy test suites (FSharpQA, Cambridge) have been migrated to xUnit-based test projects.

Next steps

  • Clean up CompilerAssert.
  • Make PEVerify tests work in netcore/non-windows environment.

Open questions:

  • As far as I know, FSharp.Compiler.Service is dependent on some of the F# compiler tests. Does it have to be changed as well?

Other

Related issues: (https://github.com/dotnet/fsharp/issues/7075)

You can find this document under 'tests/README.md'.


Overview (`vsintegration/readme.md`)

This folder contains projects and tests related to Visual Studio tooling.

src/FSharp.Editor

Top-level project for the Visual F# IDE tools. This project contains the following

  • Interfaces which implement Roslyn workspace APIs
  • Top-level interactions with the F# Compiler Service
  • Logic for handling data in the F# Compiler Service
  • Helpers for interacting with Roslyn APIs
  • Various utilities

In general, if something is implemented here and the logic becomes rather lengthy, it may be a good idea to push that logic down into the F# Compiler Service so that other editors can benefit.

src/FSharp.VS.FSI

F# Interactive implementation.

src/FSharp.UIResources

GUI controls and resources for Visual F# tooling.

src/FSharp.LanguageService

Legacy bindings to the F# Compiler Service. Most of the code paths here are dead, and exist mainly to allow our test suite to extensively test the F# Compiler Service.

src/FSharp.LanguageService.Base

Legacy bits for a base-level, editor-agnostic language service. This code isn't used anywhere else, though.

src/FSharp.ProjectSystem.Base

Legacy bindings to the legacy (and unsupported) MPF project system type in Visual Studio. This code will eventually be deprecated.

src/FSharp.ProjectSystem.FSharp

Legacy project system to handle F# projects targeting the .NET Framework, with some code paths also handling .NET Core. The latter will eventually be factored out, as this code will eventually be deprecated.

src/FSharp.ProjectSystem.PropertyPages

GUI for F# project properties.

tests/Salsa

Legacy tooling for IDE unit tests. Used extensively in tests, hence it still exists.

tests/unittests

IDE unit tests. Some code paths go through bits which aren't ever executed when using F# in Visual Studio, but they do extensively test the F# Compiler Service.

utils/LanguageServiceProfiling

A skeleton command line tool which exercises the F# Compiler Service.

ItemTemplates

Visual Studio item templates for F# projects.

ProjectTemplates

Visual Studio project templates for .NET Framework projects.


Overview (`docs/postmortems/README.md`)


title: Overview category: Postmortems categoryindex: 550 index: 100

Postmortems

Detailed write-ups of bugs that were hard to diagnose, had non-obvious root causes, or taught us something worth preserving. Each document captures the symptoms, root cause, fix, and timeline so that future contributors can recognize similar patterns early.

These are referenced from agentic instructions and serve as deeper reading โ€” the instructions tell you what to do, the postmortems explain why the rules exist.

Index


Overview (`eng/common/README.md`)

Don't touch this folder

text
uuuuuuuuuuuuuuuuuuuu
          u" uuuuuuuuuuuuuuuuuu "u
        u" u$$$$$$$$$$$$$$$$$$$$u "u
      u" u$$$$$$$$$$$$$$$$$$$$$$$$u "u
    u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u
  u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u
u" u$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$u "u
$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $
$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $
$ $$$" ... "$...  ...$" ... "$$$  ... "$$$ $
$ $$$u `"$$$$$$$  $$$  $$$$$  $$  $$$  $$$ $
$ $$$$$$uu "$$$$  $$$  $$$$$  $$  """ u$$$ $
$ $$$""$$$  $$$$  $$$u "$$$" u$$  $$$$$$$$ $
$ $$$$....,$$$$$..$$$$$....,$$$$..$$$$$$$$ $
$ $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ $
"u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u"
  "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u"
    "u "$$$$$$$$$$$$$$$$$$$$$$$$$$$$" u"
      "u "$$$$$$$$$$$$$$$$$$$$$$$$" u"
        "u "$$$$$$$$$$$$$$$$$$$$" u"
          "u """""""""""""""""" u"
            """"""""""""""""""""

!!! Changes made in this directory are subject to being overwritten by automation !!!

The files in this directory are shared by all Arcade repos and managed by automation. If you need to make changes to these files, open an issue or submit a pull request to https://github.com/dotnet/arcade first.


Overview (`src/FSharp.DependencyManager.Nuget/README.md`)

F# Interactive Dependency Manager Plugins

Since F# 5.0, dotnet fsi and FsiAnyCPU.exe (the .NET Framework variant) ships with dependency manager plugins support that can be called like so:

fsharp
#r "myextension: my extension parameters"

For F# Interactive to load the extensions[^1], they either need to:

  • be deployed next to fsi.dll of dotnet SDK (or next FsiAnyCPU.exe for .NET Framework)
  • refered to via --compilertool:<extensionsfolderpath>[^2] argument

The same goes for the tooling hosting F# Compiler Service.

The initial RFC for this feature overviews how it is designed.

More about F# Interactive References

The current implementation follows pattern that can be deducted by referring to implementation in DependencyProvider.fs; due to the system working without having a statically linked dependency, it uses a late binding approach leveraging runtime reflection to identify if an extension conforms to patterns understood by the specifics of the compiler implementation.

`#r "nuget:"` [nuget](https://github.com/dotnet/fsharp/tree/main/src/fsharp/FSharp.DependencyManager.Nuget)

Reference nuget packages, ships by default with dotnet fsi.

fsharp
#r "nuget: Newtonsoft.Json"
// Optionally, specify a version explicitly
// #r "nuget: Newtonsoft.Json,11.0.1"

open Newtonsoft.Json

let o = {| X = 2; Y = "Hello" |}

printfn "%s" (JsonConvert.SerializeObject o)

`#r "paket:"` [paket](https://fsprojects.github.io/Paket/fsi-integration.html)

Reference dependencies (nuget, git, gist, github) through Paket package manager.

Learn how to use Paket FSI integration.

fsharp
#r "paket: nuget FSharp.Data"

open FSharp.Data

type MyCsv = CsvProvider<"""
X,Y
2,Hello
4,World
""">

for r in MyCsv.GetSample().Rows do
  printfn "%i = %s" r.X r.Y

[^1]: Referencing packages in F# Interactive
[^2]: F# Interactive options


Overview (`tests/benchmarks/README.md`)

Benchmarks

What can be found here

This folder contains code and scripts used for running a selection of performance benchmarks.

How the tests are used

The existing benchmarks are designed for on-demand local runs, to guide developers in performance improvement efforts and provide limited information during code change discussions.
Each of them assesses a slightly different use case and is run in a different way.

Since there is currently no dedicated hardware setup for running benchmarks in a highly accurate fashion, the results obtained by running them locally have to be treated carefully.
Specifically results obtained on different hardware or in different environments should be treated differently.

Types of performance tests

Performance tests in this codebase can be broadly put into two groups:

  1. tests that measure runtime performance of code produced from F# source code,
  2. tests that measure performance of the compilation process itself. This involves any computations required by IDEs, for example type checking.

Group 1. affects end users of programs, while group 2. affects developer experience.

Directory structure

The code is structured as follows

  • CompiledCodeBenchmarks/ - benchmarks that test compiled code performance.
  • FCSBenchmarks/ - benchmarks of the compiler service itself.
  • FSharp.Benchmarks.Common/ - the library with common code.

Jupyter notebooks

Some benchmarks are written using F# Notebooks that use the .NET Interactive kernel.
Those can be identified by the .ipynb extension.
For instruction on how to run them see https://fsharp.org/use/notebooks/.

BenchmarkDotNet

Most of the benchmarks use BenchmarkDotNet (BDN), a popular benchmarking library for .NET.
It helps avoid common benchmarking pitfalls and provide highly-accurate, repeatable results.

A BDN benchmark is an executable. To run it, simply run dotnet run %BenchmarkProject.fsproj% in the benchmark's directory.

Quickly validating that the benchmarks work

SmokeTestBenchmarks.ps1 allows to run faster BDN benchmarks with a minimum number of iterations, as a way to verify that the benchmarks still work. This doesn't validate the notebook-based meta-benchmarks.

Authoring benchmarks

When adding a benchmark, consider:

  • choosing an appropriate subdirectory
  • adding a README with the following details:
    • what is being measured
    • how to run the benchmark, including any environment requirements
    • an example of the results it produces

Here are the steps for creating benchmarks:

  1. Perform a clean build of the compiler and FCS from source (as described in this document, build can be done with -noVisualStudio in case if FCS/FSharp.Core is being benchmarked/profiled).

  2. Create a benchmark project (in this example, the project will be created in tests\benchmarks\FCSBenchmarks).

    shell
    cd tests\benchmarks\FCSBenchmarks
    dotnet new console -o FcsBench --name FcsBench -lang F#
  3. Add needed packages and project references.

    shell
    cd FcsBench
    dotnet add package BenchmarkDotNet
    dotnet add reference ..\..\..\src\Compiler\FSharp.Compiler.Service.fsproj
  4. Additionally, if you want to test changes to the FSharp.Core (note that the relative path can be different)

    shell
    dotnet add reference ..\..\..\src\FSharp.Core\FSharp.Core.fsproj

    And the following property has to be added to FcsBench.fsproj:

    xml
    <PropertyGroup>
        <DisableImplicitFSharpCoreReference>true</DisableImplicitFSharpCoreReference>
    </PropertyGroup>
  5. Add a new benchmark for FCS/FSharp.Core by editing Program.fs.

    text
    /* Detailed source-code truncated for AI context efficiency. */
text
For more detailed information about available BenchmarkDotNet options, please refer to [BenchmarkDotNet Documentation](https://benchmarkdotnet.org/articles/overview.html).

6. Build and run the benchmark.

      ```shell
      dotnet build -c Release
      dotnet run -c Release
      ```

7. You can find results in `.\BenchmarkDotNet.Artifacts\results\` in the current benchmark project directory.

    ```shell
    > ls .\BenchmarkDotNet.Artifacts\results\

        Directory: C:\Users\vlza\code\fsharp\tests\benchmarks\FCSBenchmarks\FcsBench\BenchmarkDotNet.Artifacts\results

    Mode                 LastWriteTime         Length Name
    ----                 -------------         ------ ----
    -a---           4/25/2022  1:42 PM            638 Program.CompilerService-report-github.md
    -a---           4/25/2022  1:42 PM           1050 Program.CompilerService-report.csv
    -a---           4/25/2022  1:42 PM           1169 Program.CompilerService-report.html
    ```

    *-report-github.md can be used to post benchmark results to GitHub issue/PR/discussion or RFC.
    
    *-report.csv can be used for comparison purposes.

    **Example output:**

    ``` ini

    BenchmarkDotNet=v0.13.1, OS=Windows 10.0.25102
    Intel Core i7-8750H CPU 2.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores
    .NET SDK=6.0.200
      [Host]     : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT DEBUG
      Job-GDIBXX : .NET 6.0.3 (6.0.322.12309), X64 RyuJIT

    InvocationCount=1  UnrollFactor=1

    ```

    |                    Method |     Mean |   Error |  StdDev |   Median |     Gen 0 |     Gen 1 | Allocated |
    |-------------------------- |---------:|--------:|--------:|---------:|----------:|----------:|----------:|
    | ParsingCheckExpressionsFs | 199.4 ms | 3.84 ms | 9.78 ms | 195.5 ms | 4000.0000 | 1000.0000 |     28 MB |

8. Repeat for any number of changes you would like to test.
9. **Optionally:** benchmark code and results can be included as part of the PR for future reference.

---

### Overview (`tests/fsharp/readme.md`)

# F# Compiler Cross-Platform Test Suite

## Layout

The tests are xUnit test cases. They test a very wide range of compiler, interactive and FSharp.Core scenarios.

The bulk of the test cases are enumerated in tests.fs, these are the old cambridge test suite.  They build on a test-suite ported from windows batch files.  They run the compiler and fsi as separate processes, when built for the coreclr it runs the coreclr versions using dotnet.exe 

The framework and utilities can be found in test-framework.fs, single-test.fs.

test cases look similar to:
text
[<Fact>]
let ``array-FSI`` () = singleTestBuildAndRun "core/array" FSI
text
This test case builds and runs the test case in the folder core/array

this #define is used to exclude from the build tests that run will not run correctly on the coreclr
__#if !NETCOREAPP__

There are some older tests in this section that looks similar to:
text
[<Fact>]
let events () = 
    let cfg = testConfig "core/events"
    fsc cfg "%s -a -o:test.dll -g" cfg.fsc_flags ["test.fs"]
    peverify cfg "test.dll"
    csc cfg """/r:"%s" /reference:test.dll /debug+""" cfg.FSCOREDLLPATH ["testcs.cs"]
    peverify cfg "testcs.exe"
    use testOkFile = fileguard cfg "test.ok"
    fsi cfg "" ["test.fs"]
    testOkFile.CheckExists()
    exec cfg ("." ++ "testcs.exe") ""
text
These tests build, compile, peverify and run fsi.

Below the Compiler directory there is a set of tests built on the compiler service.  They are xUnit and instead of executing the compiler and fsi using files on disk the tests are built from memory.  These tests use the CompilerAssert framework and look similar to:

This test verifies that a warning is produces when a value is implicitly discarded.  The line ````x = 20``` looks like an assignment but in F# is a test for equality it yields and discards the value false.
text
[<Fact>]
let ``Unused compare with immutable when assignment might be intended``() =
    CompilerAssert.TypeCheckSingleError
        """

let x = 10
let y = "hello"

let changeX() =
x = 20
y = "test"
"""
FSharpDiagnosticSeverity.Warning
20
(6, 5, 6, 11)
"The result of this equality expression has type 'bool' and is implicitly discarded. Consider using 'let' to bind the result to a name, e.g. 'let result = expression'. If you intended to mutate a value, then mark the value 'mutable' and use the '<-' operator e.g. 'x <- expression'."

text
## Workflow when adding or fixing tests

When a test is run, .err/.vserr output files are created and compared to their matching .bsl files.

Refer to [Test Guide](../../TESTGUIDE.md#baselines) to know more about how to update them.

Tests are organized under modules as functions bearing xUnit `[<Fact>]` attribute and can be run from an IDE or the command line (see the [Test Guide](../../TESTGUIDE.md)).

---

### Overview (`eng/pipelines/apex-integration/README.md`)

# F# Apex integration tests on DartLab-1ES

This pipeline installs Visual Studio from `DD-CB-ReleaseVS` on a DartLab-1ES
CloudTest machine, builds and deploys the PR's F# VSIX, then runs the Apex tests in Release.
It avoids the dependency skew of older preinstalled CI images. The chosen VS drop
must still support the PR's package pins; a recent drop is not a compatibility guarantee.

## Requesting a run

Post **`/dart <sha>`** or **`/pr-val <sha>`** as the entire comment on an open
`dotnet/fsharp` PR targeting `main`. Replace `<sha>` with the full 40-character
lowercase SHA of the current PR head after reviewing that revision, including its
pipeline YAML, templates and scripts. Short, stale or non-head SHAs are rejected.
Both commands run the same pipeline. Fork PRs are supported.
The commenter must have repository write, maintain or admin access **and** be a
member of the `microsoft` GitHub organization. Supplying the SHA is the requester's
explicit authorization of that revision, not an automatic check of GitHub review status.

The [GitHub workflow](../../../.github/workflows/pr-validation.yml) captures the PR
head and base SHAs and queues Azure DevOps over OIDC. It posts a run link and those
revisions to the PR. New pushes, PR events and VS builds do **not** request runs.
Administrators can still queue runs manually, subject to pipeline permissions.

The [pipeline](../../../azure-pipelines-integration-dartlab.yml) is registered
directly against GitHub `dotnet/fsharp`; its `self` is **not** the dnceng mirror.
The Actions workflow and authorization helper run from the default branch.
The workflow pins pipeline YAML and local templates to `refs/pull/<number>/head`
at the explicitly requested head SHA, including for fork PRs. On the test machine,
[`setup-pr-validation.ps1`](../../setup-pr-validation.ps1) fetches the public PR
merge ref from a checkout of that reviewed head, verifies both parents against
the captured head/base SHAs, checks the PR is still open and observes current
head/base refs before checking out that exact merge.

A conflict, missing/stale merge, changed head/base, or API/Git failure stops the run
before building PR code in the provided pipeline. Review the updated head and
post a new command with its SHA after resolving the problem. A push
after verification does not alter the detached commit being tested. The setup and
test logs record the actual merge SHA; Azure DevOps `Build.SourceVersion` identifies
the reviewed PR head/YAML revision, **not** the tested PR merge. Results apply only
to the recorded snapshot, not automatically to later PR revisions.

## Registration and authorization

Provision these before enabling requests:

1. In **devdiv/DevDiv -> Pipelines -> New pipeline -> GitHub**, select `dotnet/fsharp`
   and the existing `azure-pipelines-integration-dartlab.yml`, default branch `main`.
   Use an approved Azure Pipelines GitHub App connection restricted to the necessary
   repository; authorize this pipeline specifically. No `dnceng-internal-code-access`
   connection is needed. Leave the existing mirror and dnceng pipelines unchanged.
2. Authorize `DevDiv/DartLab`, `DevDiv/VS.Templates`, the `DD-CB-ReleaseVS`
   pipeline/artifacts, required feeds, and the `DartLab-1ES` service connection.
   The stage uses the production CloudTest templates from `DevDiv/DartLab`; it has
   no dependency on the legacy `DevDiv/DartLab.Templates` DTL repository.
3. The DartLab team assigned the F# pipeline to the
   **`DotNet-Project-System-AMD64`** CloudTest lab. The owner notification alias
   is `fsharp`. Confirm the VS drop and installed components support F#.
4. Create the GitHub **`fsharp_pr_validation`** environment. Set its variable
   **`FSHARP_APEX_PIPELINE_ID`** to the new DevDiv pipeline's numeric ID.
   Set **`AZURE_CLIENT_ID`** and **`AZURE_TENANT_ID`** secrets for an Entra identity
   federated to `repo:dotnet/fsharp:environment:fsharp_pr_validation`.
   Grant it permission to queue this pipeline, not edit pipeline definitions.
   This flow does not require an Azure subscription.
5. Provide **`MICROSOFT_MEMBERS_APP_ID`** and **`MICROSOFT_MEMBERS_APP_PRIVATE_KEY`**
   environment secrets for an approved GitHub App installed in `microsoft`, with
   organization **Members: read** permission. The workflow requests a short-lived
   membership-only token. The dotnet/fsharp `GITHUB_TOKEN` alone cannot be assumed
   to see private memberships in another organization. Verify lookup with a private
   member before rollout. Lookup errors fail closed; they do not prove nonmembership.
6. Have the lab owners approve execution of fork PR code and verify effective
   credential/network isolation. This is a manually queued build that executes
   reviewed PR pipeline YAML: **do not assume automatic fork-build secret
   restrictions apply**. PR YAML can change steps and resource usage before the
   provided source-verification script runs, so the SHA gate is not a sandbox.
   Review job tokens, feed credentials, service connections and internal
   network access. Git fetches use the public repository, and the Actions OIDC and
   membership tokens are not sent to the test machine, but these measures alone do
   not sandbox a build. Do not enable this pipeline if lab policy cannot support it.

These are three separate identities: the Azure Pipelines GitHub source connection,
the Entra queue identity, and the Microsoft-org membership lookup App. None replaces
the others. Protect `main`, restrict who can edit/queue this privileged pipeline,
keep `testMachineHoldStrategy: None` as the default, and authorize no
signing/publishing credentials. Use `Failure` or `Completion` only for a bounded
investigation; CloudTest held machines are accessed through the generated Bastion link.

Microsoft supports [GitHub pipelines in multiple Azure DevOps organizations][github].
Only the first organization receives automatic GitHub push/PR triggers; manual queueing
remains supported in secondary organizations. This workflow uses the [Runs REST API][runs],
so the restriction does not require moving existing dnceng CI or enabling PR triggers.

## Validation and activation

Local regression commands (no product build or DartLab access required):

```powershell
pwsh -NoProfile -File .github\scripts\pr-validation.Tests.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File eng\tests\SetupPrValidation.Tests.ps1
```

The script tests use temporary local Git repositories and a mocked GitHub PR response.
They cover merge identity, ref races, closed/changed PRs and failure before checkout.
The PowerShell 7 tests run the Actions helper offline with a fake HTTP boundary.
They cover command syntax, authorization of the original commenter (also on reruns),
private membership lookup with the separate App token, the queued snapshot and PR feedback.

After provisioning, use the Runs API's `previewRun` with the same repository
ref/version, snapshot parameters, `DD-CB-ReleaseVS` resource pin and confirmed
`testMachineLab` to verify actual private-template expansion. Fix every template,
parameter, expression, service-connection and operation-expansion error before
allocating a CloudTest machine. Verify GitHub `self.version` pinning against the
requested head SHA for same-repository and fork PRs, and the test-machine source
bootstrap; local YAML parsing cannot establish either. Land the Actions
workflow/authorization helper on main before activation, and configure the
environment and real pipeline ID together.

Explicitly request same-repository and fork PR smoke runs. Confirm the exact merge
SHA, installed VS, VSIX load, Azure DevOps test counts, Apex results and CloudTest
TRX/build/setup log artifacts. Exercise changed head/base rejection and an
unauthorized request that queues no run. Exercise missing, short, mismatched and
stale command SHAs. Verify the compiled YAML/local templates come from the
explicitly requested PR head rather than main or a newer push, and no UI overrides,
schedules or resource triggers introduce automatic runs. Completion is not reported
back as a new required GitHub check; the PR comment links to DevDiv results.

Source-connection precedent: [NuGet/NuGet.Client#5936][nuget] introduced a self-contained
VS-test pipeline for manual PR-branch queueing. F# retains its VS-drop selection and
Apex build path while using DartLab-1ES CloudTest operations rather than legacy DTL
agent step lists.

[github]: https://learn.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops
[runs]: https://learn.microsoft.com/en-us/rest/api/azure/devops/pipelines/runs/run-pipeline?view=azure-devops-rest-7.1
[nuget]: https://github.com/NuGet/NuGet.Client/pull/5936

---

### Overview (`tests/EndToEndBuildTests/MultithreadedTasks/README.md`)

# SDK multithreaded task integration

Use the pinned repository SDK, not Visual Studio MSBuild or a system `dotnet`.
The host must expose `IMultiThreadableTask`, `TaskEnvironment`, and `ToolTask.TaskEnvironment`.
The validated SDK is `11.0.100-rc.1.26420.103`, with runtime MSBuild `18.11.0.42103`.
The `Microsoft.Build.*` package version (`18.12.0-1.26454.5`) is not the runtime host version.
This does not establish support for all MSBuild 18.x hosts.
The production tasks retain their parameterless constructors and existing base types.

Build the local products:

```sh
./build.sh -c Release --mt true
```

On Windows, use `.\build.cmd -configuration Release -noVisualStudio`.

Run the focused task tests:

```sh
./eng/common/dotnet.sh test --project tests/FSharp.Build.UnitTests/FSharp.Build.UnitTests.fsproj -c Release --report-spekt-xunit --report-spekt-xunit-filename FSharp.Build.UnitTests.Linux-MT.xml --results-directory artifacts/TestResults/Release
```

Run the SDK E2E from the repository root:

```sh
./eng/common/dotnet.sh fsi tests/EndToEndBuildTests/MultithreadedTasks/run.fsx -- --configuration Release --repetitions 3
```

On Windows, replace `./eng/common/dotnet.sh` with `.\eng\common\dotnet.cmd`.
CI runs this harness as required steps in the existing `Linux` and Windows `EndToEndBuildTests` jobs.
The Linux job runs the harness and publishes its evidence in Batch 1 only.
Both reuse their built products and publish the SDK evidence. There are no separate MT jobs.
The existing Linux and macOS build jobs use Arcade's `--mt true` option.
The three `Plain_Build_*` SDK jobs pass `-mt` directly to MSBuild.
Those plain builds use SDK-shipped tasks; the redirected product builds and this harness exercise the migrated tasks.
VS/MSBuild.exe jobs and Arcade-managed source-build configuration are unchanged.
No repository-wide environment override or replacement for Arcade's MT controls is added.

Use `--repetitions 10` for local stress runs.
Each repetition compares `/m:4 -mt:false` and `/m:4 -mt` builds of 16 independent SDK F# executables.
The child processes clear `MSBUILDFORCEMULTITHREADED` so an enclosing CI setting cannot override the MP control.
An inherited `MSBUILDENABLEMULTITHREADED` default is overridden by the explicit mode switch.
Each clean build is followed by an unchanged incremental build. Fsi validation reruns, but Fsc must not execute.
The minimum project count is four.
The harness needs access to the repository NuGet feeds for self-contained runtime and ILLink packs.
It adds no package references.

The fixture uses relative source, resource, intermediate, and output paths from an unrelated invocation directory.
It redirects the tasks, targets, Fsc, Fsi, and FSharp.Core to local products.
It loads `artifacts/bin/FSharp.Build/<configuration>/netstandard2.0/FSharp.Build.dll` directly, not the compiler directory's potentially stale copy.
It checks these nine tasks: Fsc, Fsi, WriteCodeFragment, FSharpEmbedResourceText, FSharpEmbedResXSource, CreateFSharpManifestResourceName, MapSourceRoots, GenerateILLinkSubstitutions, and SubstituteText.

Evidence includes:

- Exact task assembly paths from task-start events, loaded-assembly checks, and the assembly SHA-256.
- Runtime API checks and assembly paths/file versions from each execution host, not the NuGet reference assemblies.
- Four live Fsi processes at a barrier, overlapping Fsc intervals, and project-specific environment and resource assertions.
- One MT project-process PID matching the SDK routing caller, versus multiple MP PIDs.
- Mandatory out-of-process routing diagnostics for an unmarked control task, with none for the nine migrated tasks.
- Matching artifact hashes and executed resource checks across clean and incremental builds, with zero incremental Fsc executions.
- Expected malformed-resource and type errors, with baseline-identical, runnable sibling projects.
- Self-contained `PublishTrimmed` builds in both modes, with substitutions enabled and disabled.
  Both settings must retain the original application metadata, matching the existing target scheduling.
  All four published executables must pass resource checks.

Generated ILLink XML alone is not evidence of trimming. These publish checks establish MP/MT parity, not activation of metadata stripping.
A trimming failure fails the harness, even when the generated XML and build hashes match.
Missing concurrency, task-loading, or routing evidence fails the run.
Logs, binlogs, event records, generated fixtures, and hash manifests remain under `artifacts/MultithreadedTasks/<timestamp>/`.

The Windows SDK E2E step uses the SDK host. This harness does not start Visual Studio or exercise its HostObject integration.

---

### Overview (`tests/EndToEndBuildTests/ProvidedTypes/readme.md`)

ProvidedTypes.fs and fs retrieved from:             https://github.com/fsprojects/FSharp.TypeProviders.SDK

For commit Hash:  f0c4a6300db313eb2616c4f792cd6ae68c255e56

---

### Overview (`tests/benchmarks/CompiledCodeBenchmarks/README.md`)

# Compiled Code Benchmarks

This benchmarks project is meant to be used to test the performance of code generated by the F# compiler. It is set up so that, by default, it will build and run your benchmarks in two jobs:

- The first will be built using the local compiler targeting the current language version and will be used as the baseline.
- The second will be built using the local compiler targeting the `preview` language version.

Note that the intent is generally that you use this project to benchmark your changes locally. Most of the time, you will not need to check in your benchmarks.

#### Build the repo with the `Release` config

```pwsh
./Build.cmd -c Release
```

#### Run the benchmarks

```pwsh
dotnet run --project .\tests\benchmarks\CompiledCodeBenchmarks\MicroPerf\MicroPerf.fsproj -c Release
```

The benchmark switcher will prompt you to choose which benchmark or benchmarks you want to run.

#### Sample output

```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### Overview (`tests/benchmarks/FCSBenchmarks/README.md`)

# FCSBenchmarks

## What can be found here

Benchmarks that exercise performance of `FSharp.Compiler.Service`.

## Testing performance of FSharp.Compiler.Service

Performance of the compiler service is crucial for having good developer experience.
This includes compilation, type checking and any other parts of the API used by IDEs.

When making changes to the FCS source code, consider running some of these to assess impact of the changes on performance.

## Benchmark list

* `BenchmarkComparison/` - a Notebook-based benchmark that analyses performance of `FSharpChecker.ParseAndCheckFileInProject` on a single-file F# project. Supports comparing different revisions of the FCS codebase and fetching the code automatically.
* `CompilerServiceBenchmarks/` - a selection of BDN benchmarks analyzing FCS performance
* `FCSSourceFiles/` - analyses performance of `FSharpChecker.ParseAndCheckFileInProject` for the `FSharp.Core` project. Uses locally available source code.

All the above benchmarks use BenchmarkDotNet.

---

### Overview (`tests/benchmarks/FCSBenchmarks/BenchmarkComparison/README.md`)

# BenchmarkComparison

## What is it

- A meta-benchmark that compares performance between versions of the FCS codebase for a single file.
- Notebook-based, see `runner.ipynb`.
- Each run performs `FSharpChecker.ParseAndCheckFileInProject` on a single file (see `../decentlySizedStandAloneFile.fs`).

## How to run it

To run a benchmark for a local FCS in the current codebase you can run the benchmark directly:

```dotnet run --project HistoricalBenchmark.fsproj -c Release --filter *```

To run a comparison use the `runner.ipynb` .NET notebook.

## How it works

- `runner.ipynb` runs `HistoricalBenchmark.fsproj` for a selection of versions/commits/local codebases.
- Individual runs involve running a BDN benchmark in a separate process (via `HistoricalBenchmark.fsproj`).
- For each version:
  1. For a commit-based run we fetch FCS at a given revision from GitHub into a temporary folder and build it via `build.cmd`
  2. `HistoricalBenchmark.fsproj` is built with custom MSBuild properties that reference FCS in a specific way (project/package or dll reference)
  3. We run `HistoricalBenchmark` which is a BDN benchmark
  4. `runner.ipynb` then parses CSV results from all runs and plots a chart

## `HistoricalBenchmark` and backwards compatibility

Due to the requirement to run the same benchmark on older versions of the codebase and minor changes in the API, code in `HistoricalBenchmark` can be compiled in three different ways by adding one of the following DEFINEs:
- `SERVICE_13_0_0`
- `SERVICE_30_0_0`
- _none_ (default, uses latest API)

As of now the minimum supported version of FCS is 13.0.0

## Sample results

See sample results in the dedicated [sample_results](./sample_results/) folder.

---

### Overview (`tests/benchmarks/FCSBenchmarks/CompilerServiceBenchmarks/README.md`)

# CompilerServiceBenchmarks

## What is it

* A selection of BDN benchmarks analyzing FCS performance
* Uses BDN's command line API

## How to run it

Running all benchmarks:
```dotnet run -c Release --filter *```

Running a specific benchmark:
```dotnet run -c Release --filter *ParsingCheckExpressionsFs*```

## Namespace-import benchmarks (lazy ILPreNamespace)

`NamespaceImportBenchmarks.fs` measures the effect of lazy namespace reading in the IL reader: a project
that references large **non-F#** assemblies (the BCL) but opens only a few namespaces should read and
retain less. F# assemblies are unpickled and do not exercise this path.

Startup time + transient allocation (BDN, `MemoryDiagnoser`):
```dotnet run -c Release --filter *NamespaceImportStartup*```
`NarrowImport` opens one namespace (where laziness should pay off); `WideImport` opens many (control โ€”
should stay flat). Compare the `Mean` and `Allocated` columns across `main` and this branch.

Retained (live-heap) memory โ€” MemoryDiagnoser only sees allocation *during* an op, not what survives, so
this is a separate standalone probe:
```dotnet run -c Release -- retained-memory```
It prints retained KB for Narrow and Wide. The win is un-opened namespaces never being *retained*, so
compare `Narrow` retained across `main` and this branch (and Narrow-vs-Wide within a branch).

To compare branches: build + run on `main`, note the numbers, `git checkout il-pre-namespace`, rebuild
and re-run, diff. (The `BenchmarkComparison/` project automates historical before/after runs if preferred.)

## Sample results

|                                 Method |        Job | UnrollFactor |           Mean |        Error |        StdDev |         Median |       Gen 0 |      Gen 1 |     Gen 2 |  Allocated |
|--------------------------------------- |----------- |------------- |---------------:|-------------:|--------------:|---------------:|------------:|-----------:|----------:|-----------:|
|                          SimplifyNames | DefaultJob |           16 |    17,221.4 us |    378.14 us |   1,097.04 us |    17,164.1 us |   1875.0000 |    31.2500 |         - |  11,654 KB |
|                            UnusedOpens | DefaultJob |           16 |       852.7 us |     16.96 us |      36.87 us |       852.0 us |    120.1172 |    37.1094 |         - |     736 KB |
|                     UnusedDeclarations | DefaultJob |           16 |       208.2 us |      6.65 us |      19.09 us |       202.7 us |     71.5332 |     3.6621 |         - |     438 KB |
|              ParsingCheckExpressionsFs | Job-CXFNSP |            1 |   255,107.0 us | 39,778.24 us | 117,287.03 us |   186,340.7 us |   4000.0000 |  1000.0000 |         - |  30,082 KB |
|                              ILReading | Job-CXFNSP |            1 | 1,256,653.6 us | 24,802.85 us |  48,958.41 us | 1,249,170.3 us | 102000.0000 | 31000.0000 | 2000.0000 | 671,507 KB |
| TypeCheckFileWith100ReferencedProjects | Job-CXFNSP |            1 |     6,541.1 us |    242.62 us |     700.00 us |     6,614.2 us |           - |          - |         - |   3,547 KB |

---

### Overview (`tests/benchmarks/FCSBenchmarks/FCSSourceFiles/README.md`)

# FCSSourceFiles

## What is it

* A BDN-based benchmark
* Tests `FSharpChecker.ParseAndCheckFileInProject` on the `FSharp.Core` project.
* Uses locally available source code for both the code being type-checked (`FSharp.Core`) and the code being benchmarked (`FCS`).

## How to run it

1. Run `dotnet run -c release`
2. Output available on the command line and in `BenchmarkDotNet.Artifacts/`

## Sample results

```
BenchmarkDotNet=v0.13.1, OS=Windows 10.0.22621
11th Gen Intel Core i7-1185G7 3.00GHz, 1 CPU, 8 logical and 4 physical cores
.NET SDK=6.0.320
  [Host]     : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT DEBUG
  DefaultJob : .NET 6.0.25 (6.0.2523.51912), X64 RyuJIT
```

|                     Method |    Mean |   Error |  StdDev |        Gen 0 |       Gen 1 |     Gen 2 | Allocated |
|--------------------------- |--------:|--------:|--------:|-------------:|------------:|----------:|----------:|
| ParseAndCheckFileInProject | 22.14 s | 0.543 s | 1.522 s | 1645000.0000 | 307000.0000 | 6000.0000 |     10 GB |

---

### Overview (`tests/benchmarks/FCSBenchmarks/BenchmarkComparison/sample_results/README.md`)

# Sample results

This folder contains a selection of results obtained by running the notebook located in `../runner.ipynb`.

## Timings are not accurate

The results were gathered on a busy machine without much care taken to provide a reliable performance environment.
While this means the timing metrics are not very useful, the results can still be useful for two reasons:
* allocation data is quite accurate as it doesn't tend to depend much on the environment
* they work as examples that can make using the benchmarks easier

## Structure

Each directory contains 3 files output by `HistoricalBenchmark.Runner.runAll` function for a given selection of versions.

The three different version sets are:
- `sample_versions` - an arbitrary selection featuring all three types of versions supported
- `between_2_nuget_versions` - all commits between two NuGet versions of FCS
- `10_latest_nuget_versions` - 10 FCS NuGet versions between `v41.0.2` and ``v41.0.5-preview.22327.2`

## Observations

One thing that can be observed by looking at the results in `between_2_nuget_versions` is the noticeable increase of allocations in https://github.com/dotnet/fsharp/pull/11517.

While this isn't necessarily something worth addressing, partly because later revisions show reduced allocations, it shows how running a historical benchmark can be potentially useful.

## Notes

- The metrics gathered here are very limited - much more data can be gathered from each benchmark.
- Such historical benchmarks run locally might be mostly deprecated once CI setup exists for performance tests that will provide the necessary historical information

---

### Overview (`tests/service/data/TestProject/netstandard2.0/README.md`)

The file in this directory is a checked-in copy of the output of

    msbuild VisualFSharp.slnx
    msbuild C:\GitHub\dsyme\fsharp\tests\service\data\TestProject

Note "TestProject" is not part of VisualFSharp.slnx to prevent problems where the type provider DLL gets locked when
using VisualFSharp.slnx.

---

### Index (`docs/index.md`)

# F# compiler guide

Welcome to [the F# compiler and tools repository](https://github.com/dotnet/fsharp)! This guide discusses the F# compiler source code and implementation from a technical point of view.

## Documentation Topics

* [Overview](overview.md)
* [Coding Standards](coding-standards.md)
* [Compiler Startup Performance](compiler-startup-performance.md)
* [Debug Emit](debug-emit.md)
* [Diagnostics](diagnostics.md)
* [Notes on FSharp.Core](fsharp-core-notes.md)
* [F# Interactive Code Emit](fsi-emit.md)
* [Large inputs and stack overflows](large-inputs-and-stack-overflows.md)
* [Memory usage](memory-usage.md)
* [Optimizations](optimizations.md)
* [Equality optimizations](optimizations-equality.md)
* [Runtime async](runtime-async.md)
* [Project builds](project-builds.md)
* [Tooling features](tooling-features.md)

[Edit the source for these docs](https://github.com/dotnet/fsharp/tree/main/docs). The docs are published automatically daily [fsharp.github.io/fsharp-compiler-docs/](https://fsharp.github.io/fsharp-compiler-docs/) by [this repo](https://github.com/fsharp/fsharp-compiler-docs).

## Key Folders

* [src/Compiler/Utilities](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Utilities/) - various utilities, largely independent of the compiler

* [src/Compiler/Facilities](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Facilities/) - various items of functionality specific to the compiler

* [src/Compiler/AbstractIL](https://github.com/dotnet/fsharp/tree/main/src/Compiler/AbstractIL/) - the Abstract IL library used for .NET IL

* [src/Compiler/SyntaxTree](https://github.com/dotnet/fsharp/tree/main/src/Compiler/SyntaxTree/) - the SyntaxTree, parsing and lexing

* [src/Compiler/TypedTree](https://github.com/dotnet/fsharp/tree/main/src/Compiler/TypedTree/) - the TypedTree, and utilities associated with it

* [src/Compiler/Checking](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Checking/) - checking logic

* [src/Compiler/Optimize](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Optimize/) - optimization and "lowering" logic

* [src/Compiler/CodeGen](https://github.com/dotnet/fsharp/tree/main/src/Compiler/CodeGen/) - IL code generation logic

* [src/Compiler/Driver](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Driver/) - compiler options, diagnostics and other coordinating functionality

* [src/Compiler/Symbols](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Symbols/) - symbols in the public API to the compiler

* [src/Compiler/Service](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Service/) - the incremental compilation and build logic, plus editor services in the public API to the compiler

* [src/Compiler/Interactive](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Interactive/) - the components forming the interactive REPL and core of the notebook engine

* [src/FSharp.Core](https://github.com/dotnet/fsharp/tree/main/src/FSharp.Core/) - the core library

* [tests](https://github.com/dotnet/fsharp/tree/main/tests) - the tests

* [vsintegration](https://github.com/dotnet/fsharp/tree/main/vsintegration) - the Visual Studio integration

## Resources for learning

* Channel: [F# Software Foundation compiler sessions](https://www.youtube.com/channel/UCsi00IVEgPoK7HvcpWDeSxQ)

* Video: [Learn me some F# Compiler, an online chat with Vlad and Don](https://www.youtube.com/watch?v=-dKf15xSWPY)

* Video: [Understanding the F# Optimizer, and online chat with Vlad and Don](https://www.youtube.com/watch?v=sfAe5lDue7k)

* Video: [Lexer and Parser, an online chat with Vlad and Don](https://www.youtube.com/watch?v=3Zr0HNVcooU)

* Video: [Resumable State Machines, an online chat with Vlad and Don](https://www.youtube.com/watch?v=GYi3ZMF8Pm0)

* Video: [The Typechecker, an online chat with Vlad and Don](https://www.youtube.com/watch?v=EQ9fjOlmwws)

* Video: [FSharp.Compiler.Service, an online chat with Vlad and Don](https://www.youtube.com/watch?v=17a3i8WBQpg)

## Tools to help work with the compiler

* [sharplab.io](https://sharplab.io/) can be used to decompile code.

* [fantomas-tools](https://fsprojects.github.io/fantomas-tools/#/ast) can be used to view the Untyped Abstract Syntax Tree.

## Attribution

This document is based on an original document published in 2015 by the [F# Software Foundation](http://fsharp.org). It has since been updated substantially.

---

### Overview (`docs/overview.md`)

---
title: Overview
category: Compiler Internals
categoryindex: 200
index: 100
---

# Overview

There are several artifacts involved in the development of F#:

* FSharp.Compiler.Service ([docs](fcs/), [source](https://github.com/dotnet/fsharp/tree/main/src/Compiler)). Contains all logic for F# compilation - including parsing, syntax tree processing, typechecking, constraint solving, optimizations, IL importing, IL writing, pretty printing of F# constructs, and F# metadata format processing - and the F# compiler APIs for tooling.

* The [F# compiler executable](https://github.com/dotnet/fsharp/tree/main/src/fsc), called `fsc`, which is called as a console app. It sets the .NET GC into batch mode and then invokes `FSharp.Compiler.Service` with command-line arguments.

* The [FSharp.Core Library](https://github.com/dotnet/fsharp/tree/main/src/FSharp.Core), called `FSharp.Core`. Contains all primitive F# types and logic for how they interact, core data structures and library functions for operating on them, structured printing logic, units of measure for scientific programming, core numeric functionality, F# quotations, F# type reflection logic, and asynchronous programming types and logic.

* The [F# Interactive tool](https://github.com/dotnet/fsharp/tree/main/src/fsi), called `fsi`. A REPL for F# that supports execution and pretty-printing of F# code and results, loading F# script files, referencing assemblies, and referencing packages from NuGet.

The `FSharp.Compiler.Service` is by far the largest of these components and contains nearly all logic that `fsc` and `fsi` use. It is the primary subject of this guide.

## Key compiler data formats and representations

The following are the key data formats and internal data representations of the F# compiler code in its various configurations:

* _Input source files_  Read as Unicode text, or binary for referenced assemblies.

* _Input command-line arguments_  See [CompilerOptions.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Driver/CompilerOptions.fs) for the full code implementing the arguments table. Command-line arguments are also accepted by the F# Compiler Service API in project specifications, and as optional input to F# Interactive.

* _Tokens_, see [pars.fsy](https://github.com/dotnet/fsharp/blob/main/src/Compiler/pars.fsy), [lex.fsl](https://github.com/dotnet/fsharp/blob/main/src/Compiler/lex.fsl), [LexHelpers.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/SyntaxTree/LexHelpers.fs) and related files.

* _Abstract Syntax Tree (AST)_, see [SyntaxTree.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/SyntaxTree/SyntaxTree.fs), the untyped syntax tree resulting from parsing.

* _Typed Abstract Syntax Tree (Typed Tree)_, see [TypedTree.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/TypedTree/TypedTree.fs), [TypedTreeBasics.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/TypedTree/TypedTreeBasics.fs), [TypedTreeOps.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/TypedTree/TypedTreeOps.fs), and related files. The typed, bound syntax tree including both type/module definitions and their backing expressions, resulting from type checking and the subject of successive phases of optimization and representation change.

* _Type checking context/state_, see for example [`TcState` in ParseAndCheckInputs.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Driver/ParseAndCheckInputs.fsi) and its constituent parts, particularly `TcEnv` in [CheckExpressions.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/Expressions/CheckExpressions.fsi) and `NameResolutionEnv` in [NameResolution.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/NameResolution.fsi). A set of tables representing the available names, assemblies etc. in scope during type checking, plus associated information.

* _Abstract IL_, the output of code generation, then used for binary generation, and the input format when reading .NET assemblies, see [`ILModuleDef` in il.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/AbstractIL/il.fsi).

* _The .NET Binary format_ (with added "pickled" F# Metadata resource), the final output of fsc.exe, see the ECMA 335 specification and the [ilread.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/AbstractIL/ilread.fs) and [ilwrite.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/AbstractIL/ilwrite.fs) binary reader/generator implementations. The added F# metadata is stored in a binary resource, see [TypedTreePickle.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/TypedTree/TypedTreePickle.fs).

* _The incrementally emitted .NET reflection assembly,_ the incremental output of fsi.exe. See [ilreflect.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/AbstractIL/ilreflect.fs).

## Key constructs and APIs for F# tooling

The following are the most relevant parts of the F# compiler tooling, making up the "engine" and API surface area of `FSharp.Compiler.Service`.

* The incremental project build engine state in [IncrementalBuild.fsi](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Service/IncrementalBuild.fsi)/[IncrementalBuild.fs](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Service/IncrementalBuild.fs), a part of the F# Compiler Service API.

* The corresponding APIs wrapping and accessing these structures in the public-facing [`FSharp.Compiler.Service` API](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Service) and [Symbol API](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Symbols).

* The [F# Compiler Service Caches](https://fsharp.github.io/fsharp-compiler-docs/fcs/caches.html), the various caches maintained by an instance of an `FSharpChecker`.

## Key compiler phases

The following is a diagram of how the different phases of the F# compiler work:

```
/* Detailed source-code truncated for AI context efficiency. */
```

The following are the key phases and high-level logical operations of the F# compiler code in its various configurations:

* _Basic lexing_. Produces a token stream from input source file text. F# uses the [FsLex](http://fsprojects.github.io/FsLexYacc/) tool to process a declarative specification of the tokenizer in [lex.fsl](https://github.com/dotnet/fsharp/blob/main/src/Compiler/lex.fsl). This compiles the tokenizer specification to a number of tables which are then interpreted by the code in [prim-lexing.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Facilities/prim-lexing.fs) (see also [prim-lexing.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Facilities/prim-lexing.fsi).

* _White-space sensitive lexing_. Accepts and produces a token stream, augmenting per the [F# Language Specification](https://fsharp.org/specs/language-spec/).

* _Parsing_. Accepts a token stream and produces an AST per the grammar in the [F# Language Specification](https://fsharp.org/specs/language-spec/). F# uses the [FsYacc](http://fsprojects.github.io/FsLexYacc/) tool to process a declarative specification of the parser in [pars.fsy](https://github.com/dotnet/fsharp/blob/main/src/Compiler/pars.fsy). This compiles the grammar to a number of tables which are then interpreted by the code in [prim-parsing.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Facilities/prim-parsing.fs) (see also [prim-parsing.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Facilities/prim-parsing.fsi).

* _Resolving references_. For .NET SDK generally references are resolved explicitly by external tooling.
   There is a legacy aspect to this if references use old .NET Framework references including for
   scripting.  See [ReferenceResolver.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Facilities/ReferenceResolver.fs) for the abstract definition of compiler reference resolution. See [LegacyMSBuildReferenceResolver.fs](../src/LegacyMsBuildResolver/LegacyMSBuildReferenceResolver.fs) for reference resolution used by the .NET Framework F# compiler when running on .NET Framework. See [SimulatedMSBuildReferenceResolver.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Facilities/SimulatedMSBuildReferenceResolver.fs) when not using the .NET Framework F# compiler.
   See [DependencyManager](https://github.com/dotnet/fsharp/tree/main/src/Compiler/DependencyManager) for reference resolution and package management used in `fsi`.

* _Importing referenced .NET binaries_, see [import.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/import.fsi)/[import.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/import.fs). Accepts file references and produces a Typed Tree node for each referenced assembly, including information about its type definitions (and type forwarders if any).

* _Importing referenced F# binaries and optimization information as Typed Tree data structures_, see [TypedTreePickle.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/TypedTree/TypedTreePickle.fs). Accepts binary data and produces  Typed Tree nodes for each referenced assembly, including information about its type/module/function/member definitions.

* _Sequentially type checking files_, see [CheckDeclarations.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/CheckDeclarations.fsi)/[CheckDeclarations.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/CheckDeclarations.fs). Accepts an AST plus a type checking context/state and produces new Typed Tree nodes
  incorporated into an updated type checking state, plus additional Typed Tree Expression nodes used during code generation.  A key part of this is
  checking syntactic types and expressions, see [CheckExpressions.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/CheckDeclarations.fsi)/[CheckExpressions.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/Expressions/CheckDeclarations.fs) including the state held across the checking of a file (see `TcFileState`) and the
  environment active as we traverse declarations and expressions (see `TcEnv`).

* _Pattern match compilation_, see [PatternMatchCompilation.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/PatternMatchCompilation.fsi)/[PatternMatchCompilation.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/PatternMatchCompilation.fs). Accepts a subset of checked Typed Tree nodes representing F# pattern matching and produces Typed Tree expressions implementing the pattern matching. Called during type checking as each construct involving pattern matching is processed.

* _Constraint solving_, see [ConstraintSolver.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/ConstraintSolver.fsi)/[ConstraintSolver.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/ConstraintSolver.fs). A constraint solver state is maintained during type checking of a single file, and constraints are progressively asserted (i.e. added to this state). Fresh inference variables are generated and variables are eliminated (solved). Variables are also generalized at various language constructs, or explicitly declared, making them "rigid". Called during type checking as each construct is processed.

* _Post-inference type checks_, see [PostInferenceChecks.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/PostInferenceChecks.fsi)/[PostInferenceChecks.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/PostInferenceChecks.fs). Called at the end of type checking/inference for each file. A range of checks that can only be enforced after type checking on a file is complete, such as analysis when using `byref<'T>` or other `IsByRefLike` structs.

* _Quotation translation_, see [QuotationTranslator.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/QuotationTranslator.fsi)/[QuotationTranslator.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/QuotationTranslator.fs)/[QuotationPickler.fsi](../src/Compiler/TypedTree/QuotationPickler.fsi)/[QuotationPickler.fs](../src/Compiler/TypedTree/QuotationPickler.fs). Generates the stored information for F# quotation nodes, generated from the Typed Tree expression structures of the F# compiler. Quotations are ultimately stored as binary data plus some added type references. "ReflectedDefinition" quotations are collected and stored in a single blob.

* _Optimization phases_, primarily the "Optimize" (peephole/inlining) and "Top Level Representation" (lambda lifting) phases, see [Optimizer.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Optimize/Optimizer.fsi)/[Optimizer.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Optimize/Optimizer.fs) and [InnerLambdasToTopLevelFuncs.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Optimize/InnerLambdasToTopLevelFuncs.fsi)/[InnerLambdasToTopLevelFuncs.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Optimize/InnerLambdasToTopLevelFuncs.fs) and [LowerCalls.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Optimize/LowerCalls.fs). Each of these takes Typed Tree nodes for types and expressions and either modifies the nodes in place or produces new Typed Tree nodes. These phases are orchestrated in [CompilerOptions.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Driver/CompilerOptions.fs)

* _Code generation_, see [IlxGen.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/CodeGen/IlxGen.fsi)/[IlxGen.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/CodeGen/IlxGen.fs). Accepts Typed Tree nodes and produces Abstract IL nodes, sometimes applying optimizations.

* _Abstract IL code rewriting_, see [EraseClosures.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/CodeGen/EraseClosures.fs) and
  [EraseUnions.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/CodeGen/EraseUnions.fs). Eliminates some constructs by rewriting Abstract IL nodes.

* _Binary emit_, see [ilwrite.fsi](https://github.com/dotnet/fsharp/blob/main/src/Compiler/AbstractIL/ilwrite.fsi)/[ilwrite.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/AbstractIL/ilwrite.fs).

* _Reflection-Emit_, see [ilreflect.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/AbstractIL/ilreflect.fs).

These and transformations used to build the following:

* _The F# Compiler Service API_, see the [Symbol API](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Symbols) and [Service API](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Service)

* _The F# Interactive Shell_, see [fsi.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Interactive/fsi.fs).

* _The F# Compiler Shell_, see [fsc.fs](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Driver/fsc.fs) and [fscmain.fs](../src/fsc/fscmain.fs).

## Bootstrapping

The F# compiler is bootstrapped. That is, an existing F# compiler is used to build a "proto" compiler from the current source code. That "proto" compiler is then used to compile itself, producing a "final" compiler. This ensures the final compiler is compiled with all relevant optimizations and fixes.

## FSharp.Build

`FSharp.Build.dll` and `Microsoft.FSharp.targets` give MSBuild support for F# projects (`.fsproj`) and contain the targets. Although not strictly part of the F# compiler, they are essential for using F# in all contexts for .NET, aside from some more targeted scripting scenarios. The targets expose things like the `CoreCompile` and `Fsc` tasks called by MSBuild.

---

### Fcs Index (`docs/fcs/index.md`)

---
title: FSharp.Compiler.Service
category: FSharp.Compiler.Service
categoryindex: 300
index: 100
---
# FSharp.Compiler.Service

The [FSharp.Compiler.Service](https://www.nuget.org/packages/FSharp.Compiler.Service) package is a component derived from the F# compiler source code that
exposes additional functionality for implementing F# language bindings, additional
tools based on the compiler or refactoring tools. The package also includes
dynamic execution of F# code that can be used for embedding F# scripting into your applications.

## Available services

The project currently exposes the following services that are tested & documented on this page.
The libraries contain additional public API that can be used, but is not documented here.

* [**F# Language tokenizer**](tokenizer.html) - turns any F# source code into a stream of tokens.
  Useful for implementing source code colorization and basic tools. Correctly handle nested
  comments, strings etc.

* [**Processing SyntaxTree**](untypedtree.html) - allows accessing the syntax tree.
  This represents parsed F# syntax without type information and can be used to implement code formatting
  and various simple processing tasks.

* [**Working with resolved symbols**](symbols.html) - many services related to type checking
  return resolved symbols, representing inferred types, and the signatures of whole assemblies.

* [**Working with resolved expressions**](typedtree.html) - services related to working with
  type-checked expressions and declarations, where names have been resolved to symbols.

* [**Using editor services**](editor.html) - expose functionality for auto-completion, tool-tips,
  parameter information etc. These functions are useful for implementing F# support for editors
  and for getting some type information for F# code.

* [**Working with project-wide analysis**](project.html) - you can request a check of
  an entire project, and ask for the results of whole-project analyses such as find-all-references.

* [**Hosting F# interactive**](interactive.html) - allows calling F# interactive as a .NET library
  from your .NET code. You can use this API to embed F# as a scripting language in your projects.

* [**Hosting the F# compiler**](compiler.html) - allows you to embed calls to the F# compiler.

* [**File system API**](filesystem.html) - the `FSharp.Compiler.Service` component has a global variable
  representing the file system. By setting this variable you can host the compiler in situations where a file system
  is not available.

> **NOTE:** The FSharp.Compiler.Service API is subject to change when later versions of the nuget package are published

## The Public Surface Area

We are in the process of cleaning up the surface area of FCS to allow it to be fully binary compatible going forward.

The full current surface area can be seen at: https://fsharp.github.io/fsharp-compiler-docs/reference/index.html

The API is generally designed with F#/.NET design conventions (e.g. types in namespaces, not modules, no nesting of modules etc.) and we must continue to iterate to make this so.

The parts of the compiler under `FSharp.Compiler.AbstractIL.*` are "incidental" and not really designed for public use apart from the hook for JetBrains Rider (Aside: In theory all these other parts could be renamed to FSharp.Compiler though there's no need to do that right now).   These internal parts tend to be implemented with the "module containing lots of stuff in one big file" approach for layers of the compiler.

### Basics - Syntax, Text and Diagnostics

* [FSharp.Compiler.Diagnostics](reference/fsharp-compiler-diagnostics.html)
* [FSharp.Compiler.IO](reference/fsharp-compiler-io.html)
* [FSharp.Compiler.Syntax](reference/fsharp-compiler-syntax.html)
* [FSharp.Compiler.Text](reference/fsharp-compiler-text.html)

### Tokenization

* [FSharp.Compiler.Tokenization](reference/fsharp-compiler-tokenization.html)

### Symbols and Code Analysis

* [FSharp.Compiler.Symbols](reference/fsharp-compiler-symbols.html)
* [FSharp.Compiler.CodeAnalysis](reference/fsharp-compiler-codeanalysis.html)

### Editor Services

* [FSharp.Compiler.EditorServices](reference/fsharp-compiler-editorservices.html)

### Interactive Execution

* [FSharp.Compiler.Interactive.Shell](reference/fsharp-compiler-interactive-shell.html)

### Internal extension points

* [FSharp.Compiler.AbstractIL](reference/fsharp-compiler-abstractil.html)

## Projects using the F# Compiler Services

Some of the projects using the F# Compiler Services are:

* [**F# in Visual Studio**](https://github.com/dotnet/fsharp/)
* [**F# in Visual Studio for Mac**](https://github.com/mono/monodevelop/tree/master/main/external/fsharpbinding)
* [**FsAutoComplete**](https://github.com/fsharp/FsAutoComplete)
* [**F# in JetBrains Rider**](https://github.com/JetBrains/resharper-fsharp)
* [**F# in .NET Interactive Notebooks**](https://github.com/dotnet/interactive)
* [**Fantomas**](https://github.com/fsprojects/fantomas/) - Source code formatting for F#
* [**FSharpLint**](https://fsprojects.github.io/FSharpLint/) - Lint tool for F#
* [**Fable**](https://fable.io/) - F# to JavaScript Compiler and more
* [**WebSharper**](https://websharper.com/) - F# full-stack web framework

Older things:

* [**FsReveal**](https://fsprojects.github.io/FsReveal/) - FsReveal parses markdown and F# script file and generate reveal.js slides
* [**Elucidate**](https://github.com/rookboom/Elucidate) - Visual Studio extension for rich inlined comments using MarkDown
* [**FSharp.Formatting**](http://fsprojects.github.io/FSharp.Formatting/) - F# tools for generating documentation (Markdown processor and F# code formatter)
* [**FAKE**](https://fsprojects.github.io/FAKE/) - "FAKE - F# Make" is a cross platform build automation system

## Contributing and copyright

The F# source code is copyright by Microsoft Corporation and contributors.

---

### Changing The Ast (`docs/changing-the-ast.md`)

---
title: Changing the AST
category: Compiler Internals
categoryindex: 200
index: 800
---
# Changing the AST

Making changes to the AST is a common task when working on new F# compiler features or when working on developer tooling.  
This document describes the process of making changes to the AST.

The easiest way to modify the AST is to start with the type definitions in `SyntaxTree.fsi` and `SyntaxTree.fs` and then let the compiler guide you to the places where you need to make changes.
Let's look at an example: We want to extend the AST to include the range of the `/` symbol in a `SynRationalConst.Rational`.  

There are two solutions to choose from:
- Add a new field to the `Rational` union case
- Add a dedicated trivia type to the union case which contains the new range and maybe move the existing ranges to the trivia type as well  

The pros of introducing a dedicated trivia type are:
- Having the additional information in a separate structure allows it to grow more easily over time. Adding new information to an existing trivia type won't disrupt most FCS consumers.  
- It is clear that it is information that is not relevant for the compilation.  

The cons are: 
- It can be a bit excessive to introduce for a single field.
- The existing AST node might already contain fields that are historically more suited for trivia, but predate the SyntaxTrivia module.

In this example, we'll go with the first solution and add a new field named `divRange` to the `Rational` union case as it felt a bit excessive to introduce a new trivia type for a single field.  
But these are the type of decisions that need to be made when changing the AST.

```fsharp
type SynRationalConst =

    // ...

    | Rational of
        numerator: int32 *
        numeratorRange: range *
        divRange: range *   // our new field
        denominator: int32 *
        denominatorRange: range *
        range: range
    
    // ...
```	

After modifying `SyntaxTree.fsi` and `SyntaxTree.fs`, the compiler will report errors in `pars.fsy`. If not, the `fsy` file wasn't processed by the compilation. In this case, a rebuild of `FSharp.Compiler.Service.fsproj` should help.  
`pars.fsy` is the parser specification of F#, a list of rules that describe how to parse F# code. Don't be scared by the size of the file or the unfamiliar content.
It's easier than it looks.
The F# compiler uses a parser generator called [fsyacc](https://github.com/fsprojects/FsLexYacc) to generate the parser from the specification in `pars.fsy`.
Let's look at the most relevant syntax parts of a `.fsy` file:

```fsharp
rationalConstant:
  | INT32 INFIX_STAR_DIV_MOD_OP INT32
    { if $2 <> "/" then reportParseErrorAt (rhs parseState 2) (FSComp.SR.parsUnexpectedOperatorForUnitOfMeasure())
      if fst $3 = 0 then reportParseErrorAt (rhs parseState 3) (FSComp.SR.parsIllegalDenominatorForMeasureExponent())
      if (snd $1) || (snd $3) then errorR(Error(FSComp.SR.lexOutsideThirtyTwoBitSigned(), lhs parseState))
      SynRationalConst.Rational(fst $1, rhs parseState 1, fst $3, rhs parseState 3, lhs parseState) }
  | // ...
```

The first line is the name of the rule, `rationalConstant` in this case. It's a so called non-terminal symbol in contrast to a terminal symbol like `INT32` or `INFIX_STAR_DIV_MOD_OP`. The individual cases of the rule are separated by `|`, they are called productions.

By now, you should be able to see the similarities between an fsyacc rule and the pattern matching you know from F#.  
The code between the curly braces is the code that gets executed when the rule is matched and is _real_ F# code. After compilation, it ends up in 
`.\artifacts\obj\FSharp.Compiler.Service\Debug\netstandard2.0\pars.fs`, generated by fsyacc.

The first three lines do error checking and report errors if the input is invalid.
Then the code calls the `Rational` constructor of `SynRationalConst` and passes some values to it. Here we need to make changes to adjust the parser to our modified type definition.  
The values or symbols that matched the rule are available as `$1`, `$2`, `$3` etc. in the code. As you can see, `$1` is a tuple, consisting of the parsed number and a boolean indicating whether the number is a valid 32 bit signed integer or not.
The code is executed in the context of the parser, so you can use the `parseState` variable, an instance of `IParseState`, to access the current state of the parser. There are helper functions defined in `ParseHelpers.fs` that make it easier to work with it.  
`rhs parseState 1` returns the range of the first symbol that matched the rule, here `INT32`. So, it returns the range of `23` in `23/42`.  
`rhs` is short for _right hand side_.  
Another helper is `rhs2`. Using it like `rhs2 parseState 2 3` for example, returns the range covering the symbols from the second to the third symbol that matched the rule. Given `23/42`, it would return the range of `/42`.  
 `lhs parseState` returns the range of the whole rule, `23/42` in our example.
 When parser recovery is of concern for a rule, it's preferred to use `rhs2` over `lhs`.
 
 Circling back to our original example of adding a new field to `SynRationalConst`, we need to add a new parameter to the call of the `Rational` constructor. We want to pass the range of the `/` symbol, so we need to add `rhs parseState 2` as the third parameter to the constructor call:  
 
 ```fsharp
SynRationalConst.Rational(fst $1, rhs parseState 1, rhs parseState 2, fst $3, rhs parseState 3, lhs parseState)
```	

That's it. Adjusting the other constructor calls of `Rational` in `pars.fsy` should be enough to have a working parser again which returns the modified AST.  
While fixing the remaining compiler errors outside of `pars.fsy`, it's a good idea to use named access to the fields of the `SynRationalConst.Rational` union case instead of positional access. This way, the compilation won't fail if additional fields are added to the union case in the future.  
After a successful compilation, you can run the parser tests in `SyntaxTreeTests.fs` to verify that everything works as expected.  
It's likely that you'll need to update the baseline files as described in `SyntaxTreeTests.fs`.

---

### Coding Standards (`docs/coding-standards.md`)

---
title: Coding standards
category: Compiler Internals
categoryindex: 200
index: 200
---
# Coding standards and idioms

The F# compiler code base is slowly being updated to better coding standards. There is a long way to go.

The future work includes

* [ ] Consistent use of fantomas formatting across as much of the codebase as feasible
* [ ] Consistent naming conventions
* [ ] Reduction in line length
* [ ] Reduction in single-character identifiers
* [ ] XML documentation for all types, members and cross-module functions

## Abbreviations

The compiler codebase uses various abbreviations. Here are some of the most common ones.

| Abbreviation        | Meaning                                                                                                                                                                                                                                         |  
|:--------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `ad`                | Accessor domain, meaning the permissions the accessing code has to access other constructs                                                                                                                                                      |
| `amap`              | Assembly map, saying how to map IL references to F# CCUs                                                                                                                                                                                        |
| `arg`               | Argument (parameter)                                                                                                                                                                                                                            |
| `argty`             | Argument (parameter) type                                                                                                                                                                                                                       |
| `arginfo`           | Argument (parameter) metadata                                                                                                                                                                                                                   |
| `boxity`            | IL boxity (`ILBoxity` = `AsObject` \| `AsValue`), the IL-level marker distinguishing the reference (boxed) and value-type forms of a type                                                                                                       |
| `ccont`             | Cancellation continuation in the Async CPS model: `OperationCanceledException -> AsyncReturn`, invoked when an async computation is cancelled                                                                                                   |
| `ccu`               | Reference to a compilation unit (`CcuThunk`): any referenced assembly (F# or otherwise; `CcuData.IsFSharp` distinguishes), possibly the assembly currently being compiled                                                                       |
| `celem`             | Custom attribute element                                                                                                                                                                                                                        |
| `cenv`              | Compilation environment. Means different things in different contexts, but usually a parameter for a single compilation state object being passed through a set of related functions in a single phase. The compilation state is often mutable. |
| `cgbuf`             | Code-generation buffer (`CodeGenBuffer`): the mutable buffer accumulating IL instructions, locals, exception clauses and sequence points for one method body in IlxGen                                                                          |
| `cloc`              | Compile location (`CompileLocation`): IL scope + top-level impl name + namespace + enclosing-type path (+ source-file qualified name and range) used to place a generated IL entity                                                             |
| `cloinfo`           | IL closure information (`IlxClosureInfo`): the IlxGen descriptor for an emitted F# closure (free variables, formal signature, IL type)                                                                                                          |
| `cont`              | Continuation. In FSharp.Core async, the success continuation `cont<'T> = 'T -> AsyncReturn` (`async.fs`). In compiler lowering passes (`LowerCalls`, `LowerComputedCollections`, `LowerStateMachines`), the conventional name for a generic CPS-style callback (typically `Expr -> Expr` / `Expr -> Expr option`) |
| `cpath`             | Compilation path (`CompilationPath` = `CompPath of ILScopeRef * SyntaxAccess * (string * ModuleOrNamespaceKind) list`): the IL scope + access + enclosing module/namespace path (the A.B.C container) for a type or module definition           |
| `css`               | Constraint solver state.                                                                                                                                                                                                                        |
| `ctok`              | Compilation thread token (`CompilationThreadToken`): a phantom token witnessing that code runs on the single compilation thread; gates access to the (partially mutable) TAST/`TcImports` data, type-provider resolution and `SourceCodeServices`/`IncrementalBuild` caches |
| `ctxt`              | Context (spelled `ctxt` rather than the more common `ctx`): in `ilread.fs` this is an `ILMetadataReader` threaded through the `seekRead*` helpers when parsing .NET metadata; elsewhere it can be a different context type (e.g. `ExecutionContext` in `IlxGen.LookupGeneratedValue`) |
| `csenv`             | Constraint solver environment (`ConstraintSolverEnv`): the per-call solver context (range, equivalence env, display env, suspicion/trace state). Threaded through every `Solve*`/`Add*` helper in `ConstraintSolver.fs`; distinct from the mutable `css` (`ConstraintSolverState`) |
| `cuspec`            | Compiled-union spec (`IlxUnionSpec`): the IL-level descriptor of a discriminated union (type ref, generic args, cases) used by EraseUnions                                                                                                      |
| `denv`              | Display Environment. Parameters guiding the formatting of types                                                                                                                                                                                 |
| `dtree`             | Decision tree (`DecisionTree`, with cases `TDSwitch`/`TDSuccess`/`TDBind`): the compiled pattern-match tree, carried as the `decision` field of `Expr.Match`                                                                                    |
| `einfo`             | An info object for an event  (whether a .NET event, an F# event or a provided event)                                                                                                                                                            |
| `e`                 | Expression                                                                                                                                                                                                                                      |
| `econt`             | Exception continuation in the Async CPS model: `ExceptionDispatchInfo -> AsyncReturn`, invoked when an async computation faults                                                                                                                 |
| `eenv`              | IlxGen emit environment (`IlxGenEnv`): the threaded code-generation context carrying `ValStorage` mappings (`valsInScope`), witnesses-in-scope, type-parameter representation env (`tyenv`), `CompileLocation` (`cloc`), live-locals set and IL-emit flags during IL emission |
| `emenv`             | Reflection.Emit / dynamic-assembly emit env (`ILDynamicAssemblyEmitEnv`, in code spelled `emEnv`): maps compiler IL refs to live `System.Reflection.Emit` types/methods/fields (used by FSI dynamic codegen)                                    |
| `env`               | Environment. Means different things in different contexts, but usually immutable state being passed and adjusted  through a set of related functions in a single phase.                                                                         |
| `eref`              | Entity reference (`EntityRef`): a reference to a TypedTree `Entity` (Tycon/Module). Common bindings: `let eref = ERefNonLocalPreResolved โ€ฆ` (IlxGen), `(eref: EntityRef)` parameters in TypedTree helpers, the `Parent eref` case of `ParentRef` |
| `fdef`              | IL field definition (`ILFieldDef`); also commonly spelled `fd` in AbstractIL helpers โ€” these are synonyms                                                                                                                                       |
| `finfo`             | An info object for a field (whether a .NET field or a provided field)                                                                                                                                                                           |
| `fref`              | A reference to an ILFieldRef Abstract IL node for a field reference. Would normally be modernized to `ilFieldRef`                                                                                                                               |
| `fspec`             | Field spec โ€” usually an `ILFieldSpec` in IL emit/read code, also a `RecdField` (record/class field declaration) in TypedTree code paths. **These are distinct types, not synonyms** โ€” the name collision is historical                          |
| `fvs`               | Free variables (`FreeVars`): the structured record of variables/typars free in an expression (computed by `freeInExpr`/`accFreevars*`), used by DetupleArgs, closure conversion and `InnerLambdasToTopLevelFuncs` |
| `g`                 | The TcGlobals value                                                                                                                                                                                                                             |
| `hier`              | Visual Studio hierarchy (`IVsHierarchy`): the VS shell projection of a project tree (project nodes + items)                                                                                                                                     |
| `id`                | Identifier                                                                                                                                                                                                                                      |
| `ilg`               | IL globals (`ILGlobals`): cached well-known IL types/primitives (`typ_Object`, `typ_Int32`, โ€ฆ); the AbsIL counterpart of `TcGlobals`, usually reached as `g.ilg`                                                                                |
| `itemid`            | Visual Studio item id (`VSITEMID`, a `uint32`) identifying a node within an `IVsHierarchy`, with sentinels `VSITEMID_ROOT`/`_NIL`/`_SELECTION`                                                                                                  |
| `lid`               | Long Identifier (`LongIdent` = `Ident list`); SyntaxTree DU fields conventionally use the full-form synonym `longId`                                                                                                                            |
| `m`                 | A source code `range` (file-and-position span). Not to be confused with `Mark`, the IL code-label type in `IlxGen`                                                                                                                              |
| `mdef`              | Usually an IL method definition (`ILMethodDef`) being emitted, also frequently spelled `md` โ€” these are synonyms. In some IlxGen module walks `mdef` is also a module body (`ModuleOrNamespaceContents`/`ILModuleDef`) โ€” distinct types         |
| `mgbuf`             | Module/assembly generation buffer (the IlxGen `AssemblyBuilder`): collects type defs, method defs, anonymous-record types and resources for the whole assembly being emitted                                                                    |
| `mimpl`             | In IL emit/morph code, an `ILMethodImplDef` (an explicit method-implementation override, often for an interface). In the Symbols layer (`Symbols/Exprs.fs`), the parameter name for a `CheckedImplFile` (a typed implementation file). **These are distinct types, not synonyms** |
| `minfo`             | An info object for a method (whether a .NET method, an F# method or a provided method)                                                                                                                                                          |
| `minst`             | Method instantiation โ€” in TypedTree code a `TypeInst` (`TType list`) of method type arguments (the per-method analogue of `tinst`); in AbstractIL method specs an `ILGenericArgs` |
| `mk`                | Means make in old fashioned F#/OCaml coding style                                                                                                                                                                                               |
| `modref`            | Module or namespace reference โ€” usually `ModuleOrNamespaceRef` (an alias for `EntityRef`, the parallel of `tcref`/`vref`); in AbstractIL `ILScopeRef.Module` patterns it is an `ILModuleRef` |
| `modul`             | In TypedTree contexts a `ModuleOrNamespace` (the typed structure for a namespace or F# module). In AbstractIL contexts (`ilread`/`ilwrite`/`ilprint`/`ilmorph`/`ilreflect`), the same name is used for an `ILModuleDef` (the IL/CLI module being read or emitted; also spelled `ilModule`). **These are distinct types, not synonyms** |
| `mref`              | IL method reference (`ILMethodRef`): the IL-level identity of an uninstantiated method (declaring type ref, calling convention, name, generic arity, argument types, return type)                                                               |
| `mspec`             | Usually an `ILMethodSpec` (IL-level descriptor of an instantiated method) in IL emit code; also `ModuleOrNamespace` in TypedTree module-binding code (`ModuleOrNamespaceBinding.Module(mspec, โ€ฆ)`) and related IlxGen module-location helpers. **These are distinct types, not synonyms** |
| `mty`               | Module type (`ModuleOrNamespaceType`, also spelled `mtyp` โ€” these are synonyms): the signature/contents (sub-modules, types, vals) of a module or namespace โ€” used both for the type-checked contents of an implementation and for the declared contents of a signature |
| `ncenv`             | Name resolver (`NameResolver`): the `cenv`-style state object (globals/amap/infoReader) threaded through name resolution                                                                                                                        |
| `nenv`              | Name resolution environment (`NameResolutionEnv`): the in-scope name โ†’ `Item` lookup tables active at a program point                                                                                                                          |
| `nleref`            | Non-local entity reference (`NonLocalEntityRef of CcuThunk * string[]`): an in-memory path-indexed reference into the namespace/module structure of a CCU (often another assembly, but possibly the CCU being compiled)                         |
| `nlr`               | Non-local reference โ€” usually `NonLocalEntityRef` (as the `nlr` field of `EntityRef`), occasionally `NonLocalValOrMemberRef` (the `nlr` field of `ValRef`). **Distinct types, not synonyms** โ€” different reference layers                       |
| `pat`               | Pattern, a syntactic AST node representing part of a pattern in a pattern match                                                                                                                                                                 |
| `pc`                | Program counter โ€” the `int` state label allocated per resumption point in `LowerSequences`/`LowerStateMachines`                                                                                                                                 |
| `penv`              | Pass environment โ€” a pass-local `cenv`-style record threaded through an Optimizer pass (e.g. `penv` in `DetupleArgs.fs`, `RewriteContext` in `InnerLambdasToTopLevelFuncs.fs`)                                                                  |
| `pinfo`             | An info object for a property  (whether a .NET property, an F# property or a provided property)                                                                                                                                                 |
| `plid`              | Partial long identifier (a `string list`): the dotted prefix typed so far during IDE completion (the "Foo.Bar." path)                                                                                                                           |
| `q`                 | In FSharp.Core (`Linq.fs`, `Query.fs`, query/quotation transforms), an F# quotation value (`Quotations.Expr` or `Quotations.Expr<'T>`) being inspected or transformed; occasionally a parser/matcher function in quotation helpers              |
| `rdt`               | Running Document Table (`IVsRunningDocumentTable`): the Visual Studio service that tracks all open documents and their buffers                                                                                                                  |
| `reqdty`            | Required type (`reqdTy`, a `TType`): the expected/target type flowed into expression type checking                                                                                                                                              |
| `rfinfo`            | Record/class field info (`RecdFieldInfo`): an `rfref` together with the enclosing type's instantiation (i.e. a field viewed through a concrete type)                                                                                            |
| `rfref`             | Record or class field  reference, a reference to a Typed Tree node for a record or class field                                                                                                                                                  |
| `scoref`            | Scope reference (`ILScopeRef`) for an IL metadata reference: `Local`, `Module` of `ILModuleRef`, `Assembly` of `ILAssemblyRef`, or `PrimaryAssembly`                                                                                            |
| `sm`                | Resumable state machine (a `byref<ResumableStateMachine<_>>`) passed through CE builder methods, tasks and resumable code; also `byref<TaskStateMachine<_>>` in task-specific paths                                                             |
| `sp`                | Sequence points or debug points                                                                                                                                                                                                                 |
| `spat`              | Simple Pattern, a syntactic AST node representing part of a pattern in a pattern match                                                                                                                                                          |
| `tau`               | A type with the "forall" nodes stripped off (i.e. the nodes which represent generic type parameters). Comes from the notation _๐›•_ used in type theory                                                                                          |
| `tcaug`             | Type constructor augmentation (`TyconAugmentation`, accessed via `tcref.TypeContents`): the mutable bag of interfaces, members and compiler-synthesized overrides (Compare/Equals/Hash) added to a `Tycon`                                      |
| `tcref`             | Type constructor reference (`TyconRef`, an alias for `EntityRef`; long-form synonym `tyconRef` is also used): the parallel of `modref`/`vref`/`eref` for class/record/union/exception/abbreviation/measure type constructors                    |
| `tdef`              | IL type definition (`ILTypeDef`); also commonly spelled `td` in AbstractIL helpers โ€” these are synonyms                                                                                                                                         |
| `tinst`             | Type instantiation                                                                                                                                                                                                                              |
| `tpenv`             | Type parameter environment, tracks the type parameters in scope during type checking                                                                                                                                                            |
| `tps`               | Type parameter list (`Typars` = `Typar list`; long-form synonym `typars` is also used): the typars paired with `tau` in `TType_forall(tps, tau)` / `Expr.TyChoose(tps, โ€ฆ)`                                                                      |
| `tpsorig`           | Original typars (spelled `tpsorig` in code, a `Typar list`): the user-written typars before freshening/renaming, paired with the copied set (`tps`)                                                                                             |
| `traitinfo`         | Trait constraint info (`TraitConstraintInfo`, spelled `traitInfo`): the SRTP "method-like" constraint (support types, name, argument types, return type)                                                                                        |
| `tref`              | IL type reference (`ILTypeRef`): `Scope` (`ILScopeRef`) + `Enclosing` (string list of enclosing type names) + `Name`. For non-nested types the namespace is encoded in `Name`; nested types use `Enclosing`                                     |
| `ty` (not: `typ`)   | Type, usually a Typed Tree type                                                                                                                                                                                                                 |
| `tycon`             | Type constructor (`Tycon`, an alias for `Entity`): a type or exception definition (class/record/union/abbreviation/exception/measure) in the TypedTree; `tcref.Deref` returns one. Modules and namespaces use the parallel alias `ModuleOrNamespace` |
| `typar`             | Type Parameter                                                                                                                                                                                                                                  |
| `tys` (not: `typs`) | List of types, usually Typed Tree types                                                                                                                                                                                                         |
| `tyvar`             | Type Variable, usually referring to an IL type variable, the compiled form of an F# type parameter                                                                                                                                              |
| `ucref`             | Union case reference, a reference to a Typed Tree node for a union case                                                                                                                                                                         |
| `vinfo`             | Value info โ€” in the Optimizer, the abstract value of an expression: `ExprValueInfo` (the DU with cases `UnknownValue`/`SizeValue`/`ValValue`/`TupleValue`/`RecdValue`/`UnionCaseValue`/`ConstValue`/`CurriedLambdaValue`/โ€ฆ) or the per-binding wrapper `ValInfo` (`ValMakesNoCriticalTailcalls` + `ValExprInfo`). **Distinct types, not synonyms** โ€” `ValInfo` wraps an `ExprValueInfo` |
| `vref`              | Value reference (`ValRef`; long-form synonym `valRef` is also used): a reference to a Typed Tree node for a value                                                                                                                               |
| `vspec`             | Val "spec" โ€” a `Val` (the underlying definition behind a `vref`, typically obtained via `vref.Deref`). Often shortened to just `v` in tight scopes                                                                                              |

| Phase Abbreviation             |   Meaning  |  
|:------------------------------|:-----------|
| `Syn`                  | Abstract Syntax Tree |
| `Tc`                  | Type-checker |
| `IL`                 | Abstract  IL = F# representation of .NET IL |
| `Ilx`                 | Extended Abstract IL = .NET IL plus a couple of constructs that get erased |

---

### Compiler Startup Performance (`docs/compiler-startup-performance.md`)

---
title: Startup Performance
category: Compiler Internals
categoryindex: 200
index: 700
---
# Compiler Startup Performance

Compiler startup performance is a key factor affecting happiness of F# users. If the compiler took 10sec to start up, then far fewer people would use F#.

On all platforms, the following factors affect startup performance:

* Time to load compiler binaries. This depends on the size of the generated binaries, whether they are pre-compiled (for example, using NGEN or CrossGen), and the way the .NET implementation loads them.

* Time to open referenced assemblies (for example, `mscorlib.dll`, `FSharp.Core.dll`) and analyze them for the types and namespaces defined. This depends particularly on whether this is correctly done in an on-demand way.

* Time to process "open" declarations at the top of each file. Processing these declarations have been observed to take time in some cases of  F# compilation.

* Factors specific to the specific files being compiled.

On Windows, the compiler delivered with Visual Studio currently uses NGEN to pre-compile `fsc`, `fsi`, and some assemblies used in Visual Studio tooling. For .NET Core, the CrossGen tool is used to accomplish the same thing. Visual Studio will use _delayed_ NGEN, meaning that it does not run NGEN on every binary up front. This means that F# compilation through Visual Studio may be slow for a few times before it gets NGENed.

---

### Debug Emit (`docs/debug-emit.md`)

---
title: Debug emit
category: Compiler Internals
categoryindex: 200
index: 350
---
# Debug emit

The F# compiler code base emits debug information and attributes. This article documents what we do, how it is implemented and the problem areas in our implementation.

There are mistakes and missing pieces to our debug information. Small improvements can make a major difference. Please help us fix mistakes and get things right.

The file `tests\walkthroughs\DebugStepping\TheBigFileOfDebugStepping.fsx` is crucial for testing the stepping experience for a range of constructs.

## User experiences

Debugging information affects numerous user experiences:

* **Call stacks** during debugging
* **Breakpoint placement** before and during debugging
* **Locals** during debugging
* **Just my code** debugging (which limits the view of debug code to exclude libraries)
* **Exception** debugging (e.g. "first chance" debugging when exceptions occur)
* **Stepping** debugging
* **Watch** window
* **Profiling** results
* **Code coverage** results

Some experiences are un-implemented by F# including:

* **Autos** during debugging
* **Edit and Continue**
* **Hot reload**

## Emitted information

Emitted debug information includes:

* The names of methods in .NET IL
* The PDB file/information (embedded or in PDB file) which contains
  * Debug "sequence" points for IL code
  * Names of locals and the IL code scopes over which those names are active
* The attributes on IL methods such as `CompilerGeneratedAttribute` and `DebuggerNonUserCodeAttribute`, see below
* We add some codegen to give better debug experiences, see below.

We almost always now emit the [Portable PDB](https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md) format.

## Design-time services

IDE tooling performs queries into the F# language service, notably:

* `ValidateBreakpointLocation` is called to validate every breakpoint before debugging is launched. This operates on syntax trees. See notes below.

## Debugging and optimization

Nearly all optimizations are **off** when debug code is being generated.

* The optimizer is run for forced inlining only
* List and array expressions do generate collector code
* State machines are generated for tasks and sequences
* "let mutable" --> "ref" promotion happens for captured local mutables
* Tailcalls are off by default and not emitted in IlxGen.

Otherwise, what comes out of the type checker is pretty much what goes into IlxGen.fs.

## Debug points

### Terminology

We use the terms "sequence point" and "debug point" interchangeably. The word "sequence" has too many meanings in the F# compiler so in the actual code you'll see "DebugPoint" more often, though for abbreviations you may see `spFoo` or `mFoo`.

### How breakpoints work (high level)

Breakpoints have two existences which must give matching behavior:

* At design-time, before debugging is launched, `ValidateBreakpointLocation` is called to validate every breakpoint.  This operates on the SyntaxTree and forms a kind of "gold-standard" about the exact places where break points are valid.

* At run-time, breakpoints are "mapped" by the .NET runtime to actual sequence points found in the PDB data for .NET methods. The runtime searches all methods with debug points for the relevant document and determines where to "bind" the actual breakpoint to.  A typical debugger can bind a breakpoint to multiple locations.

This means there is an invariant that `ValidateBreakpointLocation` and the emitted IL debug points correspond.

> NOTE: The IL code can and does contain extra debug points that don't pass ValidateBreakpointLocation. It won't be possible to set a breakpoint for these, but they will appear in stepping.

### Intended debug points based on syntax

The intended debug points for constructs are determined by syntax as follows.  Processing depends on whether a construct is being processed as "control-flow" or not. This means at least one debug point will be placed, either over the whole expression or some of its parts.

* The bodies of functions, methods, lambdas and initialization code for top-level-bindings are all processed as control flow

* Each Upper-Cased EXPR below is processed as control-flow (the bodies of loops, conditionals etc.)

* Leaf expressions are the other composite expressions like applications that are not covered by the other constructs.

* The sub-expressions of leaf expressions are not processed as control-flow.

|  Construct   | Debug points     |
|:-----------|:----------------|
| `let x = leaf-expr in BODY-EXPR`  |  Debug point over `let x = leaf-expr`.  |
| `let x = NON-LEAF-EXPR in BODY-EXPR`  |   |
| `let f x = BODY-EXPR in BODY-EXPR`    |    |
| `let rec f x = BODY-EXPR and g x = BODY-EXPR in BODY-EXPR`   |  |
| `if guard-expr then THEN-EXPR`   | Debug point over `if guard-expr then` |
| `if guard-expr then THEN-EXPR else ELSE-EXPR`    | Debug point over `if .. then` |
| `match .. with ...`   | Debug point over `match .. with`  |
| `... -> TARGET-EXPR`   |   |
| `... when WHEN-EXPR -> TARGET-EXPR`   |   |
| `while .. do BODY-EXPR`   | Debug point over `while .. do` |
| `for .. in collection-expr do BODY-EXPR`  | Debug points over `for`, `in` and `collection-expr`   |
| `try TRY-EXPR with .. -> HANDLER-EXPR`  | Debug points over `try` and `with` |
| `try TRY-EXPR finally .. -> FINALLY-EXPR`   | Debug points `try` and `finally` |
| `use x = leaf-expr in BODY-EXPR`  |  Debug point over `use x = leaf-expr`.  |
| `use x = NON-LEAF-EXPR in BODY-EXPR`  |   |
| `EXPR; EXPR` | |
| `(fun .. -> BODY-EXPR)` | Not a leaf, do not produce a debug point on outer expression, but include them on BODY-EXPR |
| `{ new C(args) with member ... = BODY-EXPR }` | |
| Pipe `EXPR1 &amp;&amp; EXPR2` | |
| Pipe `EXPR1 &#124;&#124; EXPR2` | |
| Pipe `EXPR1 &#124;> EXPR2` |  |
| Pipe `(EXPR1, EXPR2) &#124;&#124;> EXPR3` |  |
| Pipe `(EXPR1, EXPR2, EXPR3) &#124;&#124;&#124;> EXPR4` | |
| `yield leaf-expr` | Debug point over 'yield expr' |
| `yield! leaf-expr` | Debug point over 'yield! expr' |
| `return leaf-expr` | Debug point over 'return expr' |
| `return! leaf-expr` | Debug point over 'return! expr' |
| `[ BODY ]` | See notes below. If a computed list expression with yields (explicit or implicit) then process as control-flow. Otherwise treat as leaf |
| `[| BODY |]` | See notes below. If a computed list expression with yields (explicit or implicit) then process as control-flow. Otherwise treat as leaf |
| `seq { BODY }` | See notes below |
| `builder { BODY }` | See notes below |
| `f expr`, `new C(args)`, constants or other leaf | Debug point when being processed as control-flow. The sub-expressions are processed as non-control-flow. |

#### Intended debug points for let-bindings

Simple `let` bindings get debug points that extend over the `let` (if the thing is not a function and the implementation is a leaf expression):

```fsharp
let f () =
    let x = 1 // debug point for whole of `let x = 1`
    let f x = 1 // no debug point on `let f x =`, debug point on `1`
    let x = if today then 1 else tomorrow // no debug point on `let x =`, debug point on `if today then` and `1` and `tomorrow`
    let x = let y = 1 in y + y // no debug point on `let x =`, debug point on `let y = 1` and `y + y`
    ...
```

#### Intended debug points for nested control-flow

Debug points are not generally emitted for constituent parts of non-leaf constructs, in particular function applications, e.g. consider:

```fsharp
let h1 x = g (f x)
let h2 x = x |> f |> g
```

Here `g (f x)` gets one debug point covering the whole expression. The corresponding pipelining gets three debug points.

If however a nested expression is control-flow, then debug points start being emitted again e.g.

```fsharp
let h3 x = f (if today then 1 else 2)
```

Here debug points are at `if today then` and `1` and `2` and all of `f (if today then 1 else 2)`

> NOTE: these debug points are overlapping. That's life.

### Intended debug points for `[...]`, `[| ... |]` code

The intended debug points for computed list and array expressions are the same as for the expressions inside the constructs. For example

```fsharp
let x = [ for i in 1 .. 10 do yield 1 ]
```

This will have debug points on `for i in 1 .. 10 do` and `yield 1`.

### Intended debug points for `seq { .. }` and `task { .. }` code

The intended debug points for tasks is the same as for the expressions inside the constructs. For example

```fsharp
let f() = task { for i in 1 .. 10 do printn "hello" }
```

This will have debug points on `for i in 1 .. 10 do` and `printn "hello"`.

> NOTE: there are glitches, see further below

### Intended debug points for other computation expressions

Other computation expressions such as `async { .. }` or `builder { ... }` get debug points as follows:

* A debug point for `builder` prior to the evaluation of the expression

* In the de-sugaring of the computation expression, each point a lambda is created implicitly, then the body of that
  lambda as specified by the F# language spec is treated as control-flow and debug points added per the earlier spec.

* For every `builder.Bind`, `builder.BindReturn` and similar call that corresponds to a `let` where there would be a debug point, a debug point is added immediately prior to the call.

* For every `builder.For` call, a debug point covering the `for` keyword is added immediately prior to the call.  No debug point is added for the `builder.For` call itself even if used in statement position.

* For every `builder.While` call, a debug point covering the `while` keyword plus guard expression is added immediately prior to the execution of the guard within the guard lambda expression. No debug point is added for the `builder.While` call itself even if used in statement position.

* For every `builder.TryFinally` call, a debug point covering the `try` keyword is added immediately within the body lambda expression. A debug point covering the `finally` keyword is added immediately within the finally lambda expression. No debug point is added for the `builder.TryFinally` call itself even if used in statement position.

* For every `builder.Yield`, `builder.Return`, `builder.YieldFrom` or `builder.ReturnFrom` call, debug points are placed on the expression as if it were control flow. For example `yield 1` will place a debug point on `1` and `yield! printn "hello"; [2]` will place two debug points.

* No debug point is added for the `builder.Run`, `builder.Run` or `builder.Delay` calls at the entrance to the computation expression, nor the `builder.Delay` calls implied by `try/with` or `try/finally` or sequential `Combine` calls.

The computations are often "cold-start" anyway, leading to a two-phase debug problem.

The "step-into" and "step-over" behaviour for computation expressions is often buggy because it is performed with respect to the de-sugaring and inlining rather than the original source.
For example, a "step over" on a "while" with a non-inlined `builder.While` will step over the whole call, when the user expects it to step the loop.
One approach is to inline the `builder.While` method, and apply `[]` to the body function. This however has only limited success
as at some points inlining fails to fully flatten. Builders implemented with resumable code tend to be much better in this regards as
more complete inlining and code-flattening is applied.

### Intended debug points for implicit constructors

* The `let` and `do` bindings of an implicit constructor generally gets debug points as if it were a function.
* `inherits SubClass(expr)` gets a debug point. If there is no inherits, an initial debug point is placed over the text of the arguments.

e.g.
```fsharp
type C(args) =        
    let x = 1+1         // debug point over `let x = 1+1` as the only side effect
    let f x = x + 1
    member _.P = x + f 4

type C(args) =        
    do printn "hello"         // debug point over `printn "hello"` as side effect
    static do printn "hello"         // debug point over `printn "hello"` as side effect for static init
    let f x = x + 1
    member _.P = x + f 4

type C(args) =        // debug point over `(args)` since there's no other place to stop on object construction
    let f x = x + 1
    member _.P = 4
```

## Internal implementation of debug points in the compiler

Most (but not all) debug points are noted by the parser by adding `DebugPointAtTry`, `DebugPointAtWith`, `DebugPointAtFinally`, `DebugPointAtFor`, `DebugPointAtWhile`, `DebugPointAtBinding` or `DebugPointAtLeaf`.

These are then used by `ValidateBreakpointLocation`. These same values are also propagated unchanged all the way through to `IlxGen.fs` for actual code generation, and used for IL emit, e.g. a simple case like this:

```fsharp
    match spTry with
    | DebugPointAtTry.Yes m -> CG.EmitDebugPoint cgbuf m ... 
    | DebugPointAtTry.No -> ...
    ...
```

For many constructs this is adequate. However, in practice the situation is far more complicated.

### Internals: Debug points for `[...]`, `[| ... |]`

The internal implementation of debug points for list and array expressions is conceptually simple but a little complex.

Conceptually the task is easy, e.g. `[ while check() do yield x + x ]` is lowered to code like this:

```fsharp
let $collector = ListCollector<int>()
while check() do
    $collector.Add(x+x)
$collector.Close()
```

Note the `while` loop is still a `while` loop - no magic here - and the debug points for the `while` loop can also apply to the actual generated `for` loop.

However, the actual implementation is more complicated because there is a TypedTree representation of the code in-between that at first seems to bear little resemblance to what comes in.

```text
SyntaxTree --[CheckComputationExpressions.fs]--> TypedTree --> IlxGen -->[LowerComputedListOrArrayExpr.fs]--> IlxGen
```

The TypedTree is a functional encoding into `Seq.toList`, `Seq.singleton` and so on. How do the debug points get propagated?

* In `CheckComputationExpressions.fs` we "note" the debug point for the For loop and attach it to one of the lambdas generated in the TypedTreeForm
* In `LowerSequences.fs` we "recover" the debug point from precisely that lambda.
* In `IlxGen.fs` this becomes an actual debug point in the actual generated "while" loop.

This then gives accurate debug points for these constructs.

### Internals: debug points for `seq { .. .}` code

Debug points for `seq { .. }` compiling to state machines poses similar problems.

* The de-sugaring is as for list and array expressions
* The debug points are recovered in the state machine generation

### Internals: debug points for `task { .. .}` code

Debug points for `task { .. }` poses much harder problems. We use "while" loops as an example:

* The de-sugaring is for computation expressions, and in CheckComputationExpressions.fs places a debug point for `while` directly before the evaluation of the guard
* The code is then checked and optimized, and all the resumable code is inlined, and this debug point is preserved throughout this process.

### Internals: debug points for other computation expressions

As mentioned above, other computation expressions such as `async { .. }` have significant problems with their debug points.

The main problem is stepping: even after inlining the code for computation expressions is rarely "flattened" enough, so, for example, a "step-into" is required to get into the second part of an `expr1; expr2` construct (i.e. an `async.Combine(..., async.Delay(fun () -> ...))`) where the user expects to press "step-over".  

Breakpoints tend to be less problematic.

> NOTE: A systematic solution for quality debugging of computation expressions code is still elusive, and especially for `async { ... }`.  Extensive use of inlining and `InlineIfLambda` can succeed in flattening most simple computation expression code. This is however not yet fully applied to `async` programming.

> NOTE: The use of library code to implement "async" and similar computation expressions also interacts badly with "Just My Code" debugging, see https://github.com/dotnet/fsharp/issues/5539 for example.

> NOTE: As mentioned, the use of many functions to implement "async" and friends implements badly with "Step Into" and "Step Over" and related attributes, see for example https://github.com/dotnet/fsharp/issues/3359

### FeeFee and F00F00 debug points (Hidden and JustMyCodeWithNoSource)

Some fragments of code use constructs generate calls and other IL code that should not have debug points and not participate in "Step Into", for example. These are generated in IlxGen as "FeeFee" debug points. See the [the Portable PDB spec linked here](https://learn.microsoft.com/dotnet/api/system.reflection.metadata.sequencepoint.hiddenline?view=net-5.0).

> TODO: There is also the future prospect of generating `JustMyCodeWithNoSource` (0xF00F00) debug points but these are not yet emitted by F#.  We should check what this is and when the C# compiler emits these.

> NOTE: We always make space for a debug point at the head of each method by [emitting a FeeFee debug sequence point](https://github.com/dotnet/fsharp/blob/main/src/Compiler/CodeGen/IlxGen.fs#L1953). This may be immediately replaced by a "real" debug point [here](https://github.com/dotnet/fsharp/blob/main/src/Compiler/CodeGen/IlxGen.fs#L2019).

## Generated code

The F# compiler generates entire IL classes and methods for constructs such as records, closures, state machines and so on. Each time code is generated we must carefully consider what attributes and debug points are generated.

### Generated "augment" methods for records, unions and structs

Generated methods for equality, hash and comparison on records, unions and structs do not get debug points at all.

> NOTE: Methods without debug points (or with only 0xFEEFEE debug points) are shown as "no code available" in Visual Studio - or in Just My Code they are hidden altogether - and are removed from profiling traces (in profiling, their costs are added to the cost of the calling method).

> TODO: we should also consider emitting `ExcludeFromCodeCoverageAttribute`, being assessed at time of writing, however the absence of debug points should be sufficient to exclude these.

### Generated "New*", "Is*", "Tag" etc. for unions

Discriminated unions generate `NewXYZ`, `IsXYZ`, `Tag` etc. members. These do not get debug points at all.

These methods also get `CompilerGeneratedAttribute`, and `DebuggerNonUserCodeAttribute`.

> TODO: we should also consider emitting `ExcludeFromCodeCoverageAttribute`, being assessed at time of writing, however the absence of debug points should be sufficient to exclude these.

> TODO: the `NewABC` methods are missing `CompilerGeneratedAttribute`, and `DebuggerNonUserCodeAttribute`. However, the absence of debug points should be sufficient to exclude these from code coverage and profiling.

### Generated closures for lambdas

The debug codegen involved in closures is as follows:

|  Source         | Construct         | Debug Points | Attributes   |
|:----------------|:------------------|:-------------|:-------------|
| (fun x -> ...)  | Closure class     |              |              |
|                 | `.ctor` method    | none     | CompilerGenerated, DebuggerNonUserCode |
|                 | `Invoke` method   | from body of closure     |                                        |
| generic local defn  | Closure class |      |                                        |
|                 | `.ctor` method    | none     | CompilerGenerated, DebuggerNonUserCode |
|                 | `Specialize` method |  from body of closure  |                                        |
|  Intermediate closure classes   |  For long curried closures `fun a b c d e f -> ...`.  |   | CompilerGenerated, DebuggerNonUserCode     |

Generated intermediate closure methods do not get debug points, and are labelled CompilerGenerated and DebuggerNonUserCode.

> TODO: we should also consider emitting `ExcludeFromCodeCoverageAttribute`, being assessed at time of writing

### Generated state machines for `seq { .. }`

Sequence expressions generate class implementations which resemble closures.

The debug points recovered for the generated state machine code for `seq { ... }` is covered up above. The other codegen is as follows:

|  Source         | Construct         | Debug Points | Attributes   |
|:----------------|:------------------|:-------------|:-------------|
| seq { ... }     | State machine class |            |  "Closure"             |
|                 | `.ctor` method    |   none      | none |
|                 | `GetFreshEnumerator`   |  none  | CompilerGenerated, DebuggerNonUserCode |
|                 | `LastGenerated`   |  none  | CompilerGenerated, DebuggerNonUserCode |
|                 | `Close`   |  none  | none |
|                 | `get_CheckClose`  |  none  | none |
|                 | `GenerateNext`    |  from desugaring | none |

> NOTE: it appears from the code that extraneous debug points are not being generated, which is good, though should be checked

> TODO: we should likely be generating `CompilerGeneratedAttribute` and `DebuggerNonUserCodeAttribute` attributes for the `Close` and `get_CheckClose` and `.ctor` methods

> TODO: we should also consider emitting `ExcludeFromCodeCoverageAttribute`, being assessed at time of writing

### Generated state machines for `task { .. }`

[Resumable state machines](https://github.com/fsharp/fslang-design/blob/main/FSharp-6.0/FS-1087-resumable-code.md) used for `task { .. }` also generate struct implementations which resemble closures.

The debug points recovered for the generated state machine code for `seq { ... }` is covered up above. The other codegen is as follows:

|  Source         | Construct         | Debug Points | Attributes   | Notes |
|:----------------|:------------------|:-------------|:-------------|:------|
| task { ... }     | State machine struct |          |  "Closure"   |       |
|                 | `.ctor` method    |   none      |  none       | |
|                 | TBD               |              |              | |

> TODO: we should be generating attributes for some of these

> TODO: we should assess that only the "MoveNext" method gets any debug points at all

> TODO: Currently stepping into a task-returning method needs a second `step-into` to get into the MoveNext method of the state machine.  We should emit the `StateMachineMethod` and `StateMachineHoistedLocalScopes` tables into the PDB to get better debugging into `task` methods. See https://github.com/dotnet/fsharp/issues/12000.

### Generated code for delegate constructions `Func(fun x y -> x + y)`

A closure class is generated.  Consider the code

```fsharp
open System
let d = Func<int,int,int>(fun x y -> x + y)
```

There is one debug point over all of `Func(fun x y -> x + y)` and one over `x+y`.

### Generated code for constant-sized array and list expressions

These are not generally problematic for debug.

### Generated code for large constant arrays

These are not generally problematic for debug.

### Generated code for pattern matching

The implementation is a little gnarly and complicated and has historically had glitches.

### Generated code for conditionals and boolean logic

Generally straight-forward. See for example [this proposed feature improvement](https://github.com/dotnet/fsharp/issues/11980)

### Capture and closures

Captured locals are available via the `this` pointer of the immediate closure.  Un-captured locals are **not** available as things stand.  See for example [this proposed feature improvement](https://github.com/dotnet/fsharp/issues/11262).

Consider this code:

```fsharp
let F() =
    let x = 1
    let y = 2
    (fun () -> x + y)
```

Here `x` and `y` become closure fields of the closure class generated for the final lambda. When inspecting locals in the inner closure, the C# expression evaluator we rely on for Visual Studio takes local names like `x` and `y` and is happy to look them up via `this`. This means hovering over `x` correctly produces the value stored in `this.x`.

For nested closures, values are implicitly re-captured, and again the captured locals will be available.

However this doesn't work with "capture" from a class-defined "let" context. Consider the following variation:

```fsharp
type C() =
    let x = 1
    member _.M() = 
        let y = 2
        (fun () -> x + y)
```

Here the implicitly captured local is `y`, but `x` is **not** captured, instead it is implicitly rewritten by the F# compiler to `c.x` where `c` is the captured outer "this" pointer of the invocation of `M()`.  This means that hovering over `x` does not produce a value. See [issue 3759](https://github.com/dotnet/fsharp/issues/3759).

### Provided code

Code provided by erasing type providers has all debugging points removed.  It isn't possible to step into such code or if there are implicit debug points they will be the same range as the construct that was macro-expanded by the code erasure.

> For example, a [provided if/then/else expression has no debug point](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Checking/MethodCalls.fs#L1805)

## Added code generation for better debugging

We do some "extra" code gen to improve debugging. It is likely much of this could be removed if we had an expression evaluator for F#.

### 'this' value

For `member x.Foo() = ...` the implementation of the member adds a local variable `x` containing the `this` pointer from `ldarg.0`. This means hovering over `x` in the method produces the right value, as does `x.Property` etc.

### Pipeline debugging

For pipeline debugging we emit extra locals for each stage of a pipe and debug points at each stage.

See [pipeline debugging mini-spec](https://github.com/dotnet/fsharp/pull/11957).

### Shadowed locals

For shadowed locals we change the name of a local for the scope for which it is shadowed.

See [shadowed locals mini-spec](https://github.com/dotnet/fsharp/pull/12018).

### Discriminated union debug display text

For discriminated union types and all implied subtypes we emit a `DebuggerDisplayAttribute` and a private `__DebugDisplay()` method that uses `sprintf "%+0.8A" obj` to format the object.

## Missing debug emit

### Missing debug emit for PDBs

Our PDB emit is missing considerable information:

* Not emitted: [LocalConstants table](https://github.com/dotnet/fsharp/issues/12003)
* Not emitted: [Compilation options table](https://github.com/dotnet/fsharp/issues/12002)
* Not emitted: [Dynamic local variables table](https://github.com/dotnet/fsharp/issues/12001)
* Not emitted: [StateMachineMethod table and StateMachineHoistedLocalScopes table](https://github.com/dotnet/fsharp/issues/12000)
* Not emitted: [ImportScopes table](https://github.com/dotnet/fsharp/issues/1003)

These are major holes in the F# experience. Some are required for things like hot-reload.

### Missing design-time services

Some design-time services are un-implemented by F#:

* Unimplemented: [F# expression evaluator](https://github.com/dotnet/fsharp/issues/2544)
* Unimplemented: [Proximity expressions](https://github.com/dotnet/fsharp/issues/4271) (for Autos window)

These are major holes in the F# experience and should be implemented.

---

### Diagnostics (`docs/diagnostics.md`)

---
title: Diagnostics
category: Compiler Internals
categoryindex: 200
index: 300
---
# Diagnostics

The key types are:

* `ErrorLogger`
* `FSharpDiagnosticSeverity`
* `FSharpDiagnostic`
* `DiagnosticWithText`

and functions

* `warning` - emit a warning
* `errorR` - emit an error and continue
* `error` - emit an error and throw an exception
* `errorRecovery` - recover from an exception

For the compiler, a key file is `https://github.com/dotnet/fsharp/blob/main/src/Compiler/FSComp.txt` holding most of the messages. There are also a few other similar files including some old error messages in `FSStrings.resx`.

## Adding Diagnostics

Adding or adjusting diagnostics emitted by the compiler is usually straightforward (though it can sometimes imply deeper compiler work). Here's the general process:

1. Reproduce the compiler error or warning with the latest F# compiler built from the [F# compiler repository](https://github.com/dotnet/fsharp).
2. Find the error code (such as `FS0020`) in the message.
3. Use a search tool and search for a part of the message. You should find it in `FSComp.fs` with a title, such as `parsMissingTypeArgs`.
4. Use another search tool or a tool like Find All References / Find Usages to see where it's used in the compiler source code.
5. Set a breakpoint at the location in source you found. If you debug the compiler with the same steps, it should trigger the breakpoint you set. This verifies that the location you found is the one that emits the error or warning you want to improve.

From here, you can either simply update the error text, or you can use some of the information at the point in the source code you identified to see if there is more information to include in the error message. For example, if the error message doesn't contain information about the identifier the user is using incorrectly, you may be able to include the name of the identifier based on data the compiler has available at that stage of compilation.

If you're including data from user code in an error message, it's important to also write a test that verifies the exact error message for a given string of F# code.

## Formatting Typed Tree items in Diagnostics

Diagnostics must often format TAST items as user text. When formatting these, you normally use either

* The functions in the `NicePrint` module such as `NicePrint.outputTyconRef`. These take a `DisplayEnv` that records the context in which a type was referenced, for example, the open namespaces. Opened namespaces are not shown in the displayed output.

* The `DisplayName` properties on the relevant object. This drops the `'n` text that .NET adds to the compiled name of a type, and uses the F#-facing name for a type rather than the compiled name for a type (for example, the name given in a `CompiledName` attribute).

When formatting "info" objects, see the functions in the `NicePrint` module.

## Notes on displaying types

Diagnostics must often format types.

* When displaying a type, you will normally want to "prettify" the type first. This converts any remaining type inference variables to new, better user-friendly type variables with names like `'a`. Various functions prettify types prior to display, for example, `NicePrint.layoutPrettifiedTypes` and others.

* When displaying multiple types in a comparative way, for example, two types that didn't match, you will want to display the minimal amount of information to convey the fact that the two types are different, for example, `NicePrint.minimalStringsOfTwoTypes`.

* When displaying a type, you have the option of displaying the constraints implied by any type variables mentioned in the types, appended as `when ...`. For example, `NicePrint.layoutPrettifiedTypeAndConstraints`.

## Localization

The file `FSComp.txt` contains the canonical listing of diagnostic messages, but there are also `.xlf` localization files for various languages.
See [the DEVGUIDE](../DEVGUIDE.md#updating-fscompfs-fscompresx-and-xlf) for more details.

## Enabling a warning or error by default

The file `CompilerDiagnostics.fs` contains the function `IsWarningOrInfoEnabled`, which determines whether a given diagnostic is emitted.

---

### Fsharp Core Notes (`docs/fsharp-core-notes.md`)

---
title: Guidance
category: FSharp.Core
categoryindex: 500
index: 100
---
# Notes and Guidance on FSharp.Core

This technical guide discusses the FSharp.Core library.

Reference documentation for FSharp.Core can be found here: https://fsharp.github.io/fsharp-core-docs/

Much of the guidance below applies to any .NET library respecting binary compatibility.

## FSharp.Core is binary compatible

FSharp.Core is binary compatible across versions of the F# language. For example, this means you can create a newer project with a newer FSharp.Core in an older codebase and things should generally "just work".

**Binary compatibility means that a component built for X can bind to Y at runtime. It doesn't mean that Y behaves 100% the same as X, though.** For example, an older compiler that doesn't know how to understand `inref<'T>` referencing a newer FSharp.Core that has `inref<'T>` defined may not behave correctly if `inref<'T>` is used in source.

## FSharp.Core and F# scripts

F# scripts, executed by F# interactive, execute against the FSharp.Core deployed with the .NET SDK you are using. If you're expecting to use a more modern library feature and find that it's missing, it's likely because you have an older .NET SDK and thus an older F# Interactive. Upgrade your .NET SDK.

## Guidance for package authors

If you are authoring a NuGet package for consumption in the F# and .NET ecosystem, you already have to make a decision about functionality vs. reach by deciding what target framework(s) you support.

As an F# package author, you also need to make this decision with respect to FSharp.Core:

* Targeting an earlier version of FSharp.Core increases your reach because older codebases can use it without issue
* Targeting a newer version of FSharp.Core lets you use and extend newer features

This decision is critical, because it can have a network effect. If you choose a higher FSharp.Core version, then that also becomes a dependency for any other package that may depend on your package.

### Package authors should pin their FSharp.Core reference

The default templates for F# projects carry an implicit reference to FSharp.Core. This is ideal for application developers, since applications almost always want to be referencing the highest FSharp.Core available to them. As you upgrade your .NET SDK, the FSharp.Core package referenced implicitly will also be upgraded over time, since FSharp.Core is also distributed with the .NET SDK.

However, as a package author this means that unless you reference FSharp.Core explicitly, you will default to the latest possible version and thus eliminate any hope of reaching older projects in older environments.

### How to explicitly reference FSharp.Core

It's a simple gesture in your project file that pins to FSharp.Core 4.7.2:

```xml
<ItemGroup>
  <PackageReference Update="FSharp.Core" Version="4.7.2" />
</ItemGroup>
```

Or if you're using Paket:

```
nuget FSharp.Core >= 4.7.2
```

And that's it!

### Compatibility table

The following table can help you decide the minimum language/package version you want to support:

|Minimum F# language version|Minimum FSharp.Core package version|
|------------------------------|------------------------------|
|F# 4.1|4.3.4|
|F# 4.5|4.5.2|
|F# 4.6|4.6.2|
|F# 4.7|4.7.2|
|F# 5.0|5.0.0|
|F# 6.0|6.0.0|

If you want to be compatible with much older projects using an F# 4.0 compiler or earlier, you can still do that but it's not recommended. People using those codebases should upgrade instead.

### Do *not* bundle FSharp.Core directly with a library 

Do _not_ include a copy of FSharp.Core with your library or package, such in the `lib` folder of a package. If you do this, you will create havoc for users of your library.

The decision about which `FSharp.Core` a library binds to is up to the application hosting of the library.

## Guidance for everyone else

If you're not authoring packages for distribution, you have a lot less to worry about.

If you are distributing library code across a private organization as if it were a NuGet package, please see the above guidance, as it likely still applies. Otherwise, the below guidance applies.

### Application authors don't have to explicitly reference FSharp.Core

In general, applications can always just use the latest FSharp.Core bundled in the SDK they are built with.

### C# projects referencing F# projects may need to pin FSharp.Core

You can reference an F# project just fine without needing to be explicit about an FSharp.Core reference when using C# projects based on the .NET SDK. References flow transitively for SDK-style projects, so even if you need to use types directly from FSharp.Core (which you probably shouldn't do anyways) it will pick up the right types from the right assembly.

If you do have an explicit FSharp.Core reference in your C# project that you **need**, you should pin your FSharp.Core reference across your entire codebase. Being in a mixed pinned/non-pinned world is difficult to keep straight over a long period of time.

## Guidance for older projects, compilers, and tools

Modern .NET development, including F#, uses SDK-style projects. You can read about that here: https://learn.microsoft.com/dotnet/core/project-sdk/overview

If you are not using SDK-style projects F# projects and/or have an older toolset, the following guidance applies.

### Consider upgrading

Yes, really. The old project system that manages legacy projects is not that good, the compiler is older and unoptimized for supporting larger codebases, tooling is not as responsive, etc. You will really have a much better life if you upgrade. Try out the `try-convert` tool to do that: https://github.com/dotnet/try-convert

If you cannot upgrade for some reason, the rest of the guidance applies.

### Always deploy FSharp.Core as part of a compiled application

For applications, FSharp.Core is normally part of the application itself (so-called "xcopy deploy" of FSharp.Core).  

For older project files, you may need to use ``true`` in your project file. In  Visual Studio this is equivalent to setting the `CopyLocal` property to `true` properties for the `FSharp.Core` reference.

FSharp.Core.dll will normally appear in the `bin` output folder for your application. For example:

```
    Directory of ...\ConsoleApplication3\bin\Debug\net5.0
    
    18/04/2020  13:20             5,632 ConsoleApplication3.exe
    14/10/2020  12:12         1,400,472 FSharp.Core.dll
```

### FSharp.Core and static linking

The ILMerge tool and the F# compiler both allow static linking of assemblies including static linking of FSharp.Core.
This can be useful to build a single standalone file for a tool.

However, these options must be used with caution. 

* Only use this option for applications, not libraries. If it's not a .EXE (or a library that is effectively an application) then don't even try using this option.

Searching on stackoverflow reveals further guidance on this topic.

## Reference: FSharp.Core version and NuGet package numbers

See [the F# version information RFC](https://github.com/fsharp/fslang-design/blob/master/tooling/FST-1004-versioning-plan.md).

---

### Fsi Emit (`docs/fsi-emit.md`)

---
title: F# Interactive Emit
category: Compiler Internals
categoryindex: 200
index: 375
---
# F# Interactive Code Generation

F# Interactive (`dotnet fsi`) accepts incremental code fragments. This capability is also used by [hosted execution capability of the FSharp.Compiler.Service API](fcs/interactive.fsx) which is used to build the F# kernel for .NET Interactive notebooks.

Historically F# Interactive code was  emitted into a single dynamic assembly using Reflection.Emit and ilreflect.fs (meaning one assembly that is continually growing). However, .NET Core Reflection.Emit does not support the emit of debug symbols for dynamic assemblies, so in Feb 2022 we switched to emitting multiple non-dynamic assemblies (meaning assemblies dynamically created in-memory using ilwrite.fs, and loaded, but not growing).

The assemblies are named:

`FSI-ASSEMBLY1`
`FSI-ASSEMBLY2`

etc.

## Compat switch

There is a switch `fsi --multiemit` that turns on the use of multi-assembly generation (when it is off, we use Reflection Emit for  single-dynamic-assembly generation).  This is on by default for .NET Core, and off by default for .NET Framework for compat reasons.

## Are multiple assemblies too costly?

There is general assumption in this that on modern dev machines (where users execute multiple interactions) then generating 50, 100 or 1,000 or 10,000 dynamic assemblies by repeated manual execution of code is not a problem: the extra overheads of multiple assemblies compared to one dynamic assembly is of no real significance in developer REPL scenarios given the vast amount of memory available on modern 64-bit machines.

Quick check: adding 10,000 `let x = 1;;` interactions to .NET Core `dotnet fsi` adds about 300MB to the FSI.EXE process, meaning 30K/interaction. A budget of 1GB for interactive fragments (reasonable on a 64-bit machine), and an expected maximum of 10000 fragments before restart (that's a lot!), then each fragment can take up to 100K. This is well below the cost of a new assembly.

Additionally, these costs are not substantially reduced if `--multiemit` is disabled, so they've always been the approximate costs of F# Interactive fragment generation.

## Internals and accessibility across fragments

Generating into multiple assemblies raises issues for some things that are assembly bound such as "internals" accessibility. In a first iteration of this we had a failing case here:

```fsharp
> artifacts\bin\fsi\Debug\net50\fsi.exe --optimize-
...
// Fragment 1
> let internal f() = 1;;
val internal f: unit -> int

// Fragment 2 - according to existing rules it is allowed to access internal things of the first
f();; 
System.MethodAccessException: Attempt by method '<StartupCode$FSI_0003>.$FSI_0003.main@()' to access method 'FSI_0002.f()' failed.
   at <StartupCode$FSI_0003>.$FSI_0003.main@()
```

This is because we are now generating into multiple assemblies. Another bug was this:

```fsharp
> artifacts\bin\fsi\Debug\net50\fsi.exe --optimize+
...
// Fragment 1 - not `x` becomes an internal field of the class
> type C() =
>    let mutable x = 1
>    member _.M() = x
> ;;
...
// Fragment 2 - inlining 'M()' gave an access to the internal field `x`
> C().M();;
...<bang>...
```

According to the current F# scripting programming model (the one checked in the editor), the "internal" thing should be accessible in subsequent fragments. Should this be changed? No:

* It's very hard to adjust the implementation of the editor scripting model to consider fragments delimited by `;;` to be different assemblies, whether in the editor or in F# Interactive. 
* And would we even want to?  It's common enough for people to debug code scattered with "internal" declarations.  
* In scripts, the `;;` aren't actually accurate markers for what will or won't be sent to F# Interactive, which get added implicitly.

  For example, consider the script

  ```fsharp
  let internal f() = 1;;
  f();; 
  ```

  In the editor should this be given an error or not?  That is, should the `;;` be seen as accurate indicators of separate script fragments? (Answer: yes if we know the script will be piped-to-input, no if the script is used as a single file entry - when the `;;` are ignored)

* Further, this would be a breaking change, e.g. it could arise in an automated compat situation if people are piping into standard input and the input contains `;;` markers.

Because of this we emit IVTs for the next 30 `FSI-ASSEMBLYnnn` assemblies on each assembly fragment, giving a warning when an internal thing is accessed across assembly boundaries within that 30 (reporting it as a deprecated feature), and give an error if internal access happens after that.

From a compat perspective this seems reasonable, and the compat flag is available to return the whole system to generate-one-assembly behavior.

---

### Labelops (`docs/labelops.md`)

---
title: LabelOps
category: Compiler Internals
categoryindex: 200
index: 960
---
# LabelOps

Opt-in, label-gated agentic workflows that keep open PRs healthy. Add a label to a PR โ†’ the agent checks it every 3 hours.

## Labels

| Label | Applied by | Effect |
|---|---|---|
| `AI-Auto-Resolve-Conflicts` | Maintainer | Merges `main` and resolves real conflicts (not mere behind-main). |
| `AI-Auto-Resolve-CI` | Maintainer | Triages CI failures: small fix, flake dispatch, or escalation. |
| `AI-needs-CI-fix-input` | Agent | Agent couldn't fix within budget; see escalation comment. |

All labels are sticky โ€” the agent never removes them. Remove a label to opt out.

## How it works

Each run (every 3h): select up to 3 eligible PRs โ†’ for each: **CI first** (fix or escalate), **conflicts second** (only real conflicts). Pushing restarts CI, so only one action per PR per run.

Proven flakes (โ‰ฅ3 distinct PRs via `flaky-test-detector`) get dispatched to `labelops-flake-fix` โ€” a separate workflow that opens a dedicated fix/quarantine PR.

## Guardrails

- Never modifies `.github/**`, never merges/approves/closes PRs.
- Never rebases or force-pushes. Merge commits only.
- Skips fork PRs, drafts, and `AI-Issue-Regression-PR` PRs.
- Every comment prefixed `๐Ÿค– *LabelOps โ€” <subtopic>.*`.

## Setup (one-time)

```bash
gh label create "AI-Auto-Resolve-Conflicts" --repo dotnet/fsharp --color ededed \
  --description "Opt-in: LabelOps merges main and resolves conflicts every 3h"
gh label create "AI-Auto-Resolve-CI" --repo dotnet/fsharp --color ededed \
  --description "Opt-in: LabelOps triages CI failures every 3h"
gh label create "AI-needs-CI-fix-input" --repo dotnet/fsharp --color fbca04 \
  --description "LabelOps escalated: agent couldn't fix; see comment"
```

## Workflows

- [`.github/workflows/labelops-pr-maintenance.md`](../.github/workflows/labelops-pr-maintenance.md) โ€” scheduled babysitter.
- [`.github/workflows/labelops-flake-fix.md`](../.github/workflows/labelops-flake-fix.md) โ€” flake-fix spinoff (dispatch only).

---

### Large Inputs And Stack Overflows (`docs/large-inputs-and-stack-overflows.md`)

---
title: Large inputs
category: Compiler Internals
categoryindex: 200
index: 500
---
# Processing large inputs without stack overflows

The compiler accepts large inputs such as:

* Large literals, such as `let str = "a1" + "a2" + ... + "a1000"`
* Large array expressions
* Large list expressions
* Long lists of sequential expressions
* Long lists of bindings, such as `let v1 = e1 in let v2 = e2 in ....`
* Long sequences of `if .. then ... else` expressions
* Long sequences of `match x with ... | ...` expressions
* Combinations of these

The compiler performs constant folding for large constants so there are no costs to using them at runtime. However, this is subject to a machine's stack size when compiling, leading to `StackOverflow` exceptions if those constants are very large. The same can be observed for certain kinds of array, list, or sequence expressions. This appears to be more prominent when compiling on macOS because macOS has a smaller stack size.

Many sources of `StackOverflow` exceptions prior to F# 4.7 when processing these kinds of constructs were resolved by processing them on the heap via continuation passing techniques. This avoids filling data on the stack and appears to have negligible effects on overall throughput or memory usage of the compiler.

There are two techniques to deal with this

1. Linearizing processing of specific input shapes, keeping stacks small
2. Using stack guards to simply temporarily move to a new thread when a certain threshold is reached.

## Linearizing processing if certain inputs

Aside from array expressions, most of the previously-listed inputs are called "linear" expressions. This means that there is a single linear hole in the shape of expressions. For example:

* `expr :: HOLE` (list expressions or other right-linear constructions)
* `expr; HOLE` (sequential expressions)
* `let v = expr in HOLE` (let expressions)
* `if expr then expr else HOLE` (conditional expression)
* `match expr with pat[vs] -> e1[vs] | pat2 -> HOLE` (for example, `match expr with Some x -> ... | None -> ...`)

Processing these constructs with continuation passing is more difficult than a more "natural" approach that would use the stack.

For example, consider the following contrived example:

```fsharp
and remapLinearExpr g compgen tmenv expr contf =
    match expr with
    | Expr.Let (bind, bodyExpr, m, _) ->
        ...
        // tailcall for the linear position
        remapLinearExpr g compgen tmenvinner bodyExpr (contf << (fun bodyExpr' ->
            ...))

    | Expr.Sequential (expr1, expr2, dir, spSeq, m)  ->
        ...
        // tailcall for the linear position
        remapLinearExpr g compgen tmenv expr2 (contf << (fun expr2' ->
            ...))

    | LinearMatchExpr (spBind, exprm, dtree, tg1, expr2, sp2, m2, ty) ->
        ...
        // tailcall for the linear position
        remapLinearExpr g compgen tmenv expr2 (contf << (fun expr2' ->  ...))

    | LinearOpExpr (op, tyargs, argsFront, argLast, m) ->
        ...
        // tailcall for the linear position
        remapLinearExpr g compgen tmenv argLast (contf << (fun argLast' -> ...))

    | _ -> contf (remapExpr g compgen tmenv e)

and remapExpr (g: TcGlobals) (compgen:ValCopyFlag) (tmenv:Remap) expr =
    match expr with
    ...
    | LinearOpExpr _
    | LinearMatchExpr _
    | Expr.Sequential _
    | Expr.Let _ -> remapLinearExpr g compgen tmenv expr (fun x -> x)
```

The `remapExpr` operation becomes two functions, `remapExpr` (for non-linear cases) and `remapLinearExpr` (for linear cases). `remapLinearExpr` uses tailcalls for constructs in the `HOLE` positions mentioned previously, passing the result to the continuation.

Some common aspects of this style of programming are:

* The tell-tale use of `contf` (continuation function)
* The processing of the body expression `e` of a let-expression is tail-recursive, if the next construct is also a let-expression.
* The processing of the `e2` expression of a sequential-expression is tail-recursive
* The processing of the second expression in a cons is tail-recursive

The previous example is considered incomplete, because arbitrary _combinations_ of `let` and sequential expressions aren't going to be dealt with in a tail-recursive way. The compiler generally tries to do these combinations as well.

## Stack Guards

The `StackGuard` type is used to move to a new thread if there is no sufficient stack space for a synchronous recursive call. Compilation globals are re-installed. Sample:

```fsharp

...
   stackGuard = StackGuard("TcExpr")

let rec ....

and TcExpr cenv ty (env: TcEnv) tpenv (expr: SynExpr) =

    // Guard the stack for deeply nested expressions
    cenv.stackGuard.Guard <| fun () ->

    ...

```

Note stack guarding doesn't result in a tailcall so will appear in recursive stack frames, because a counter must be decremented after the call.

Compiling with `--times` option will show a summary if any thread switches were made due to stack guarding:


```
    StackGuard jumps:
    -----------------------------------------------------
    | caller |        source        | jumps | min depth |
    |--------|----------------------|-------|-----------|
    | exprF  | TypedTreeOps.fs:7444 |    25 |      1601 |
    -----------------------------------------------------
```

---

### Lsp (`docs/lsp.md`)

---
title: F# LSP
category: Language Service Internals
categoryindex: 300
index: 500
---
# F# LSP

F# LSP support design proposal. To be expanded as we learn more / settle on things.

## Table of contents

- [F# LSP](#f-lsp)
  - [Table of contents](#table-of-contents)
  - [Goals / Requirements](#goals--requirements)
  - [Overview](#overview)
  - [Components](#components)
    - [FsLSP](#fslsp)
      - [FsLSPServer](#fslspserver)
      - [Workspace State Management](#workspace-state-management)
        - [Things to keep track of:](#things-to-keep-track-of)
      - [Project model](#project-model)
      - [Example sequence diagram](#example-sequence-diagram)
      - [Concurrency and cancellation](#concurrency-and-cancellation)
      - [Request tracking](#request-tracking)
      - [Configuration updates](#configuration-updates)
      - [Extensibility](#extensibility)
      - [Release process](#release-process)
    - [NewVsix](#newvsix)
    - [FCS](#fcs)
  - [LSP library](#lsp-library)
    - [Candidates](#candidates)
  - [LSP client (?)](#lsp-client-)
  - [Diagnostic mode](#diagnostic-mode)
  - [LSIF](#lsif)
  - [Performance](#performance)
  - [Analyzers \& Refactoring support](#analyzers--refactoring-support)
  - [Mixed F# / C# solutions](#mixed-f--c-solutions)
  - [Multiple workspace support](#multiple-workspace-support)
  - [Action plan](#action-plan)
    - [Phase 0](#phase-0)
    - [Phase 1](#phase-1)
    - [Phase 2 - 5](#phase-2---5)
  - [LSP Endpoints](#lsp-endpoints)


## Goals / Requirements

- Fully featured F# LSP server implementation that can be used by any editor
  - Can be used as a library (with optional customization) or executable
  - Independent of any particular project system - this needs to be handled by the client
  - Target LSP protocol version 3.17 (at least initially, we can upgrade as we go)
  - Support all useful LSP APIs
  - Support for analyzers
  - Support for code fixes and refactoring
  - Support for mixed F# / C# solutions (being able to delegate to C# LSP server)
  - Good performance
  - Support for multiple workspaces
  - Support for LSIF
- Visual Studio extension that will use the LSP server

## Overview

```mermaid

graph TD

VS(VS) -->|VS LSP client| NewVsix("NewVsix (Out-of-process)")

VsCode(VS Code) --> Ionide(Ionide/FSAC)

Vim(Vim) --> Ionide(Ionide/FSAC)

FsLSPServer(FsLSP Server)

NewVsix --> ProjectQuery(Project Query API)
NewVsix --> FsLSP

FsLSPServer --> FsLSP



subgraph FsLSP
ProjectModel(Project Model)
WorkspaceStateManagement(Workspace State Management)
LSPLibrary(LSP Library)
end

FsLSP --> FCS

Ionide --> FsLSPServer
Ionide --> ProjInfo(ProjInfo)

style VS stroke-dasharray: 2 4
style Vim stroke-dasharray: 2 4
style VsCode stroke-dasharray: 2 4
style Ionide stroke-dasharray: 2 4
style ProjInfo stroke-dasharray: 2 4
style LSPLibrary stroke-dasharray: 2 4
style ProjectQuery stroke-dasharray: 2 4

```



| Component | Description | Release Process |
| --- | --- | --- |
| FCS | F# Compiler Service | Nuget |
| **FsLSP** | The F# LSP library. This can be used to create an LSP server.  | Nuget |
| - **FsLSPServer** | The F# LSP server. An executable that can be started and will process LSP calls. Thin wrapper around FsLSP. | with **FsLSP** |
|  - **Workspace State Management** | A system for keeping a "current snapshot" of projects the user is working with |  |
|  - **Project Model** | A model for a project system we will use since we can't be tied to any particular one | |
|  - LSP Library | A ready made library we will hopefully find to simplify LSP server development |  |
| **NewVsix** | The new VS Extension which will be powered by LSP | with VS |


## Components

### FsLSP

The library containing the LSP functionality. Can be used to create an LSP server by adding the transport layer.

#### FsLSPServer

The LSP server executable using STD IN/OUT for transport.

Will be packaged with FsLSP and available as a dotnet tool (?)

It should always target the latest .NET runtime.

#### Workspace State Management

This part of the library is responsible for keeping track of the current workspace state. The internal workspace model is used to create requests for FCS. It needs to make sure the internal state doesn't get out of sync with what's in the editor and on disk.

##### Things to keep track of:
- Incoming document changes
- Documents' open/closed status
- Source files on-disk changes for documents that aren't open
- Any on-disk references that could have been rebuilt/updated

#### Project model

We need some internal model for a workspace/solution with projects. We don't want to use any project system library because we don't need most of the functionality which will be instead handled by the client.

The model needs to be able to quickly absorb incoming changes and easily convert to (or already contain) inputs for FCS. We could consider a reactive/adaptive approach for this.

We should keep track of versions of each project and source file including all dependencies with regards to parsing and type checking.

#### Example sequence diagram

```mermaid

sequenceDiagram

participant Client
participant FsLSP
participant WorkspaceStateManagement
participant FCS

Client->>FsLSP: textDocument/didChange
FsLSP->>WorkspaceStateManagement: Update workspace state
Client->>+FsLSP: textDocument/diagnostic
FsLSP->>+WorkspaceStateManagement: Get current workspace snapshot
WorkspaceStateManagement-->>-FsLSP: Current workspace snapshot
FsLSP->>+FCS: ParseAndCheckFileInProject
FCS-->>-FsLSP: FSharpCheckFileResults
FsLSP-->>-Client: DocumentDiagnosticReport

```


#### Concurrency and cancellation

We need to make sure when a project snapshot is needed to process a request, it will always contain all change notifications that were received before. There shouldn't be need for a global request queue, possibly only for change notifications inside WorkspaceStateManagement. And any language requests will wait until WorkspaceStateManagement has absorbed all previous changes.

For cancellation we should decide, potentially on a case-by-case basis what to do when an incoming change notification invalidates an ongoing request.

1. Do nothing and let the request run either until it completes or is cancelled by the client. This is the easiest so we will probably start with this.
2. Cancel the request and return "cancelled" result to the client. The client then needs to deal with it.
3. Internally restart the work updated inputs and complete the original request until cancelled. This mode might save a bunch of back-and-forth with the client when user is typing. But it requires the client to expect it and not send cancellations a re-requests for the same thing.


Request update example

```mermaid

sequenceDiagram

participant Client
participant FsLSP
participant WorkspaceStateManagement
participant FCS

Client->>+FsLSP: textDocument/diagnostic
FsLSP->>+WorkspaceStateManagement: Get current workspace snapshot
WorkspaceStateManagement-->>-FsLSP: Current workspace snapshot
FsLSP->>FCS: ParseAndCheckFileInProject
activate FCS
Client->>FsLSP: textDocument/didChange
FsLSP->>FCS: Cancel ParseAndCheckFileInProject
deactivate FCS
FsLSP->>WorkspaceStateManagement: Update workspace state
FsLSP->>+WorkspaceStateManagement: Get current workspace snapshot
WorkspaceStateManagement-->>-FsLSP: Current workspace snapshot

FsLSP->>+FCS: ParseAndCheckFileInProject
FCS-->>-FsLSP: FSharpCheckFileResults
FsLSP-->>-Client: DocumentDiagnosticReport

```




#### Request tracking

This will hopefully be handled by the LSP library. Although we might need to be able to keep track of requests in progress for the purpose of cancellation.


#### Configuration updates

We should be able to apply configuration changes that require re-creating the FCS checker. We should be able to keep the caches since the results shouldn't depend on checker settings (rather only the input Project Snapshots).


#### Extensibility

The FsLSP library should be extensible by adding custom endpoints or overriding existing ones.

It should also allow to hook into background processes or events to customize behavior.

The code for extending the behavior should look the same as the code for implementing the core functionality. That way it's easy to test out and then potentially contribute it directly to the library.

#### Release process

We should use a similar release process as we have now for FCS. Potentially also provide a pre-release package with the latest version from `main`.

### NewVsix

New VS extension that will be backed by the LSP server. It should mostly contain plumbing to connect LSP to the editor and CPS.

We should build on new [VisualStudio.Extensibility](https://github.com/microsoft/VSExtensibility/) model to run the extension out-of-process. It can also directly connect native VS LSP directly to our server which we can host directly in the extension.

We might need a wrapper C# project because the new extensibility model relies on source generation.

We should be able to load project information via [ProjectQuery API](https://github.com/microsoft/VSExtensibility/tree/main/New_Extensibility_Model/Samples/VSProjectQueryAPISample) to which we can also subscribe for changes.

The new extension should be able to run side by side with the current and extension gradually take over the functionality we will add to LSP.

If there is any missing functionality in any of these, we might need to supplement it from our current MEF extension and connect from it to the new one.

### FCS

The only strict requirement on FCS is to be able to process open files from memory and non-open files from filesystem. But probably best to abstract that within FsLSP and just provide `ISourceText` objects for both cases.

At the moment this is possible via the experimental `DocumentSource.Custom` parameter. But hopefully we will be able to use a new `FSharpProjectSnapshot` model (from [#15179](https://github.com/dotnet/fsharp/pull/15179)) which combines all the necessary inputs into a single object.


## LSP library

The library needs to support all the features we want to implement including the ability to add custom F# ones. It also shouldn't incur an unnecessary performance overhead.

### Candidates

| Library  | Pros | Cons |
| ---  | --- | --- |
| We write our own | + Complete control over everything | - A lot of extra work |
| [Ionide LanguageServerProtocol](https://github.com/ionide/LanguageServerProtocol) | + Battle tested with Ionide and FSAC + Natural F# API | - Might not be able to fully customize it to our need |
| [Roslyn CLaSP](https://github.com/dotnet/roslyn/blob/main/src/Features/LanguageServer/Microsoft.CommonLanguageServerProtocol.Framework/README.md) | + Should be performant + Future support | - Not stable API - Unnatural C# API |
| [OmniSharp C# LSP](https://github.com/OmniSharp/csharp-language-server-protocol)  |  | - Unnatural C# API |

It probably makes sense to start with an existing library which should be able to get us to a working product and only then consider writing our own in case it's the only way to enable more functionality or performance.

## LSP client (?)

- Is there any code that can be shared by various clients?
- Most likely not, since each client will be specific to its platform and project system


## Diagnostic mode

Initially we will be offering the **Pull Diagnostics** mode where the client will request diagnostics for a particular document. It will be up to the client to ask for diagnostics for documents at appropriate times and cancel requests that are no longer needed. The client can also use this to implement background checking of all documents in the workspace.

Later we can also add an option to have the server control diagnostics and publish them via **Publish Diagnostics**. Then it will be pushing diagnostics as they are discovered either for active document, open documents or all documents.

## LSIF

We should eventually add support for LSIF. At the moment it looks like we would have to construct it manually (as in, no helpful libraries are available).

This is probably lower priority than anything else.

It might also be a good opportunity to re-think how we store symbols for purposes of finding references, go to definition, or code search (go to symbol in workspace by name/query).


## Performance

Areas to pay attention to with regards to performance

- Serialization. Hopefully this will be handled reasonably by a library.
- Spamming too many requests. Though this will be mainly up to the client, we could think about throttling or debouncing.
- Caching. This will be mostly up to FCS but we can send hints about what can be cleaned up.


## Analyzers & Refactoring support

These will hopefully not require any special treatment and can just use existing LSP APIs.

## Mixed F# / C# solutions

We need to figure out how to talk to C# LSP server for:

- Go to definition
- Find references / rename
- (Make sure these work both ways)


## Multiple workspace support

We should be able to use a single instance of LSP server even for multiple workspaces. We can keep a separate instance of `WorkspaceStateManagement` for each workspace but send language requests to a single instance of FCS checker. This way it's possible to reuse some results if the workspaces share the same projects (with the same options).

## Action plan

A rough draft of how to plan the work and which items can be done in parallel.

### Phase 0

We need to investigate which system we'll use for the New Vsix. Ideally we're able to go with new VisualStudio.Extensibility but if that has any blockers we might need to stick to in-process MEF.

We can prototype the New Vsix with some sample LSP server or maybe with an existing one like FSAC. Just to figure out how to connect it to F# projects in VS.

For FsLSP we need to choose a library and implement a dummy LSP server with it that we can connect to the New Vsix and get some sort of basic setup that can then be improved upon.



```mermaid

gantt
    axisFormat .
    section FsLSP
        Choose LSP library :fs1, 1900-01-01, 1d
        Add projects for FsLSP and FsLSPServer :fs2, after fs1, 1d
        Implement dummy LSP server using the library :fs3, after fs2, 1d
        Connect to NewVsix :after vsix2 fs3, 1d
    section Vsix
        Investigate options for NewVsix :vsix1, 1900-01-01, 1d
        Implement NewVsix prototype with dummy or some existing LSP server :vsix2, after vsix1, 2d
```

### Phase 1

Get some sort of working prototype that can do semantic highlighting and show diagnostics.

Figure out how to run side-by-side and gradually take over functionality from the current Vsix, if possible.

```mermaid

gantt
    axisFormat .
    section FsLSP
        Implement WorkspaceStateManagement :fs1, 1900-01-01, 3d
        Implement Phase 1 endpoints :fs2, after fs1, 2d
    section Vsix
        Investigate running side-by-side with old Vsix :vsix1, 1900-01-01, 1d
        Implement configuration of which features are handled by which Vsix :vsix2, after vsix1, 2d
```

### Phase 2 - 5

Implement endpoints based on their assigned priority. See next section.

Independently we can start on setting up the build & release process for FsLSP and insertions of NewVsix into VS.

Another potentially parallel track can be figuring out interop with C# LSP server for mixed solutions.

```mermaid

gantt
    axisFormat .
    section FsLSP
        Implement Phase 2 endpoints :fs1, 1900-01-01, 1d
        Implement Phase 3 endpoints :fs2, after fs1, 1d
        Implement Phase 4 endpoints :fs3, after fs2, 1d
        Implement Phase 5 endpoints :fs4, after fs3, 1d
    section Distribution
        Settle on release process :d1, 1900-01-01, 1d
        Add / update build pipelines :d2, after d1, 1d
        Set up VS insertions :d3, after d2, 1d

    section Cs interop
        Figure out how to talk to CSharp LSP server :c1, 1900-01-01, 4d
```



## LSP Endpoints

This is a preliminary list of LSP APIs we want (or don't want) to implement, roughly ordered by priority. We will probably re-classify some of them as we go.


| Name                                     | Type         | Direction | Category                          | Priority / Dev phase | Supported by FSAC | Note                                              |
| ---------------------------------------- | ------------ | --------- | --------------------------------- | -------------------- | ----------------- | ------------------------------------------------- |
| `initialize`                             | Request      | โ†ฉ๏ธ         | Lifecycle                         | 0                    | โœ…                 |                                                   |
| `initialized`                            | Notification | โžก๏ธ         | Lifecycle                         | 0                    |                   |                                                   |
| `textDocument/didChange`                 | Notification | โžก๏ธ         | Document synchronization          | 1                    | โœ…                 |                                                   |
| `textDocument/didClose`                  | Notification | โžก๏ธ         | Document synchronization          | 1                    |                   |                                                   |
| `textDocument/didOpen`                   | Notification | โžก๏ธ         | Document synchronization          | 1                    | โœ…                 |                                                   |
| `textDocument/diagnostic`                | Request      | โ†ฉ๏ธ         | Language features                 | 1                    |                   |                                                   |
| `textDocument/semanticTokens/full`       | Request      | โ†ฉ๏ธ         | Language features                 | 1                    |                   |                                                   |
| `$/logTrace`                             | Notification | โฌ…๏ธ         | Lifecycle                         | 1                    |                   |                                                   |
| `$/setTrace`                             | Notification | โžก๏ธ         | Lifecycle                         | 1                    |                   |                                                   |
| `exit`                                   | Notification | โžก๏ธ         | Lifecycle                         | 1                    |                   |                                                   |
| `shutdown`                               | Request      | โ†ฉ๏ธ         | Lifecycle                         | 1                    |                   |                                                   |
| `completionItem/resolve`                 | Request      | โ†ฉ๏ธ         | Language features                 | 2                    | โœ…                 |                                                   |
| `textDocument/completion`                | Request      | โ†ฉ๏ธ         | Language features                 | 2                    | โœ…                 |                                                   |
| `textDocument/declaration`               | Request      | โ†ฉ๏ธ         | Language features                 | 2                    |                   |                                                   |
| `textDocument/definition`                | Request      | โ†ฉ๏ธ         | Language features                 | 2                    | โœ…                 |                                                   |
| `textDocument/hover`                     | Request      | โ†ฉ๏ธ         | Language features                 | 2                    | โœ…                 |                                                   |
| `textDocument/implementation`            | Request      | โ†ฉ๏ธ         | Language features                 | 2                    | โœ…                 |                                                   |
| `textDocument/references`                | Request      | โ†ฉ๏ธ         | Language features                 | 2                    | โœ…                 |                                                   |
| `textDocument/semanticTokens/full/delta` | Request      | โ†ฉ๏ธ         | Language features                 | 2                    |                   |                                                   |
| `textDocument/semanticTokens/range`      | Request      | โ†ฉ๏ธ         | Language features                 | 2                    |                   |                                                   |
| `textDocument/typeDefinition`            | Request      | โ†ฉ๏ธ         | Language features                 | 2                    | โœ…                 |                                                   |
| `workspace/semanticTokens/refresh`       | Request      | โ†ฉ๏ธ         | Language features                 | 2                    | โœ…                 |                                                   |
| `fsharp/compilerLocation`                | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/documentAnalyzed`                | Notification | โฌ…๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/documentation`                   | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/documentationGenerator`          | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/documentationSymbol`             | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/dotnetaddproject`                | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/dotnetaddsln`                    | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/dotnetnewlist`                   | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/dotnetnewrun`                    | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/dotnetremoveproject`             | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/fileParsed`                      | Notification | โฌ…๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/lineLens`                        | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/notifyCancel`                    | Notification | โฌ…๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/notifyWorkspace`                 | Notification | โฌ…๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/notifyWorkspacePeek`             | Notification | โฌ…๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/pipelineHint`                    | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/project`                         | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/signature`                       | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/signatureData`                   | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/testDetected`                    | Notification | โฌ…๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/workspaceLoad`                   | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsharp/workspacePeek`                   | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsproj/addExistingFile`                 | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsproj/addFile`                         | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsproj/addFileAbove`                    | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsproj/addFileBelow`                    | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsproj/moveFileDown`                    | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsproj/moveFileUp`                      | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `fsproj/removeFile`                      | Request      | โ†ฉ๏ธ         | F# Custom                         | 3                    | โœ…                 |                                                   |
| `textDocument/documentHighlight`         | Request      | โ†ฉ๏ธ         | Language features                 | 3                    | โœ…                 |                                                   |
| `textDocument/documentSymbol`            | Request      | โ†ฉ๏ธ         | Language features                 | 3                    | โœ…                 |                                                   |
| `textDocument/formatting`                | Request      | โ†ฉ๏ธ         | Language features                 | 3                    | โœ…                 |                                                   |
| `textDocument/rename`                    | Request      | โ†ฉ๏ธ         | Language features                 | 3                    | โœ…                 |                                                   |
| `textDocument/signatureHelp`             | Request      | โ†ฉ๏ธ         | Language features                 | 3                    | โœ…                 |                                                   |
| `$/progress`                             | Notification | โฌ…๏ธ         | Window Features                   | 3                    | โœ…                | Would be nice to have for long running operations |
| `workspace/didChangeWatchedFiles`        | Notification | โžก๏ธ         | Workspace Features                | 3                    | โœ…                 |                                                   |
| `textDocument/willSave`                  | Notification | โžก๏ธ         | Document synchronization          | 4                    |                   |                                                   |
| `textDocument/willSaveWaitUntil`         | Request      | โ†ฉ๏ธ         | Document synchronization          | 4                    |                   |                                                   |
| `codeAction/resolve`                     | Request      | โ†ฉ๏ธ         | Language features                 | 4                    |                   |                                                   |
| `codeLens/resolve`                       | Request      | โ†ฉ๏ธ         | Language features                 | 4                    | โœ…                 |                                                   |
| `inlayHint/resolve`                      | Request      | โ†ฉ๏ธ         | Language features                 | 4                    |                   |                                                   |
| `textDocument/codeAction`                | Request      | โ†ฉ๏ธ         | Language features                 | 4                    | โœ…                 |                                                   |
| `textDocument/codeLens`                  | Request      | โ†ฉ๏ธ         | Language features                 | 4                    | โœ…                 |                                                   |
| `textDocument/prepareRename`             | Request      | โ†ฉ๏ธ         | Language features                 | 4                    |                   |                                                   |
| `textDocument/publishDiagnostics`        | Notification | โฌ…๏ธ         | Language features                 | 4                    | โœ…                 |                                                   |
| `workspace/codeLens/refresh`             | Request      | โ†ช๏ธ         | Language Features                 | 4                    | โœ…                 |                                                   |
| `window/showMessage`                     | Notification | โฌ…๏ธ         | Window Features                   | 4                    | โœ…                 |                                                   |
| `workspace/configuration`                | Request      | โ†ช๏ธ         | Workspace Features                | 4                    |                   |                                                   |
| `workspace/diagnostic`                   | Request      | โ†ฉ๏ธ         | Workspace Features                | 4                    |                   |                                                   |
| `workspace/didChangeConfiguration`       | Notification | โžก๏ธ         | Workspace Features                | 4                    | โœ…                 |                                                   |
| `workspace/symbol`                       | Request      | โ†ฉ๏ธ         | Workspace Features                | 4                    | โœ…                 |                                                   |
| `workspaceSymbol/resolve`                | Request      | โ†ฉ๏ธ         | Workspace Features                | 4                    |                   |                                                   |
| `notebookDocument/didChange`             | Notification | โžก๏ธ         | Notebook Document synchronization | 5                    |                   |                                                   |
| `notebookDocument/didClose`              | Notification | โžก๏ธ         | Notebook Document synchronization | 5                    |                   |                                                   |
| `notebookDocument/didOpen`               | Notification | โžก๏ธ         | Notebook Document synchronization | 5                    |                   |                                                   |
| `notebookDocument/didSave`               | Notification | โžก๏ธ         | Notebook Document synchronization | 5                    |                   |                                                   |
| `fsharp/f1Help`                          | Request      | โ†ฉ๏ธ         | F# Custom                         | โ”                    | โœ…                 |                                                   |
| `fsharp/fsdn`                            | Request      | โ†ฉ๏ธ         | F# Custom                         | โ”                    | โœ…                 |                                                   |
| `fsharp/loadAnalyzers`                   | Request      | โ†ฉ๏ธ         | F# Custom                         | โ”                    | โœ…                 |                                                   |
| `callHierarchy/incomingCalls`            | Request      | โ†ฉ๏ธ         | Language features                 | โ”                    |                   |                                                   |
| `callHierarchy/outgoingCalls`            | Request      | โ†ฉ๏ธ         | Language features                 | โ”                    |                   |                                                   |
| `documentLink/resolve`                   | Request      | โ†ฉ๏ธ         | Language features                 | โ”                    |                   |                                                   |
| `textDocument/documentLink`              | Request      | โ†ฉ๏ธ         | Language features                 | โ”                    |                   | Is this useful for us?                            |
| `textDocument/inlayHint`                 | Request      | โ†ฉ๏ธ         | Language features                 | โ”                    |                   |                                                   |
| `textDocument/inlineValue`               | Request      | โ†ฉ๏ธ         | Language features                 | โ”                    |                   |                                                   |
| `textDocument/moniker`                   | Request      | โ†ฉ๏ธ         | Language features                 | โ”                    |                   |                                                   |
| `textDocument/prepareCallHierarchy`      | Request      | โ†ฉ๏ธ         | Language features                 | โ”                    |                   |                                                   |
| `textDocument/prepareTypeHierarchy`      | Request      | โ†ฉ๏ธ         | Language features                 | โ”                    |                   |                                                   |
| `textDocument/rangeFormatting`           | Request      | โ†ฉ๏ธ         | Language features                 | โ”                    |                   |                                                   |
| `textDocument/selectionRange`            | Request      | โ†ฉ๏ธ         | Language features                 | โ”                    |                   |                                                   |
| `typeHierarchy/subtypes`                 | Request      | โ†ฉ๏ธ         | Language features                 | โ”                    |                   |                                                   |
| `typeHierarchy/supertypes`               | Request      | โ†ฉ๏ธ         | Language features                 | โ”                    |                   |                                                   |
| `client/registerCapability`              | Request      | โ†ฉ๏ธ         | Lifecycle                         | โ”                    |                   |                                                   |
| `client/unregisterCapability`            | Request      | โ†ฉ๏ธ         | Lifecycle                         | โ”                    |                   |                                                   |
| `telemetry/event`                        | Notification | โฌ…๏ธ         | Window Features                   | โ”                    | โœ…                 |                                                   |
| `window/logMessage`                      | Notification | โฌ…๏ธ         | Window Features                   | โ”                    | โœ…                 |                                                   |
| `window/workDoneProgress/cancel`         | Notification | โฌ…๏ธ         | Window Features                   | โ”                    |                   |                                                   |
| `window/workDoneProgress/create`         | Request      | โ†ฉ๏ธ         | Window Features                   | โ”                    |                   |                                                   |
| `workDoneProgress/cancel`                | Notification | โžก๏ธ         | Window Features                   | โ”                    |                   |                                                   |
| `workDoneProgress/create`                | Request      | โฌ…๏ธ         | Window Features                   | โ”                    |                   |                                                   |
| `workspace/applyEdit`                    | Request      | โ†ช๏ธ         | Workspace Features                | โ”                    | โœ…               | Could be used for refactoring?                   |
| `workspace/diagnostic/refresh`           | Request      | โ†ช๏ธ         | Workspace Features                | โ”                    |                   |                                                   |
| `workspace/executeCommand`               | Request      | โ†ฉ๏ธ         | Workspace Features                | โ”                    |                   | Could be used for refactoring?                   |
| `textDocument/didSave`                   | Notification | โžก๏ธ         | Document synchronization          | โŒ                    | โœ…                 | Probably don't need this?                         |
| `textDocument/colorPresentation`         | Request      | โ†ฉ๏ธ         | Language features                 | โŒ                    |                   |                                                   |
| `textDocument/documentColor`             | Request      | โ†ฉ๏ธ         | Language features                 | โŒ                    |                   |                                                   |
| `textDocument/linkedEditingRange`        | Request      | โ†ฉ๏ธ         | Language features                 | โŒ                    |                   |                                                   |
| `textDocument/onTypeFormatting`          | Request      | โ†ฉ๏ธ         | Language features                 | โŒ                    |                   |                                                   |
| `window/showDocument`                    | Request      | โ†ช๏ธ         | Window Features                   | โŒ                    |                   |                                                   |
| `window/showMessageRequest`              | Request      | โ†ช๏ธ         | Window Features                   | โŒ                    |                   |                                                   |
| `workspace/didChangeWorkspaceFolders`    | Notification | โžก๏ธ         | Workspace Features                | โŒ                    |                   |                                                   |
| `workspace/didCreateFiles`               | Notification | โžก๏ธ         | Workspace Features                | โŒ                    |                   |                                                   |
| `workspace/didDeleteFiles`               | Notification | โžก๏ธ         | Workspace Features                | โŒ                    |                   |                                                   |
| `workspace/didRenameFiles`               | Notification | โžก๏ธ         | Workspace Features                | โŒ                    |                   |                                                   |
| `workspace/willCreateFiles`              | Notification | โžก๏ธ         | Workspace Features                | โŒ                    |                   |                                                   |
| `workspace/willDeleteFiles`              | Notification | โžก๏ธ         | Workspace Features                | โŒ                    |                   |                                                   |
| `workspace/willRenameFiles`              | Notification | โžก๏ธ         | Workspace Features                | โŒ                    |                   |                                                   |
| `workspace/workspaceFolders`             | Request      | โฌ…๏ธ         | Workspace Features                | โŒ                    |                   |                                                   |
|                                          |              |           |                                   |                      |                   |                                                   |

---

### Memory Usage (`docs/memory-usage.md`)

---
title: Memory usage
category: Compiler Internals
categoryindex: 200
index: 600
---
# Compiler Memory Usage

Overall memory usage is a primary determinant of the usability of the F# compiler and instances of the F# compiler service. 

## Why memory usage matters

Overly high memory usage results in poor throughput (particularly due to increased GC times) and low user interface responsivity in tools such as Visual Studio or other editing environments. In some extreme cases, it can lead to Visual Studio crashing or another IDE becoming unusable due to constant paging from absurdly high memory usage. Luckily, these extreme cases are very rare.

When you do a single compilation to produce a binary, memory usage typically doesn't matter much. It's often fine to allocate a lot of memory because it will just be reclaimed after compilation is over.

However, the F# compiler is not simply a batch process that accepts source code as input and produces an assembly as output. When you consider the needs of editor and project tooling in IDEs, the F# compiler is:

* A database of syntactic and semantic data about the code hosted in an IDE
* An API for tools to request tooling-specific data (e.g. F# tooltip information)
* In FsAutoComplete or other LSP implementations, it's a server process that accepts requests for syntactic and semantic information

Thinking about the F# compiler in these ways makes performance far more complicated than just throughput of a batch compilation process.

## Analyzing compiler memory usage

In general, the F# compiler allocates a lot of memory. More than it needs to. However, most of the "easy" sources of allocations have been squashed out and what remains are many smaller sources of allocations. The remaining "big" pieces allocate as a result of their current architecture, so it isn't straightforward to address them.

Some allocations are much more than others
* Large Object Heap (LOH) allocations (> ~80K) are rarely collected and should only be used for long-lived items. 
* Ephemeral allocations that never escape the Gen0 seem to not matter that much, though of course should be considered.
* Don't try to remove all allocations, and don't assume copying of large structs is better than allocating a reference type. Measure instead.

To analyze memory usage of F# tooling, you have two primary avenues:

1. Take a process dump on your machine and analyze it 
2. Use sampling to collect a trace of your system while you perform various tasks in an IDE, ideally for 60 seconds or more.

You can analyze dumps and take samples with [dotMemory](https://www.jetbrains.com/dotmemory/) or [PerfView](https://github.com/Microsoft/perfview).

To analyze memory usage of the F# compiler itself:
* extract the compilation arguments out of msbuild output (or in the output pane of Visual Studio)
* put this content in a "response file" (a text file listing compiler arguments, one per line)
* use the memory profiler tool of choice, invoking the compiler (either fsc.exe, or through `dotnet path/to/fsc.dll`) giving it the argument `@name-of-response-file`, and setting the directory of the project that is compiled as `working directory`

### Analyzing a process dump file

Process dump files are extremely information-rich data files that can be used to see the distribution of memory usage across various types. Tools like [dotMemory](https://www.jetbrains.com/dotmemory/) will show these distributions and intelligently group things to help identify the biggest areas worth improving. Additionally, they will notice things like duplicate strings and sparse arrays, which are often great ways to improve memory usage since it means more memory is being used than is necessary.

### Analyzing a sample trace of IDE usage

The other important tool to understand memory and CPU usage for a given sample of IDE usage is a trace file. These are collected and analyzed by tools like [PerfView](https://github.com/Microsoft/perfview) and [dotTrace](https://www.jetbrains.com/profiler/).

When analyzing a trace, there are a few things to look out for:

1. Overall GC statistics for the sample to give an overall picture of what was going on in the IDE for your sample:
   a. How much CPU time was spent in the GC as a percentage of total CPU time for the IDE process?
   b. What was the peak working set (total memory usage)?
   c. What was the peak allocations per second?
   d. How many allocations were Gen0? Gen1? Gen2?
2. Memory allocations for the sample, typically also ignoring object deaths:
   a. Is `LargeObject` showing up anywhere prominently? If so, that's a problem!
   b. Which objects show up highest on the list? Does their presence that high make sense?
   c. For a type such as `System.String`, which caller allocates it the most? Can that be improved?
3. CPU sampling data, sorted by most CPU time
   a. Are any methods showing up that correspond with high memory allocations? Something showing up prominently in both places is often a sign that it needs work!

After analyzing a trace, you should have a good idea of places that could see improvement. Often times a tuple can be made into a struct tuple, or some convenient string processing could be adjusted to use a `ReadonlySpan<'T>` or turned into a more verbose loop that avoids allocations.

---

### Names (`docs/names.md`)

---
title: Display names, logical names and compiled names
category: Compiler Internals
categoryindex: 200
index: 350
---
# Names of entities and values in the F# Compiler

The F# tooling distinguishes between the following concepts of "name" for values, union cases, class/record fields and entities:

* Display names as they appear in code

  Characteristics:
  - For most identifiers, have double backticks, e.g. ` ``Module name with spaces`` `
  - For operator names, are short and parenthesized, e.g. `(+)` for the logical name `op_Addition`
  - For active patterns, are parenthesized, e.g. `(|A|_|)`
  - etc., see exact specification below

  Used in:
  - Code outputs, e.g. signature files
  - Diagnostics (BUG: not consistently the case today)

  Current aliases in code:
  - `vref.DisplayName`
  - `entity.DisplayName`
  - `entity.DisplayNameWithStaticParameters`
  - `entity.DisplayNameWithStaticParametersAndUnderscoreTypars`
  - `minfo.DisplayName`
  - `pinfo.DisplayName`
  - `einfo.DisplayName`
  - etc.

* Display names as they appear in declaration lists, navigation etc.

  Characteristics:
  - Same as above without the double backticks or parentheses

  Current aliases in code:
  - `vref.DisplayNameCore`
  - `entity.DisplayNameCore`
  - `minfo.DisplayNameCore`
  - `pinfo.DisplayNameCore`
  - `einfo.DisplayNameCore`
  - etc.

* Logical names

  Characteristics:
  - Are used in `TypedTree`, often "canonical"
  - Sometimes require extra flags to qualify the meaning of the name

  Current aliases in code:
  - `vref.LogicalName`
  - `entity.LogicalName`
  - `minfo.LogicalName`
  - `pinfo.PropertyName`
  - `einfo.EventName`
  - etc.

* Compiled names

  Characteristics: 
  - Mark the names that appear in the .NET IL

  Current aliases in code:
  - `vref.CompiledName`
  - `entity.CompiledName`
  - etc.


## Specification of all logical names

The following tables loosely characterise the variations in logical names, how
they correspond to F# source constructs and the `SyntaxTree`/`TypedTree` metadata for these.

Entities:

Display name in code        | Logical name   | Notes 
----------------------------|----------------|-------
C                           | C              | type definition
C                           | C`1            | e.g. generic type, see notes below for variations of display names
M                           | M              | module definition
M                           | MModule        | "ModuleSuffix" attribute for F# modules, now somewhat legacy, rarely used, but still allowed; also where "ModuleSuffix" is implied because type and module have the same name
JsonProvider<"foo.json">    | JsonProvider,Schema=\"xyz\" |   static parameters, see notes below for variations of display names

Values:

 Display name in code | Relation | Logical name         | Notes
 ---------------------|----------|----------------------|------
 (+)                  | <-->     | op_Addition          |
 (+ )                 |  -->     | op_Addition          | not reversed
 op_Addition          |  -->     | op_Addition          | not reversed
 (*)                  | <-->     | op_Multiply          | 
 ( * )                |  -->     | op_Multiply          | not reversed
 op_Multiply          |  -->     | op_Multiply          | not reversed
 ( *+ )               | <-->     | op_MultiplyPlus      | 
 ( *+  )              |  -->     | op_MultiplyPlus      | not reversed
 op_MultiplyPlus      |  -->     | op_MultiplyPlus      | not reversed
 (+++)                | <-->     | op_PlusPlusPlus      |
 op_PlusPlusPlus      |  -->     | op_PlusPlusPlus      | not reversed
 (%)                  | <-->     | op_Modulus           |
 op_Modulus           |  -->     | op_Modulus           |
 (?)                  | <-->     | op_Dynamic           | not defined by default, for x?SomeThing
 (?<-)                | <-->     | op_DynamicAssignment | not defined by default, for x?SomeThing <- "a"
 (..)                 | <-->     | op_Range             |       for "3 .. 5"
 (.. ..)              | <-->     | op_RangeStep         |       for "5 .. -1 .. 3"
 or                   | <-->     | or                   |
 mod                  | <-->     | mod                  |
 ``let``              | <-->     | let                  | this is a keyword, in code it appears as ``let``
 ``type``             | <-->     | type                 | this is a keyword, in code it appears as ``type``
 base                 | <-->     | base                 | for IsBaseVal=true only. Base is a keyword, this is a special base val
 ``base``             | <-->     | base                 | for IsBaseVal=false only. Base is a keyword, this is not a special base val
 SomeClass            | <-->     | .ctor                | IsConstructor=true
 ``.ctor``            | <-->     | .ctor                | IsConstructor=false, this is only allowed for let-definitions, e.g. let ``.ctor`` x = 1
 <not-shown>          | <-->     | .cctor               | IsClassConstructor=true, should never really appear in diagnostics or user-facing output
 ``.cctor``           | <-->     | .cctor               | IsClassConstructor=false, this is only allowed for let-definitions, e.g. let ``.cctor`` x = 1
 (\|A\|_\|)           | <-->     | \|A\|_\|             |
 (\|A  \|_  \|)       |  -->     | \|A\|_\|             | not reversed
 P                    | <-->     | get_P                | IsPropertyGetterMethod = true
 P                    | <-->     | set_P                | IsPropertySetterMethod = true

Other Val constructs less problematic for naming are:

Display name in code | Relation | Logical name         | Notes
---------------------|----------|----------------------|------
this                 | <-->     | this                 | IsCtorThisVal = true; From `type C() as this`; Can have any name, not particularly special with regard to names; This has a 'ref' type for initialization checks
this                 | <-->     | this                 | IsMemberThisVal = true; From `member this.M() = ...`; This can have a 'ref' type for initialization checks; Can have any name, not particularly special with regard to names
\<not-shown\>        | <-->     | System.IDisposable.Dispose | ImplementedSlotSigs is non-empty, i.e. length 1, should never really appear in diagnostics or user-facing output

Union cases:

Display name in code | Relation | Logical name         | Notes
---------------------|----------|----------------------|------
SomeCase             | <-->     | SomeCase
` ``Case with space`` `  | <-->     | Case with space
` ``type`` `             | <-->     | type                 | This is a keyword
(::)                 | <-->     | op_ColonColon        | This is the logical name for the cons union case on `FSharpList` only
[]                   | <-->     | op_Nil               | This is the logical name for the nil case on `FSharpList` only

Class and record fields, enum cases, active pattern cases, anonymous record fields:

Display name in code | Relation | Logical name         | Notes
---------------------|----------|----------------------|------
SomeField            | <-->     | SomeField
` ``Field with space`` `| <-->     | Field with space
` ``type`` `            | <-->     | type                 | This is a keyword

Generic parameters:

Display name in code | Relation | Logical name         | Notes
---------------------|----------|----------------------|------
'T                   | <-->     | T
'` ``T T T`` `           | <-->     | T T T                | BUG: the backticks are not currently added
'` ``type`` `            | <-->     | type                 | This is a keyword, BUG: the backticks are not currently added

## Variations on display names

In different display settings, Entities/Types/Modules can have some variations on display names. For example, when showing some kinds of output we may set `shortTypeNames=true` which will never show full names.

* `SomeTypeProvider`
  - Used for omitting static parameters
  - Alias in code: `entity.CompiledName`

* `SomeTypeProvider<...>`   
  - Used for eliding static parameters

* `SomeTypeProvider<"foo.json">`   
  - Used for showing static parameters. These can be very large, e.g. entire connection strings, so better to elide or omit.
  - Alias in code: `entity.DisplayNameWithStaticParameters`

* `List<_>`   
  - Used with underscore typars
  - Alias in code: `entity.DisplayNameWithStaticParametersAndUnderscoreTypars`

* `Dictionary<'TKey,'TResult>`
  - Used with general typars

* Full name

Examples:
  - `SomeNamespace.OtherNamespace.SomeType`
  - ``` ``Some Namespace With Spaces``.SomeType```  <-- BUG: not double-ticks today
  - `SomeEnclosingType<_>.SomeStaticMethod`         <-- BUG: the mangled generic type counts are shown today


## Compiled names

The name that appears in the .NET IL.

Affected by:
- `CompiledName` attribute
- some heuristics for generic type parameters

Also the name from signature is generally preferred; if there is any difference, a warning is emitted.

Example of how signature affects compiled names

```fsharp
Foo.fsi 

  val SomeFunction: x: int -> y: int -> int 

Foo.fs

  let SomeFunction a b = a + b // compiled name of parameters is x, y - warning emitted
```

---

### Optimizations Equality (`docs/optimizations-equality.md`)

---
title: Equality optimizations
category: Compiler Internals
categoryindex: 200
index: 450
---
# Compiling Equality

This spec covers how equality is compiled and executed by the F# compiler and library, based mainly on the types involved in the equality operation after all inlining, type specialization and other optimizations have been applied.

## What do we mean by an equality operation?

This spec is about the semantics and performance of the following coding constructs

* `a = b`
* `a <> b`

It is also about the semantics and performance of uses of the following `FSharp.Core` constructs which, after inlining, generate code that contains an equality check at the specific `EQTYPE`
* `HashIdentity.Structural<'T>`
* `{Array,Seq,List}.contains`
* `{Array,Seq,List}.countBy`
* `{Array,Seq,List}.groupBy`
* `{Array,Seq,List}.distinct`
* `{Array,Seq,List}.distinctBy`
* `{Array,Seq,List}.except`

All of which have implied equality checks. Some of these operations are inlined, see below, which in turn affects the semantics and performance of the overall operation.

## ER vs PER equality

In math, a (binary) relation is a way to describe a relationship between the elements of sets. "Greater than" is a relation for numbers, "Subset of" is a relation for sets.

Here we talk about 3 particular relations:
1) **Reflexivity** - every element is related to itself
  - For integers, `=` is reflexive (`a = a` is always true) and `>` is not (`a > a` is never true)
2) **Symmetry** - if `a` is related to `b`, then `b` is related to `a`
  - For integers, `=` is symmetric (`a = b` -> `b = a`) and `>` is not (if `a > b` then `b > a` is false)
3) **Transitivity** -  if `a` is related to `b`, and `b` is related to `c`, then `a` is also related `c`
  - For integers, `>` is transitive (`a > b` && `b > c` -> `a > c`) and `โˆš` is not (`a = โˆšb` && `b = โˆšc` doesn't mean `a = โˆšc`)

If a relation has 1, 2, and 3, we talk about **Equivalence Relation (ER)**. If a relation only has 2 and 3, we talk about **Partial Equivalence Relation (PER)**.

This matters in comparing floats since they include [NaN](https://en.wikipedia.org/wiki/NaN). Depending on if we consider `NaN = NaN` true or false, we talk about ER or PER comparison respectively. 

## What is the type known to the compiler and library for an equality operation?

The static type known to the F# compiler is crucial to determining the performance of the operation. The runtime type of the equality check is also significant in some situations.

Here we define the relevant static type `EQTYPE` for the different constructs above:

### Basics

* `a = b`:  `EQTYPE` is the statically known type of `a` or `b`
* `a <> b`: `EQTYPE` is the statically known type of `a` or `b`

### Inlined constructs

* `HashIdentity.Structural<'T>`, `EQTYPE` is the **inlined** `'T` (results in specialized equality)
* `Array.contains<'T>`, `EQTYPE` is the **inlined** `'T` (results in specialized equality)
* `List.contains<T>` likewise
* `Seq.contains<T>` likewise

These only result in naked generic equality if themselves used from a non-inlined generic context.

### Non-inlined constructs always resulting in naked generic equality

* `Array.groupBy<'Key, 'T> f array`, `EQTYPE` is non-inlined `'Key`, results in naked generic equality
* `Array.countBy array` likewise for `'T`
* `Array.distinct<'T> array` likewise
* `Array.distinctBy array` likewise
* `Array.except array` likewise
* `List.groupBy` likewise
* `List.countBy` likewise
* `List.distinct` likewise
* `List.distinctBy` likewise
* `List.except` likewise
* `Seq.groupBy` likewise
* `Seq.countBy` likewise
* `Seq.distinct` likewise
* `Seq.distinctBy` likewise
* `Seq.except` likewise

These **always** result in naked generic equality checks.

Example 1:

```fsharp
let x = HashIdentity.Structural<byte>  // EQTYPE known to compiler is `byte`
```

Example 2 (a non-inlined "naked" generic context):

```fsharp
let f2<'T> () =
   ... some long code
   // EQTYPE known to the compiler is `'T`
   // RUNTIME-EQTYPE known to the library is `byte`
   let x = HashIdentity.Structural<'T>
   ... some long code

f2<byte>() // performance of this is determined by EQTYPE<'T> and RUNTIME-EQTYPE<byte>
```

Example 3 (an inlined generic context):

```fsharp
let f3<'T> () =
   ... some long code
   // EQTYPE known to the compiler is `byte`
   // RUNTIME-EQTYPE known to the library is `byte`
   let x = HashIdentity.Structural<'T>
   ... some long code

f3<byte>() // performance of this is determined by EQTYPE<byte> and RUNTIME-EQTYPE<byte>
```

Example 4 (a generic struct type in a non-inline generic context):

```fsharp
let f4<'T> () =
   ... some long code
   // EQTYPE known to the compiler is `SomeStructType<'T>`
   // RUNTIME-EQTYPE known to the library is `SomeStructType<byte>`
   let x = HashIdentity.Structural<SomeStructType<'T>>
   ... some long code

f4<byte>() // performance of this determined by EQTYPE<SomeStructType<'T>> and RUNTIME-EQTYPE<SomeStructType<byte>>
```

## How we compile equality "a = b"

This very much depends on the `EQTYPE` involved in the equality as known by the compiler

Aim here is to flesh these all out with:
* **Semantics**: what semantics the user expects, and what the semantics actually is
* **Perf expectation**: what perf the user expects
* **Compilation today**: How we actually compile today
* **Perf today**: What is the perf we achieve today
* (Optional) sharplab.io link to how things are in whatever version is selected in sharplab
* (Optional) notes

### primitive integer types (`int32`, `int64`, ...)

```fsharp
let f (x: int) (y: int) = (x = y)
```

* Semantics: equality on primitive
* Perf: User expects full performance down to native
* Compilation today: compiles to IL instruction โœ…
* Perf today: good โœ…
* [sharplab int32](https://sharplab.io/#v2:DYLgZgzgNAJiDUAfYBTALgAjBgFADxAwEsA7NASlwE9DSKMBeXPRjK8gWACgg===)

### primitive floating point types (`float32`, `float64`)

```fsharp
let f (x: float32) (y: float32) = (x = y)
```

* Semantics: IEEE floating point equality (respecting NaN etc.)
* Perf: User expects full performance down to native
* Compilation today: compiles to IL instruction โœ…
* Perf today: good โœ…
* [sharplab float32](https://sharplab.io/#v2:DYLgZgzgNAJiDUAfYBTALgAjBgFADxC2AHsBDNAZgCYBKXAT0LBPOroF5c8NP6aBYAFBA===)

### primitive `string`, `decimal`

* Semantics: .NET equivalent equality, non-localized for strings
* Perf: User expects full performance down to native
* Compilation today: compiles to `String.Equals` or `Decimal.op_Equality` call โœ…
* Perf today: good โœ…
* [sharplab decimal](https://sharplab.io/#v2:DYLgZgzgNALiCWwoBMQGoA+wCmMAEYeAFAB4h7LYDG8AtgIbACUxAnuZTQ83gLzEk+eVkwCwAKCA)
* [sharplab string](https://sharplab.io/#v2:DYLgZgzgNALiCWwoBMQGoA+wCmMAEYeAFAB4h4QwBO8AdgOYCUxAnuZTQ8wLzEl68WjALAAoIA==)

### reference tuple type (size <= 5)

* Semantics: User expects structural
* Perf: User expects flattening to constituent checks
* Compilation today: tuple equality is flattened to constituent checks โœ…
* Perf today: good โœ…
* [sharplab (int * double * 'T), with example reductions/optimizations noted](https://sharplab.io/#v2:DYLgZgzgPgsAUMApgFwARlQCgB4iwSwDs0AqVAEwHsBXAIyVTIHIAVASjdQE9UBeLbH25t48TCVFxB/LpIC0cosCJEA5goB8kgOKJCiAE74AxgFEAjtQCGy5D0Gy48BUpWF1crU7gAJKxAALAGFKAFsABysDRAA6XX0jM0sbfDsAMX80B1R5RUJlQjVNHT1DEwtrWy4ASWIjQggTAB4WAEZGVBYAJg6WAGYNVAdcgHlw5HxQ/AAvQ00sckQAN3wDNHiypMrUmrqiRuMRbwyIZAqbCBZqcKQ+1AAZK3drVUQABSMpiaXECDjSxIhCJRQwCVoAGmwXUhfU4mC4EK40K4sNyrkK7mK3iQaGMYUi0QMQkezysrw+k1S+B+fw2gPxIIM8Dp5WSVQA6qlggzCSdcTzQdh2gjUAAyUXMgGs7Z2TnIbnA3mZVB4xWCnpIsUSuAsrYpWVcoEEwx8lUConYO4o3KDSQ4s1qon8EmqF7vT5Umn/BImI2M+DGRDmIbC9rigNBoYanrhnVSvUcw3m2rIeoHB3Gi1WvqSEhHeBAA==)

### reference tuple type (size > 5)

* Semantics: User expects structural
* Perf: User expects flattening to constituent checks
* Compilation today: not flattened, compiled to `GenericEqualityIntrinsic`
* Perf today: the check does type tests, does virtual calls via `IStructuralEqualityComparer`, boxes etc. โŒ(Problem1)
* [sharplab for size 6](https://sharplab.io/#v2:DYLgZgzgPgsAUMApgFwARlQCgB4iwSwDs0AqVI0841MimqyigSidQE9UBeLbL9p+EA==)

### struct tuple type

* Semantics: User expects structural
* Perf: User expects flattening to constituent checks or at least the same optimizations as tuples
* Compilation today: compiled to `GenericEqualityIntrinsic`
* Perf today: boxes, does type tests, does virtual calls via `IStructuralEqualityComparer` etc. โŒ(Problem2)
* [sharplab for size 3](https://sharplab.io/#v2:DYLgZgzgPgsAUMApgFwARlQCgB4lRZAJwFcBjNTASwDs0AqVG+x2gSldQE9UBeLbXl1bwgA=)

### C# or F# enum type

* Semantics: User expects identical to equality on the underlying type
* Perf: User expects same perf as flattening to underlying type
* Compilation today: flattens to underlying type
* Perf today: good โœ…
* [sharplab for C# enum int](https://sharplab.io/#v2:DYLgZgzgNALiCWwA+BYAUMApjABGHAFAB4g4DKAnhDJgLYB0AIgIYUDyYA6ppgNYCUOCjgC8hIqKH90QA===)
* [sharplab for F# enum int](https://sharplab.io/#v2:DYLgZgzgNALiCWwA+BYAUDAngBwKYAIBRfAXn3X0qXwEFT8BGCq/AIXoCZ11hcZ8w+ABQAPEEQCU+TPVH1ME9EA=)

### C# struct type

* Semantics: User expects call to `IEquatable<T>` if present, but F# spec says call `this.Equals(box that)`, in practice these are the same
* Perf expected: no boxing
* Compilation today: EqualityComparer<T>.Default
* Perf today: good โœ…
* [sharplab](https://sharplab.io/#v2:DYLgZgzgNALiCWwA+BYAUMApjABGHAFAB4g4DKAnhDJgLYB0AIgIY0Aq8tmA8mJNgEocFHAF5CRMcIHogA==)

### F# struct type (records, tuples - with compiler-generated structural equality)

* Semantics: User expects field-by-field structural equality
* Perf expected: no boxing
* Compilation today: `GenericEqualityIntrinsic<SomeStructType>`
* Perf today: good โœ…
* [sharplab](https://sharplab.io/#v2:DYLgZgzgNALiCWwA+BYAUAbQDwGUYCcBXAYxgD4BddGATwAcBTAAhwHsBbBvI0gCgDcQTeADsYUJoSGiYASiYBedExVNO7AEYN8TAPoA6AGqKm/ZavVadBgKonC6dMAYwmYJrwAeQtp24k5JhoTLxMaWXQgA)



### array type (byte[], int[], some-struct-type[],  ...)

* Semantics: User expects structural
* Perf expected: User expects perf is sum of constituent parts
* Compilation today: either ``FSharpEqualityComparer_PER`1::get_EqualityComparer().Equals(...)`` or ``FSharpEqualityComparer_PER`1<T[]>::get_EqualityComparer().Equals(...)``
* Perf today: good โœ…
* [sharplab for `byte[]`](https://sharplab.io/#v2:DYLgZgzgPgsAUMApgFwARlQCgB4lQIwE9lEBtAXQEpVDUBeLbemy+IA=)

### F# large reference record/union type

Here "large" means the compiler-generated structural equality is NOT inlined.

* Semantics: User expects structural by default
* Perf expected: User expects perf is sum of constituent parts, type-specialized if generic
* Compilation today: direct call to `Equals(T)`
* Perf today: the call to `Equals(T)` has specialized code but boxes fields if struct or generic, see Problem3 โŒ, Problem4 โŒ

### F# tiny reference (anonymous) record or union type

Here "tiny" means the compiler-generated structural equality IS inlined.

* Semantics: User expects structural by default
* Perf expected: User expects perf is sum of constituent parts, type-specialized if generic
* Compilation today: ``FSharpEqualityComparer_ER`1<!a>::get_EqualityComparer().Equals(...)``
* Perf today: good โœ…

### Generic `'T` in non-inlined generic code

* Semantics: User expects the PER equality semantics of whatever `'T` actually is  
* Perf expected: User expects no boxing
* Compilation today: ``FSharpEqualityComparer_ER`1<!a>::get_EqualityComparer().Equals(...)``
* Perf today: good โœ…

### Generic `'T` in recursive position in structural comparison

This case happens in structural equality for tuple types and other structural types

* Semantics: User expects the PER equality semantics of whatever `'T` actually is  
* Perf: User expects no boxing
* Compilation today: ``FSharpEqualityComparer_ER`1<!a>::get_EqualityComparer().Equals(...)`` 
* Perf today: good โœ…
* [Sharplab](https://sharplab.io/#v2:DYLgZgzgPgsAUMApgFwARlQCgB4iwSwDs0AqVAEwHsBXAIyVTIHIAVASjdQE9UBeLbH25t48TCVFxB/LpIC0cosCJEA5goB8kgOKJCiAE74AxgFEAjtQCGy5D0Gy48BUpWF1crU7gAJKxAALAGFKAFsABysDRAA6XX0jM0sbfDsAMX80B1R5RUJlQjVNHT1DEwtrWy4ASWIjQggTAB4WAEZGVBYAJg6WAGYNVAdcgHlw5HxQ/AAvQ00sckQAN3wDNHiypMrUmrqiRuMRbwyIZAqbCBZqcKQ+1AAZK3drVUQABSMpiaXECDjSxIhCJRQwCVoAGmwXUhfU4mC4EK40K4sNyrkK7mK3iQaGMYUi0QMQkezysrw+k1S+B+fw2gPxIIM8Dp5WSVQA6qlggzCSdcTzQdh2gjUAAyUXMgGs7Z2TnIbnA3mZVB4xWCnpIsUSuAsrYpWVcoEEwx8lUConYO4o3KDSQ4s1qon8EmqF7vT5Umn/BImI2M+DGRDmIbC9rigNBoYanrhnVSvUcw3m2rIeoHB3Gi1WvqSEhHeBAA==)

## Techniques available to us
 
1. Flatten and inline
2. RCG: Use reflective code generation internally in FSharp.Core
3. KFS: Rely on known semantics of F# structural types and treat those as special
4. TS: Hand-code type-specializations using static optimization conditions in FSharp.Core
5. TT: Type-indexed tables of baked (poss by reflection) equality comparers and functions, where some pre-computation is done 
6. DV: De-virtualization
7. DEQ: Use `EqualityComparer<'T>.Default` where possible

## Notes on previous attempts to improve things

### [#5112](https://github.com/dotnet/fsharp/pull/5112)

* Uses TT, DEQ, KFS, DV
* Focuses on solving Problem4
* 99% not breaking, apart from the case of value types with custom equality implemented differently than the `EqualityComparer.Default` - the change would lead to the usage of the custom implementation which is reasonable

Note: this included [changes to the optimizer to reduce GenericEqualityIntrinsic](https://github.com/dotnet/fsharp/pull/5112/files#diff-be48dbef2f0baca27a783ac4a31ec0aedb2704c7f42ea3a2b8228513f9904cfbR2360-R2363) down to a type-indexed table lookup fetching an `IEqualityComparer` and calling it. These hand-coded reductions appear unnecessary as the reduction doesn't open up any further optimizations.

---

### Optimizations (`docs/optimizations.md`)

---
title: Optimizations
category: Compiler Internals
categoryindex: 200
index: 400
---
# Code Optimizations

Code optimizations are in [`Compiler/Optimize/*`](https://github.com/dotnet/fsharp/tree/main/src/Compiler/Optimize).

Some of the optimizations performed in [`Optimizer.fs`](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Optimize/Optimizer.fs) are:

* Propagation of known values (constants, x = y, lambdas, tuples/records/union-cases of known values)
* Inlining of known lambda values
* Eliminating unused bindings
* Eliminating sequential code when there is no side-effect
* Eliminating switches when we determine definite success or failure of pattern matching
* Eliminating getting fields from an immutable record/tuple/union-case of known value
* Expansion of tuple bindings "let v = (x1,...x3)" to avoid allocations if it's not used as a first class value
* Splitting large functions into multiple methods, especially at match cases, to avoid massive methods that take a long time to JIT
* Removing tailcalls when it is determined that no code in the transitive closure does a tailcall nor recurses

In [`DetupleArgs.fs`](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Optimize/DetupleArgs.fs), tuples at call sites are eliminated if possible. Concretely, functions that accept a tuple at all call sites are replaced by functions that accept each of the tuple's arguments individually. This may require inlining to activate.

Considering the following example:

```fsharp
let max3 t =
    let (x, y, z) = t
    max x (max y z)

max3 (1, 2, 3)
```

The `max3` function gets rewritten to simply accept three arguments, and depending on how it gets called it will either get inlined at the call site or called with 3 arguments instead of a new tuple. In either case, the tuple allocation is eliminated.

However, sometimes this optimization is not applied unless a function is marked `inline`. Consider a more complicated case:

```fsharp
let rec runWithTuple t offset times =
    let offsetValues x y z offset =
        (x + offset, y + offset, z + offset)
    if times <= 0 then
        t
    else
        let (x, y, z) = t
        let r = offsetValues x y z offset
        runWithTuple r offset (times - 1)
```

The inner function `offsetValues` will allocate a new tuple when called. However, if `offsetValues` is marked as `inline` then it will no longer allocate a tuple.

Currently, these optimizations are not applied to `struct` tuples or explicit `ValueTuple`s passed to a function. In most cases, this doesn't matter because the handling of `ValueTuple` is well-optimized and may be erased at runtime. However, in the previous `runWithTuple` function, the overhead of allocating a `ValueTuple` each call ends up being higher than the previous example with `inline` applied to `offsetValues`. This may be addressed in the future.

In [`InnerLambdasToTopLevelFuncs.fs`](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Optimize/InnerLambdasToTopLevelFuncs.fs), inner functions and lambdas are analyzed and, if possible, rewritten into separate methods that do not require an `FSharpFunc` allocation.

Consider the following implementation of `sumBy` on an F# list:

```fsharp
let sumBy f xs =
    let rec loop xs acc =
        match xs with
        | [] -> acc
        | x :: t -> loop t (f x + acc)
    loop xs 0
```

The inner `loop` function is emitted as a separate static method named `loop@2` and incurs no overhead involved with allocating an `FSharpFunc` at runtime.

In [`LowerCalls.fs`](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Optimize/LowerCalls.fs): 

* Performs eta-expansion on under-applied values applied to lambda expressions and does a beta-reduction to bind any known arguments

In [`LowerSequences.fs`](https://github.com/dotnet/fsharp/blob/main/src/Compiler/Optimize/LowerSequences.fs): 

* Analyzes a sequence expression and translates it into a state machine so that operating on sequences doesn't incur significant closure overhead

### Potential future optimizations: Better Inlining

Consider the following example:

```fsharp
let inline f k = (fun x -> k (x + 1))
let inline g k = (fun x -> k (x + 2))

let res = (f << g) id 1 // 4
```

Intermediate values that inherit from `FSharpFunc` are allocated at the call set of `res` to support function composition, even if the functions are marked as `inline`. Currently, if this overhead needs removal, you need to rewrite the code to be more like this:

```fsharp
let f x = x + 1
let g x = x + 2

let res = id 1 |> g |> f // 4
```

The downside of course being that the `id` function can't propagate to composed functions, meaning the code is now different despite yielding the same result.

More generally, any time a first-order function is passed as an argument to a second-order function, the first-order function is not inlined even if everything is marked as `inline`. This results in a performance penalty.

---

### Perf Discussions Archive (`docs/perf-discussions-archive.md`)

---
title: Perf discussions archive
category: Compiler Internals
categoryindex: 200
index: 970
---
This is just a typed version of [these notes](https://github.com/dotnet/fsharp/issues/16498), generated during perf discussions on summer of 2023. Can be used as a reference point.

---

# Comparisons
- OCaml
- Scala
- C#
- Rust

# Underlying problems
- LOH allocations
- Build does too much for deltas
- GC Gen 2

# Major problems
- Script start up CLI
- Build on Linux & Mac
- Glitches in test discovery
- Edit & test workflow
- Edit & run workflow
- Edit & check workflow
- Unnecessary rebuilds

# Hosted compiler problems
- Stamp overflow
- Non-deterministic
- Memory leaks
- Infinite loops
- Stay resident compiler
- Permission elevation

# Minor problems
- Benchmarking
- Squigglies
- Colorization
- Debug
- Press dot

# Incremental phases
- Incremental parsing file
- Incremental checking file
- Incremental optimization for deltas
- Cascading DLL builds
- Incremental DLL builds
- Incremental ILxGen for deltas
- Incremental assembly generation
- Incremental PDB generation for deltas

# Community guidelines
- Community leadership
- Community enablement for tool performance
- Performance acceptance criteria
- Performance running
- Docs for tooling performance

---

### Project Builds (`docs/project-builds.md`)

---
title: Project builds
category: Language Service Internals
categoryindex: 300
index: 600
---
# Project builds

The compiler is generally built to compile one assembly: the assumption that the compiler is compiling one assembly is baked into several aspects of the design of the Typed Tree. In contrast, FCS supports compiling a graph of projects, each for a different assembly, each undergoing incremental change.

Project builds are currently stateful and reliant on I/O to on-disk assemblies. This causes many problems and we have a plan for how to fix this core issue in the F# Language Service implementation. See [Plan: Modernizing F# Analysis](https://github.com/dotnet/fsharp/issues/11976). Please read this carefully if you plan to work on any of service.fs, IncrementalBuild.fs or FSharpCheckerResults.fs.

Key data structures:

* `IncrementalBuilder`  manages an incremental build graph for the build of an F# project.
  * See also [Plan: Modernizing F# Analysis](https://github.com/dotnet/fsharp/issues/11976) for how this will evolve to `FSharpProject`.

* `FSharpParseFileResults` -  represents the enrichment (e.g. breakpoint validation) available from the parse tree of a file. The enrichment is made up of several pieces such as:
  * `SyntaxTree`/`ParsedInput`
  * diagnostics from the parsing

* `FSharpCheckFileResults` -  represents the enrichment (e.g. tooltips) available after checking a file. The enrichment is made up of several pieces such as
  * `TcGlobals` - the globals for the compilation, also used in command-line build
  * `TcConfig` - the compiler configuration for the compilation, also used in command-line build
  * `TcImports` - the table of imports for the compilation
  * `CcuThunk` - the thunk of the assembly being compiled
  * `TcState` - the state of the compilation up to this point
  * `TcResolutions` - name environments across the file, ultimately from NameResolution.fs
  * `TcSymbolUses` - resolutions of symbols across the file
  * `LoadClosure` - the `#load` closure of a script
  * `TypedImplFile` - the TAST expression results of compilation, may be thrown away if `keepAssemblyContents` is not true

* `FSharpCheckProjectResults` -  represents the enrichment (e.g. find-all symbol uses) available after checking a project
  * `TcGlobals` - the globals for the compilation, also used in command-line build
  * `TcConfig` - the compiler configuration for the compilation, also used in command-line build
  * `TcImports` - the table of imports for the compilation
  * `CcuThunk` - the thunk of the assembly being compiled
  * `TcState` - the final state of the compilation

## Multi-project builds and cross-project references

In FCS, there is no single abstraction for a "solution build" and instead you have multiple project builds. These are all essentially independent, in the sense they each logically represent an invocation of the F# compiler. That is, the Typed Tree (TAST), TcState etc. nodes are **not** shared between different project compilations. 

If you want to understand why this invariant is important, some key things to understand are:

* The `RawFSharpAssemblyData` is the data blob that would normally be stuffed in the F# resource in the generated DLL  in a normal compilation. That's the "output" of checking each project.

* This is used as "input" for the assembly reference of each consuming project (instead of an on-disk DLL)

* Within each consuming project that blob is then resurrected to Typed Tree nodes in `TypedTreePickle.fs`.

Could the compiler share this data across projects? In theory, yes. In practice, it's very tricky business. From a correctness point of view: the process of generating this blob (TypedTreePickle `p_XYZ`) and resurrecting it (TypedTreePickle `u_*`) does some transformations to the Typed Tree that are necessary for correctness of compilation, for example, [in `TypedTreePickle`](https://github.com/dotnet/fsharp/blob/main/src/Compiler/TypedTree/TypedTreePickle.fs#L738). Basically, the Typed Tree nodes from the compilation of one assembly are _not_ valid when compiling a different assembly.

The Typed Tree nodes include `CcuData` nodes, which have access to a number of callbacks into the `TcImports` compilation context for the assembly being compiled. TypedTree nodes are effectively tied to a particular compilation of a particular assembly due to this. There isn't any way to share this data without losing correctness and invalidating many invariants held in the current design. From a lifetime point of view: the Typed Tree nodes are tied together in a graph, so sharing one or two of them might drag across the entire graph and extend lifetimes of that graph. None of these interrelated nodes were designed to be shared across assemblies.

---

### Reflectionfree Printing (`docs/reflectionfree-printing.md`)

---
title: Reflection-free printing
category: FSharp.Core
categoryindex: 500
index: 200
---
# Simple vs Reflection-based DU and Record printing

This document describes two modes for printing Discriminated Unions (DUs) and Records in F#: a **simple** reflection-free mode that delegates to a `string`-like operator for printing field values, and a `sprintf` mode (`sprintf "%A"`), which uses **reflection** to create output looking like F# code. In this document, the terms *simple* and *reflection* are used to distinguish the two modes.

Without the `--reflectionfree` flag, the compiler generates a `ToString` for DUs and Records that calls `sprintf "%A"`. With the flag, the compiler generates a `ToString` that uses the simple mode.

Users can choose between the two modes by 1. use of `--reflectionfree`, and by 2. calling with a `sprintf`-type caller or a `string`-type caller (e.g. the `string` operator, `ToString`, or interpolated strings).

If `x` is a DU or Record, then output will be simple or reflection-based as follows:
| | `--reflectionfree` | no `--reflectionfree` |
|---|---|---|
| `string x` | simple | reflection |
| `x.ToString()` | simple | reflection |
| `$"{x}"` | simple | reflection |
| `sprintf "%A" x` | disallowed (would be reflection) | reflection |

As such, the current default reflection `ToString` generation forces reflection formatting on all callers. On the other hand, generating simple `ToString` output means that the records and DUs are printed with simple or reflection formatting depending on whether the caller is of simple or reflection affinity. The `--reflectionfree` flag combines this property with a ban on `sprintf` to prevent the reflection mode from being used.

In addition to user-defined types, the FSharp.Core `option` type uses simple printing, while other types either have no `ToString` or use some other format.

## Behaviour: definitions

In simple printing, field values are printed with `string`-type formatting, more precisely `anyToStringShowingNull`. No line breaks are inserted.

- **Record**: `{ Name1 = value1; Name2 = value2 }`.
- **Anonymous record**: the same, but with `{| ` and ` |}`.
- **Union**: A case with no fields renders as just its name. A case with fields renders as `CaseName(value1, value2)`.

`[<Struct>]` records and unions, and struct anonymous records, render identically to their reference-type forms.

A type that supplies its own `ToString` override keeps it, with no `ToString` generated for it (either simple or reflection).

Reflection-mode printing is described in [plain text formatting](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/plaintext-formatting).

## Behavioural differences

### Differences in field rendering

The following differences between `string` and `sprintf "%A"` carry over directly into differences in field rendering between simple and reflection printing:

| F# value | simple (`anyToStringShowingNull`) | reflection (`sprintf "%A"`) |
|---|---|---|
| string field `"hi"` | `hi` | `"hi"` |
| char field `'a'` | `a` | `'a'` |
| float `5.0` | `5` | `5.0` |
| `250uy` / `42n` / `1.5M` | `250` / `42` / `1.5` | `250uy` / `42n` / `1.5M` |
| option field `None` | `null` | `None` |
| array field `[\|1;2;3\|]` | `System.Int32[]` | `[\|1; 2; 3\|]` |
| unit field `()` | `null` | `()` |

The overall differences here are:
- Simple printing converts to strings, while reflection printing is more bi-directional, often generating compilable F# code.
- F# types that have null representation (`unit`, `option`, and in general types with `AllowNullLiteral` or `UseNullAsTrueValue`) are printed as `null` in simple printing, while reflection printing uses a more F#-like representation.

### Other differences

These differences are in the printing of the record or DU itself rather than of its fields:

| F# value | simple (`string`) | reflection (`sprintf "%A"`) |
|---|---|---|
| `B 5` (single field) | `B(5)` | `B 5` |
| `C (3, 4)` (two fields) | `C(3, 4)` | `C (3, 4)` |
| record `{ X = 1; Y = 2 }` | `{ X = 1; Y = 2 }` | `{ X = 1`โŽ`  Y = 2 }` |
| `[<StructuredFormatDisplay("Custom<{X}>")>]` | `{ X = 5 }` | `Custom<5>` |

The overall differences here are:
- Simple printing always brackets a case's fields and never pads, while reflection printing omits brackets for a single non-tuple field and inserts a space before them otherwise.
- Simple printing uses a single line (unless a field's own rendering contains breaks), while reflection printing breaks records and nested values across lines with indentation.
- `StructuredFormatDisplay` is ignored in simple printing and honoured in reflection printing.

## Recursion and depth

Rendering recurses into nested records and unions. Deep nesting is guarded by `RuntimeHelpers.EnsureSufficientExecutionStack`, raising a catchable `InsufficientExecutionStackException` rather than `StackOverflowException`; cycles (which require mutation to construct) still overflow, as `option` and `list` do.

---

### Regression Testing Pipeline (`docs/regression-testing-pipeline.md`)

---
title: Regression testing pipeline
category: Compiler Internals
categoryindex: 200
index: 950
---
# F# Compiler Regression Testing

This document describes the F# compiler regression testing functionality implemented as a reusable Azure DevOps template in `eng/templates/regression-test-jobs.yml` and integrated into the main PR pipeline (`azure-pipelines-PR.yml`).

## Purpose

The regression testing helps catch F# compiler regressions by building popular third-party F# libraries with the freshly built compiler from this repository. This provides early detection of breaking changes that might affect real-world F# projects.

## How It Works

### Integration with PR Pipeline

The regression tests are automatically run as part of every PR build, depending on the `EndToEndBuildTests` job for the F# compiler artifacts.

### Template-Based Architecture

The regression testing logic is implemented as a reusable Azure DevOps template that can be consumed by multiple pipelines:

- **Template Location**: `eng/templates/regression-test-jobs.yml`
- **Integration**: Called from `azure-pipelines-PR.yml` 
- **Dependencies**: Depends on `EndToEndBuildTests` job for compiler artifacts

### Workflow

1. **Build F# Compiler**: The `EndToEndBuildTests` job builds the F# compiler and publishes required artifacts
2. **Matrix Execution**: For each library in the test matrix (running in parallel):
   - Checkout the third-party repository at a specific commit
   - Pin `global.json` to the exact SDK that built the local compiler
   - Inject `UseLocalCompiler.Directory.Build.props` via `CustomAfterDirectoryBuildProps`
   - Build the library using its standard build script
   - Publish MSBuild binary logs for analysis
3. **Report Results**: Success/failure status is reported with build logs for diagnosis

### Key Features

- **Reproducible Testing**: Uses specific commit SHAs for third-party libraries to ensure consistent results
- **Matrix Configuration**: Supports testing multiple libraries with different build requirements
- **Detailed Logging**: Captures comprehensive build logs, binary logs, and environment information
- **Artifact Publishing**: Publishes build outputs for analysis when builds fail

## Current Test Matrix

The pipeline currently tests against:

| Library | Repository | Commit | Build Script | Purpose |
|---------|------------|--------|--------------|---------|
| FSharpPlus | fsprojects/FSharpPlus | f614035b75922aba41ed6a36c2fc986a2171d2b8 | build.cmd | Tests advanced F# language features |

## Adding New Libraries

To add a new library to the test matrix, update the template invocation in `azure-pipelines-PR.yml`:

```yaml
# F# Compiler Regression Tests using third-party libraries
- template: /eng/templates/regression-test-jobs.yml
  parameters:
    testMatrix:
    - repo: fsprojects/FSharpPlus
      commit: f614035b75922aba41ed6a36c2fc986a2171d2b8
      buildScript: build.cmd
      displayName: FSharpPlus
    - repo: your-org/your-library    # Add your library here
      commit: abc123def456...         # Specific commit SHA
      buildScript: build.sh           # Build script (build.cmd, build.sh, etc.)
      displayName: YourLibrary        # Human-readable name
```

Each test matrix entry requires:
- **repo**: GitHub repository in `owner/name` format
- **commit**: Specific commit SHA for reproducible results
- **buildScript**: Build command to execute โ€” a `dotnet ...` command or a script file (`build.cmd`/`build.sh`); `;;` separates commands run sequentially, fail-fast
- **displayName**: Human-readable name for the job
- **expectLocalCore** (optional): set `true` when the repo has projects that take the implicit `FSharp.Core`; the job then fails unless the locally built FSharp.Core is actually restored โ€” a tripwire for a silently broken shim

## Pipeline Configuration

### Triggers

Regression tests run automatically as part of PR builds when:
- **PR Pipeline**: Triggered by pull requests to main branches  
- **Dependencies**: Runs after `EndToEndBuildTests` completes successfully
- **Parallel Execution**: Each repository in the test matrix runs as a separate job in parallel

### Build Environment

- **OS**: Windows by default (`$(WindowsMachineQueueName)`); matrix entries can override to Linux. The scripts are OS-agnostic
- **Pool**: Standard public build pool (`$(DncEngPublicBuildPool)`)
- **Timeout**: 120 minutes per regression test job
- **.NET SDK**: Each test repo's `global.json` is pinned to the SDK that built the local compiler, so `fsc.dll` and its host runtime line up

### Artifacts

The regression tests publish focused artifacts for analysis:
- **FSharpCompilerArtifacts**: F# compiler build output (from `EndToEndBuildTests`)
- **UseLocalCompilerProps**: Configuration file for using local compiler (from `EndToEndBuildTests`)
- **{LibraryName}_BinaryLogs**: MSBuild binary logs from each tested library for efficient diagnosis

## Troubleshooting Build Failures

When a regression test fails:

1. **Check the Job Summary**: Look at the final status report for high-level information.

2. **Download Build Logs**: Download the published artifacts to examine detailed build output.

3. **Compare Compiler Changes**: Review what changes were made to the compiler that might affect the failing library.

4. **Local Reproduction**: Use the `UseLocalCompiler.Directory.Build.props` file to reproduce the issue locally.

### Local Testing

To reproduce a regression locally, on any OS, without editing the library:

1. Build the compiler and pack FSharp.Core in your `dotnet/fsharp` checkout: `./build.sh -c Release -pack` (`Build.cmd` on Windows).
2. Clone the library at the failing commit and build it against your local build:
   ```
   git clone --recursive https://github.com/<owner>/<repo>.git TestRepo
   cd TestRepo && git checkout <commit>
   # If TestRepo's global.json pins a different SDK, align sdk.version with <fsharp-repo>/global.json
   # (allowPrerelease: true, rollForward: disable) โ€” the clone is disposable, as in CI.
   dotnet fsi <fsharp-repo>/eng/scripts/BuildWithLocalFSharp.fsx --build-script '<the repo build command>'
   ```

`BuildWithLocalFSharp.fsx` runs the same command CI runs, from the current directory, without touching the repo's sources. Add `--verify` to fail unless every project consumes the local FSharp.Core; the script header lists the other options. Because the local package keeps a fixed `-dev` version, the script evicts it from the global NuGet cache before each run so a rebuild is never served stale; pass `--nuget-packages <dir>` to use an isolated cache when running several builds concurrently or against a repo that redirects its packages folder.

## Best Practices

### For Library Selection

- **Coverage**: Choose libraries that exercise different F# language features
- **Popularity**: Include widely-used libraries that represent real-world usage
- **Stability**: Use libraries with stable build processes and minimal external dependencies
- **Diversity**: Include libraries with different build systems and target frameworks

### For Maintenance

- **Regular Updates**: Periodically update commit SHAs to newer stable versions
- **Monitor Dependencies**: Watch for changes in third-party library build requirements
- **Baseline Management**: Update baselines when intentional breaking changes are made

## Technical Details

### UseLocalCompiler.Directory.Build.props

This MSBuild props file redirects projects to the locally built F# compiler (and, for the matrix, the locally built FSharp.Core) instead of the SDK version. It is organised into gates so it can be injected into unmodified repos as well as imported directly by in-repo tests โ€” see the `Gate 1/2/3` comments in the file. Its companion `UseLocalCompiler.Directory.Build.targets` is injected via `CustomAfterDirectoryBuildTargets` (after the target repo's project body) so the local FSharp.Core version wins over the repo's own reference, whether implicit, an explicit `PackageReference Include`, a `PackageReference Update`, or a central `PackageVersion` (Central Package Management).

## Future Enhancements

Potential improvements to the pipeline:

1. **Performance Testing**: Measure compilation times and memory usage
2. **Multiple Target Frameworks**: Test libraries across different .NET versions
3. **Parallel Execution**: Run library tests in parallel for faster feedback
4. **Automatic Bisection**: Automatically identify which commit introduced a regression
5. **Integration with GitHub**: Post regression test results as PR comments

---

### Representations (`docs/representations.md`)

---
title: Representations
category: Compiler Internals
categoryindex: 200
index: 350
---
# Representation Decisions in the F# Compiler

Consider the following declarations, all of which look very similar.

```fsharp
module M = 
    let z = 1                   
    let f = x + z               
                                

type C(w: int, z: int) =        
                                
    let f x = x + z             
    let f x = f 3 + x           
                                

let g (z: int) =                
    let f x = x + 1             
```
Part of the job of the F# compiler is to "decide" how these declarations are compiled. The following acts as a guide to how these different bindings are represented and where these decisions are made.

First for module-level `let` bindings. These representations are decided by code in `CheckExpressions.fs` and `CheckDeclarations.fs` based on syntax. 

```fsharp
module M = 
    let z = 1              // z --> static property + field, required by spec, compiled name mandated
    let f x = x + z          // f --> static method, required by spec, compiled name mandated
```

Next for class-level `let` bindings.  These representations are decided by code in `CheckIncrementalClasses.fs` based on analysis of use. 
```fsharp
//    Decided in CheckIncrementalClasses.fs based on analysis of use
type C(w: int, z: int) =   // w --> local to object constructor, required by spec
                           // z --> private instance field, required by spec
    let f x = x + z        // f --> private instance method, required by spec, compiled name not mandated
                           // Note: initially uses an ephemeral 'f' Val then creates a member Val with compiled name
                           
    let f x = f 3 + x      // f --> private instance method, required by spec, compiled name not mandated
                           // Note: initially uses an ephemeral 'f' Val then creates a member Val with compiled name
                           
    static let g x = x + 1 // g --> private static method, required by spec, compiled name not mandated, initially uses an ephemeral 'g' Val then creates a member Val with compiled name
    
    static let g x = g 3   // g --> private static method, required by spec, compiled name not mandated, initially uses an ephemeral 'g' Val then creates a member Val with compiled name
```
Next for expression-level `let` bindings.  These representations are decided by code in various optimization phases.
```fsharp
let g (z: int) =          // z --> local + field in closure for 'f', not mandated
    let f x = x + 1       // f --> FSharpFunc value, or maybe a static method, not mandated 
                          //    Decided in various optimization phases
```

> NOTE: The representation decision is implied by the addition of ValReprInfo to the `Val` node.

---

### Reusing Typechecking Results (`docs/reusing-typechecking-results.md`)

---
title: Reusing typechecking results
category: Compiler Internals
categoryindex: 200
index: 42
---

# Reusing typechecking results between compiler runs

Caching and reusing compilation results between compiler runs will be an optimization technique aimed at improving the performance and efficiency of the F# development experience.

## Motivation

- **Performance**. Things like recomputing type information for large projects are time-consuming. By caching the results, subsequent compilation can skip some steps, leading to faster builds.

- **Better IDE cold start**. IDEs can provide faster IntelliSense, code navigation, and other features if they can quickly access cached compilation information. The bigger and the less coupled the project is, the greater will be the gains.

### Example real-world scenarios

The optimization will benefit the most in large and not very interdependent projects. For instance, a 100-files test project where a single test is changed. MSBuild will mark the project for recompilation but thanks to the improvement only one file would be recompiled in the full-blown manner.

## Premises

Here are some assumptions I am coming with after tinkering with the topic and investigating the current state of the art. 

### Premise 1: current compiler design

The heart of the compiler, [fsc.fs](../src/Compiler/Driver/fsc.fs), is split into 6 phases (`main1` - `main6`). The code is designed to pass minimum information between phases, using the `Args` structure, which is essentially a data bag. The first phase takes info from the program arguments.

```fsharp
main1 (...args...)
|> main2
|> main3
|> main4 (tcImportsCapture, dynamicAssemblyCreator)
|> main5
|> main6 dynamicAssemblyCreator
```

### Premise 2: current compilation time consumption

Let's measure (`fsc --times`) the fresh compilation of large F# files. We'll use examples from our benchmarks - those are different but should be good to see general trends.

**SomethingToCompile.fs**
```
/* Detailed source-code truncated for AI context efficiency. */
```

**decentlySizedStandAloneFile.fs**
```
/* Detailed source-code truncated for AI context efficiency. */
```

**CE100xnest5.fs**
```
/* Detailed source-code truncated for AI context efficiency. */
```

The phases taking the longest are marked in the right. Those are assembly import, type check, optimizations and IL writing.

Optimizations are not relevant in the dev loop with run/debug/test cycles so we won't take those into account.

## Experiment: force-generated signature files efficiency

In the initial discussions, we decided to make an experiment to see if force-generating and caching signature files in a project saves much time during recompilations.

My setup was a project of 25 independent big files (copies of `Utilities.fs`).
There, I measured (few times, with cleaning artifacts, using `--times`):
1. Typechecking - **time1**
2. Typechecking + all signature generation (`--allsigs`) - **time2**
3. Typechecking with signatures generated (left from previous run and added to the project) - **time3**
4. Typechecking with partial signature usage and generation (20 sigs used, 5 generated) - **time4**

I picked a slow machine to better see the differences. But _all the differences_ were largely **marginal** (including between **time1** and **time3**). I can imagine squeezing stable single-digit % performance differences in a cleaner experiment - that said:
- It's not clear if it's going to be for better or for worse, since the signature generation penalty can be bigger than the benefit of having them.
- Even if it's for better, such a small improvement won't be worse the hassle.

Therefore, I think it's not the way to go here.



Numbers, for completeness

```
Normal compilation:
1. Typecheck: 23.7531
2. Typecheck: 27.4234
3. Typecheck: 24.5202

Normal compilation + generating signatures (added extra `ReportTime` for measuring the latter):
1. Typecheck: 26.5991, Siggen: 1.9369
2. Typecheck: 25.0246, Siggen: 1.7517
3. Typecheck: 27.0057, Siggen: 1.9093

Compilation with given signatures:
1. Typecheck: 25.7741
2. Typecheck: 26.2904
3. Typecheck: 24.3852

Compilation with given 80% signatures + generating 20% signatures:
1. Typecheck: 24.1338, Siggen: 0.5284
2. Typecheck: 27.6037, Siggen: 0.7526
3. Typecheck: 25.6967, Siggen: 0.6244
```




## Implementation plan

The conclusion from the above is that there is a lot of potential in caching - at the same time, implementing things to the full potential right away will require serious changes in the compiler code, which will be dangerous, hard to test and review. Therefore, I propose to implement the feature in stages where each stage brings gains in some scenarios.

**Stage 1 - force-gen and compare typechecking graphs**

We already create the TC graph in `main1` and we are able to dump it (in Mermaid) via the compiler flags.

That means we can force-gen and save the graph which will allow us to skip retypechecking if we detect that the graph is not changed. We should also track all the compilation information (the argument string) and the last update times of the files.

This step won't bring big observable benefits, yet it will create necessary MSBuild hooks to communicate the intermediate files folder towards the compiler, add time-based and hash-based cache invalidation logic, and create testing rails which will include the `clean` and `rebuild` tests to make sure the cache is easily invalidated on demand.

**Stage 2 - skip retypechecking for some files**

In `main1`, we do unpickling (= deserializing) and pickling (= serializing) of the code. This currently happens on the module/namespace basis. 

In this stage, we will fully implement pickling and unpickling of all typechecked files (`CheckedImplFile` per file as well as all other outputs of `main1` which cannot be cheaply recreated like `topAttrs` and `CcuThunk`). Parts of the pickling/unpickling can reuse the primitives already existing in `TypedTreePickle` and built upon them. So we can save acquired typechecking information to the intermediate folder after the typechecking - and for the files not needing retypechecking as per graph diff, skip the phase, instead restoring the typechecking info. We also need to identify the side effects happening during typechecking and probably replay them in such cases.

This is likely the biggest amount of work expected but it will hugely benefit the scenarios when only the edge of the graph is affected (think one test in a test project).

**Stage 3 - reduce signature data generation**

In `main3`, we encode signature data in the assemblies.

Since we are addressing run/debug/test scenarios, a lot of projects can avoid signature data at all, because signature data is only needed for cross-project compilation. This could be detected automatically for certain types of projects (think web apps, console apps, test projects).

For libraries, the signature information resource blob (a byte array) would have to be split into per-file data and we'll and logic to recombine the splits with freshly typechecked information into a single binary resource back.

**Stage 4 - reuse import data**

In `main1`, we restore imported assemblies to get their IL (e.g. system assemblies).

So here, we can serialize and cache imported IL - we'll need to evaluate and benchmark if per assembly or per assembly block (cases like  System.*.dll). We'll apply this for the cross-project case, to not reimport IL for repeating assemblies but instead restore it from the cache - by adding a cross-project intermediate file location to be coordinated with MSBuild properties.

This will be a smaller gain for any particular project but a big accumulated one for large multi-project solutions.

## Testing and benchmarking

We should have a compiler switch for this: applying it to current typechecking tests shouldn't make any difference in results. Tests should test that restored cached results + reusing them should be equivalent to fresh typechecking results.

Benchmarks should be added or run at every stage to the `FCSBenchmarks` project. A good inspiration can be workflow-style tests for updating files done by @0101 in Transparent Compiler.

---

### Running Documentation Locally (`docs/running-documentation-locally.md`)

---
title: Running the documentation locally
category: Compiler Internals
categoryindex: 200
index: 999
---
# Running the documentation locally

The source of this documentation website is hosted on https://github.com/fsharp/fsharp-compiler-docs.  
You can follow this guide to see the results of your document changes rendered in the browser.

## Setup

`fsharp/fsharp-compiler-docs` is driven by a [Fun.Build](https://github.com/slaveOftime/Fun.Build) script, `build.fsx`.
By default it clones `dotnet/fsharp` into a git-ignored `fsharp` folder, builds `FSharp.Compiler.Service` and
generates the site from that clone. To work on the `docs` of your own `dotnet/fsharp` checkout instead, point the
`FSHARP_REPO` environment variable at it: the clone is skipped and your checkout is built and read in place.

* Clone `fsharp/fsharp-compiler-docs`:


    git clone https://github.com/fsharp/fsharp-compiler-docs.git
    cd fsharp-compiler-docs


* Run the `Build` pipeline once. It builds `FSharp.Compiler.Service.slnx` with the SDK your checkout pins (using
  the `eng/common/dotnet.sh` bootstrap, so that SDK does not need to be on your `PATH`), restores the `fsdocs` tool
  and generates the site into `output/`:


    FSHARP_REPO=/path/to/your/fsharp dotnet fsi build.fsx


* Then serve the docs with live reload:


    FSHARP_REPO=/path/to/your/fsharp dotnet fsi build.fsx -- -p Watch


Anything after the pipeline name is passed on to `fsdocs watch`, for example `-- -p Watch --nolaunch --port 8080`.
The `Watch` pipeline does not rebuild `FSharp.Compiler.Service`. Rerun `Build` after changing signature files or
XML documentation in `src/Compiler`.

## How watch works

`fsdocs watch` is a lazy development server: a page is built the first time it is requested and cached until a
file that influences it changes. Diagnostics, including which `FSharp.Compiler.Service.dll` is being documented,
are at `/.fsdocs/doctor` on the served site.

A page is rebuilt when the content of its own `.md` or `.fsx` file changes. Files a script pulls in through
`#load`, and files it reads while evaluating, are not tracked
([FSharp.Formatting#1309](https://github.com/fsprojects/FSharp.Formatting/issues/1309)). This affects the release
notes pages in `docs/release-notes`, which are composed from the MarkDown files in the hidden subfolders through
`.aux/Common.fsx`: editing those does not regenerate the served page. Make any edit to the `.fsx` of the page
(touching the file is not enough, the content has to change), or restart the watch.

Restart the watch as well after changing the package versions referenced from `.aux/Common.fsx`. The running F#
Interactive session keeps the assemblies it already loaded.

---

### Runtime Async (`docs/runtime-async.md`)

---
title: Runtime async
category: Compiler Internals
categoryindex: 200
index: 375
---

# Runtime async

This document describes the current proof-of-concept implementation of F#
support for the .NET runtime-async feature. It describes the code as
implemented, not an aspirational design. The .NET design is still evolving:

* [Runtime-async specification](https://github.com/dotnet/runtime/blob/main/docs/design/specs/runtime-async.md)
* [Runtime-async code-generation contract](https://github.com/dotnet/runtime/blob/main/docs/design/coreclr/botr/runtime-async-codegen.md)
* [Roslyn runtime async design](https://github.com/dotnet/roslyn/blob/main/docs/compilers/CSharp/Runtime%20Async%20Design.md) โ€”
  how C# lowers `await` (including the exception-handling hoisting described below)

The implementation targets functions, lambdas, and members returning
`System.Threading.Tasks.Task<'T>`, `Task`, `ValueTask<'T>`, or `ValueTask`.
Inline computation-expression builders can use the feature, but no such
builder is currently part of FSharp.Core.

## Runtime contract

Runtime-async methods are CIL methods marked with
`MethodImplOptions.Async` (`0x2000`). The runtime, rather than a compiler
generated state machine and method builder, owns suspension and resumption.

The compiler provides a return intrinsic for each of these carrier shapes:
generic and non-generic `Task`, and generic and non-generic `ValueTask`.

Suspension is explicit, via `System.Runtime.CompilerServices.AsyncHelpers`:

* `Await` for `Task`, `ValueTask`, and configured awaitables
* `AwaitAwaiter` and `UnsafeAwaitAwaiter` for awaiters (used by SRTP
  awaitable bindings)

The compiler emits the adjacent IL sequence the runtime specification expects:

```il
call Task<int32> SomeAsyncMethod(...)
call int32 AsyncHelpers::Await<int32>(Task<int32>)
```

In runtime-async bodies, the compiler emits `Task` upcasts as `castclass`
instructions, including before `ConfigureAwait`. These casts prevent the runtime
from incorrectly optimizing a generic task-producing call followed by a
non-generic await.

Known runtime restrictions (currently **not** diagnosed by the F# compiler):

* `tail.` and `localloc` are forbidden.
* generated suspension points cannot occur inside exception-handling regions.
  Awaiting in a protected `try` body now works on the current runtime. Direct
  intrinsic bodies rewrite suspending `try/with` handlers and filters, and
  `try/finally` compensations, so the suspension runs outside the EH region.

  C# avoids this by rewriting EH-region awaits at lowering time (see the
  Roslyn design doc): `try B finally { await x }` becomes
  `try B catch-all { pend e }`, then `await x` outside the region, then
  rethrow the pending exception. The compiler applies the same transformation
  to a suspending compensation: it captures the body result or exception,
  runs `DisposeAsync` (possibly suspending) *outside* the `try`, then restores
  the pending exception. This makes `use` on an `IAsyncDisposable` work under
  runtime async.
Byref, byref-like, and pinned locals that are used after a suspension are
rejected with diagnostic FS3917.

Calls to `AsyncHelpers` suspension methods emitted outside a runtime-async
method are rejected during code generation. Explicitly `inline` method bodies
are treated as templates and checked at their eventual use site.

## F# surface

The source-level markers are compiler intrinsics on
`Microsoft.FSharp.Core.CompilerServices.StateMachineHelpers`, available from
the `net10.0` FSharp.Core target and declared in `resumable.fsi` alongside the
other compiler intrinsics:

```fsharp
val __runtimeAsyncReturn<'T> : 'T -> System.Threading.Tasks.Task<'T>
val __runtimeAsyncReturnValueTask<'T> : 'T -> System.Threading.Tasks.ValueTask<'T>
val __runtimeAsyncReturnUnit : unit -> System.Threading.Tasks.Task
val __runtimeAsyncReturnValueTaskUnit : unit -> System.Threading.Tasks.ValueTask
```

Their FSharp.Core implementations throw; the compiler consumes every
occurrence before code generation, so those bodies are never executed. They
are marked `NoInlining` so a missed consumption does not silently fold into a
caller.

The feature is gated on `langversion:preview`
(`LanguageFeature.RuntimeAsync`) and on the target reference assemblies
exposing `MethodImplOptions.Async` (see "Runtime capability check" below).
Without the language version the checker reports error 3350; without runtime
support it reports 3351.

Typical forms:

```fsharp
let add (x: int) (y: int) : Task<int> =
    __runtimeAsyncReturn (
        let first = AsyncHelpers.Await (getTask x)
        first + y)

type C() =
    member _.Add(x: int, y: int) : Task<int> =
        __runtimeAsyncReturn (
            AsyncHelpers.Await (getTask x) + y)

// Let-bound value (not a function): also supported.
let answer : Task<int> = __runtimeAsyncReturn 42
```

There is no implicit awaiting: the argument of a generic return marker is
checked as the logical `'T` result, and flattening requires an explicit
`AsyncHelpers.Await`.

## Type checking

The return intrinsics are ordinary values in the typed tree; no new expression
node or `Val` flag is added. Type checking special-cases their applications in
two places in `CheckExpressions.fs`:

* `Propagate` skips function-type propagation for the intrinsic so the
  argument is not checked against a function domain.
* `TcApplicationThen` (`tryTcRuntimeAsyncApplication`) recognises the
  intrinsic (possibly type-applied), gates the language feature and runtime
  capability, extracts the result carrier and argument type from the
  intrinsic's instantiated signature, and checks the argument with
  `TcExprFlex2`. The result carrier then unifies with the declared return
  type of the enclosing binding in the usual way.

User code that defines its own same-named marker is unaffected: the intrinsic is
only recognised when the `ValRef` resolves (via `valRefEq`) to the FSharp.Core
declaration.

## Optimization

`Optimizer.fs` preserves the marker application as-is, optimizing its
argument and rewriting any suspending exception handlers in that argument.
The marked expression is forced to `HasEffect = true` and `UnknownValue`, so
the optimizer never inlines, duplicates, or discards it. The marker therefore
survives optimization as an ordinary `Expr.App` node; nothing else in the
typed tree records that a method is runtime-async.

Inline values whose bodies contain a return marker or an `AsyncHelpers`
suspension are recursively specialized at their call sites, including when
optimization is disabled. The analysis follows inline and local values with a
cycle guard, and `InlineIfLambda` arguments are forced through when the caller
is already in a runtime-async context. The optimizer follows nested inline
calls and does not create a generated helper method for the specialized
suspension fragment, keeping every suspension in the eventual runtime-async
method.

After specialization, lambda arguments are substituted and their applications
are beta-reduced before and after runtime-async reoptimization. This includes
debug-point-wrapped lambdas, compiler-generated `let` wrappers, curried
applications, and multi-argument lambdas.
That step is required for computation-expression shapes where `Bind` returns a
closure containing `Await`, and later `Combine`/`Delay` calls apply that closure.

When runtime-async specialization is forced in a debug build, the builder
combinator is copied with its definition-site debug ranges remarked before
arguments are substituted. User continuation arguments keep their own ranges,
so `let!`, `do!`, `yield`, and other
computation-expression statements remain associated with the source that
authored them without exposing the implementation ranges of `Run`, `Bind`,
`Combine`, or `Yield`.

Dead branches eliminated by optimization do not reach code generation and do
not produce a suspension-outside-runtime-async diagnostic.

Runtime-async boundary recognition is centralized in
`TypedTree/RuntimeAsync.fs`. `IsRuntimeAsyncBoundary` (with the more specific
`TryGetRuntimeAsyncReturn` and `IsRuntimeAsyncSuspensionExpr`) lets consumers
use the shared recognizers rather than matching typed-tree shapes
independently.

The optimizer uses a context-local `RuntimeAsyncAnalyzer`. It memoizes
completed expression results by reference identity and inline-value results by
value stamp, with a visiting set for recursive inline-value graphs. The cache
is not global: optimizer environments can provide different inline bodies, and
optimization creates new expression trees. Context-dependent decisions such as
`runtimeAsyncContext` remain outside the cached facts.

## Code generation

`IlxGen.fs` recognises the return-marker family in three placements through the
shared runtime-async boundary contract, which strips `DebugPoint` wrappers:

1. **Method body** (`GenMethodForBinding`): the marker is unwrapped from the
   top of the method lambda body; the generated `ILMethodDef` gets
   `.WithAsync(true)`, which sets impl attribute bit `0x2000`
   (`MethodImplOptions.Async`, written as a literal because older reference
   assemblies do not define the enum member).
2. **Closure body** (`GenClosureAsLocalTypeFunction` and
   `GenClosureAsFirstClassFunction`): the same unwrapping marks the closure
   `Invoke` method's IL body (`ILMethodBody.IsRuntimeAsync`).
   `EraseClosures.convIlxClosureDef` copies that flag onto the emitted
   method.
3. **Any other expression position** (`GenRuntimeAsyncReturnAsStartedTask`), e.g.
   a `let`-bound value initializer: the marker application is wrapped in a
   fresh `fun () -> ...` lambda that is immediately applied to `unit` and
   regenerated. The lambda flows through the closure path (2), producing a
   generated runtime-async helper method whose call starts the task. This
   relies on `GenApp` never beta-reducing a lambda application (it always
   emits a closure plus an indirect call); see the comment at
   `GenRuntimeAsyncReturnAsStartedTask`.

A marker that ends up wrapped in anything other than `DebugPoint` at the top
of a method or closure body is not detected there, but still reaches the
catch-all case (3), so compilation stays correct โ€” the cost is an extra
nested runtime-async helper method rather than marking the enclosing method
directly.

## Debug stepping and call stacks

The compiler emits ordinary Portable PDB sequence points for runtime-async
methods. It does not emit `StateMachineMethod` or async state-machine stepping
records because runtime-async methods have no compiler-generated `MoveNext`
method. Forced inlining therefore preserves user computation-expression
sequence points in the generated runtime-async method while remapping the
inlined builder implementation ranges.

Suspension, continuation mapping, and reconstruction of logical async call
stacks are owned by the runtime and debugger through the `Async` method
implementation flag and the runtime-async debug information contract. Missing
logical frames after a continuation cannot be repaired by inventing F# state
machine metadata; such cases must be validated against the target runtime and
tracked with the runtime/debugger implementation.

Case (3) re-homes the marker argument into a compiler-synthesized closure
during code generation, *after* `LowerLocalMutables` has run. Without special
handling, mutable locals used both in that body and in the enclosing scope
would be copied into the closure by value, silently disconnecting the two
copies. `LowerLocalMutables` therefore treats the marker argument as a lambda
body (`DecideExpr`), promoting its free mutable locals to reference cells so
the synthesized closure and the enclosing scope share them.

`InvokeFast` is not a separate runtime-async path. It is the closure-erasure
shape for an indirect call with multiple arguments. Fragment substitution and
beta reduction happen before closure erasure; if a suspending fragment survives
until an indirect `InvokeFast` call, it is still outside a runtime-async method
and is rejected by code generation.

## Runtime capability check

`InfoReader` gates `LanguageFeature.RuntimeAsync` on the target reference
assemblies: it looks up the `Async` field on
`System.Runtime.CompilerServices.MethodImplOptions`. This is a metadata-only
probe of the *reference* assemblies; it does not prove the *executing* host
JIT supports runtime-async. Compiling against new reference assemblies and
running on an older runtime is not a supported configuration.

## Computation-expression usage

The feature is usable from an inline computation-expression builder. A
task-like builder can keep `Delay` and its other combinators synchronous and
inline; `Run` introduces the return marker:

```fsharp
type RuntimeTaskBuilder() =
    member inline _.Delay([<InlineIfLambda>] generator: unit -> 'T) = generator
    member inline _.Run([<InlineIfLambda>] code: unit -> 'T) =
        __runtimeAsyncReturn (code ())
    member inline _.Bind(task: Task<'T>, [<InlineIfLambda>] continuation: 'T -> 'U) =
        continuation (AsyncHelpers.Await task)
```

`Bind`, `ReturnFrom`, and `MergeSources` can use `Await` for known
`Task`/`ValueTask` types and SRTP awaiter operations for arbitrary task-like
values. `MergeSources` awaits its already-started sources sequentially.
`Async<'T>` can be adapted with `Async.StartImmediateAsTask`.

An async-sequence builder can use the same pattern to produce
`IAsyncEnumerable<'T>`. Its `Run` creates a producer that is started when
`GetAsyncEnumerator` is called. A `ManualResetValueTaskSourceCore` handshake
makes enumeration pull-driven: `yield` publishes one item and waits for the
next `MoveNextAsync` request. `yield!` and `for` can consume synchronous or
asynchronous enumerables, and nested async enumerables receive the caller's
cancellation token. A single active `MoveNextAsync` is enforced. A builder may
also hand off directly between compatible producers for `YieldFromFinal`,
avoiding a second enumeration handshake.

These builders are examples rather than FSharp.Core APIs. Applications can
define their own inline builders over the same intrinsics, subject to the
runtime-async restrictions and inline-fragment rules described above.

### Direct async-sequence proposal

`__runtimeAsyncSequence` consumes a statically known `unit -> seq<'T>` recipe. It reuses sequence lowering, but emits runtime-async `MoveNextAsync(): ValueTask` and `DisposeAsync(): ValueTask` methods on a reference type. Each resume checks the enumeration's cancellation token before running recipe code. User awaits remain in these methods. Yield positions persist between calls. Ordinary nested sequences remain synchronous.

Generated types implement the enumerator interfaces directly, without base-class forwarding. The first acquisition reuses the factory instance. Later acquisitions return independent, already-acquired clones. Immediately consumed runtime-async inputs remain adjacent to their awaits so the runtime can fuse the calls.

The [example builder](../tests/FSharp.Compiler.ComponentTests/Language/RuntimeAsync/RuntimeAsyncSequenceBuilder.fs) uses existing sequence combinators. Its `Bind` supports tasks, value tasks, and typed custom/configured awaiters. `For` and `YieldFrom` select synchronous or asynchronous enumeration through library overloads, not compiler syntax cases.

```fsharp
let values (work: Task<int>) (resource: IAsyncDisposable) =
    runtimeAsyncSeq {
        use cleanup = resource
        let! value = work
        for offset in [1; 2] do
            yield value + offset
        yield! [10]
        for value in runtimeAsyncSeq { yield 11 } do
            yield value
        yield! runtimeAsyncSeq { yield 12 }
    }
```

The reference builder uses the compiler-recognized `cancellationToken()` helper to pass the current enumeration token to nested `IAsyncEnumerable` sources. Recipes can call the same helper for explicit cancellation checks without taking a token parameter. The `withCancellation (fun token -> runtimeAsyncSeq { ... })` adapter remains available when the recipe needs to capture the enumeration token explicitly.

```fsharp
let tokens =
    runtimeAsyncSeq {
        let token = cancellationToken()
        token.ThrowIfCancellationRequested()
        yield token
    }
```

Recipes undergo mandatory local normalization even with `--optimize-`. This does not enable optimization for surrounding code. It can remove intermediate recipe locals. Body faults await active cleanup before rethrowing with their original dispatch information. Successful moves do not allocate an exception-transport object.

When cleanup can suspend, the saved exception lives on the iterator instead of enlarging every runtime continuation. The failure path clears it even if cleanup throws.

Builder-generated `MoveNextAsync` can lose visible sequence points. The optimized control has no visible points, and its nonoptimized form omits the terminal yield's range. Direct-intrinsic and ordinary-sequence controls retain their ranges. This proposal does not guarantee complete source stepping.

This is a proposal, not a drop-in TaskSeq replacement. Producer `try/with` is lowered through an ordinary nested sequence, while runtime-async suspensions inside that handler remain unsupported. Opaque recipes and optimized tail handoff are rejected. Early disposal retains ordinary sequence exception precedence: an outer cleanup failure replaces an inner cleanup failure. Concurrent move/dispose calls are unsupported. Awaiting a non-cancellable operation does not make it cancellable. Performance qualification must include genuinely pending operations, not only completed awaits.

Build and run the small [usage and lifecycle example](../tests/FSharp.Compiler.ComponentTests/Language/RuntimeAsync/RuntimeAsyncSequence.fs) with the matching preview SDK:

```sh
./build.sh -c Release
dotnet test --project tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj \
  -c Release --no-build --filter-class 'Language.RuntimeAsyncSequenceTests'
```

The tests compile the library and consumer together and separately, with optimization enabled and disabled.

### Unsupported inline-fragment positions

An inline fragment that escapes as a first-class value, is passed to a
non-inline function, or is dynamically dispatched cannot be preserved as a
runtime-async suspension fragment. If the suspension remains in the generated
non-runtime-async method, code generation reports FS3916 rather than emitting
an unsafe closure. Fragments in statically eliminated branches do not trigger
this diagnostic.

## Not yet implemented

* Complete diagnostics for runtime restrictions. The optimizer rewrites
  suspending `try/with` handlers and filters, and `try/finally` compensations,
  so they execute outside exception-handling regions. There is no general
  diagnostic for runtime-contract violations in other generated or imported
  shapes, and `localloc` has no dedicated diagnostic. Runtime-async methods
  suppress `tail.` emission rather than reporting it.
* A builder in FSharp.Core; builders using the feature are currently
  application/library code.
* Compile-time enforcement that the marker was actually consumed before
  code generation (a missed marker throws only when its FSharp.Core stub is
  reached at run time, or produces invalid IL as described above).

---

### Srtp Guide (`docs/srtp-guide.md`)

---
title: SRTP guide (draft)
category: Compiler Internals
categoryindex: 200
index: 980
status: draft
target: Microsoft Learn (F# language guide)
notes: >
  This document is a draft intended for eventual inclusion in the official
  F# documentation on Microsoft Learn. It lives here during development of
  RFC FS-1043 so reviewers can evaluate the guidance alongside the implementation.
---

# Guide to Writing SRTP Code in F#

This guide documents best practices for using Statically Resolved Type Parameters (SRTP) in F#, including the new extension constraint solutions feature (RFC FS-1043).

## What Are SRTPs?

Statically Resolved Type Parameters (SRTPs) allow you to write generic code that requires types to have specific members, resolved at compile time:

```fsharp
let inline add (x: ^T) (y: ^T) = x + y
```

The `^T` syntax (hat-type) declares a statically resolved type parameter. The use of `(+)` generates a member constraint that the compiler resolves at each call site based on the concrete type.

## Extension Constraint Solutions (Preview)

With `--langversion:preview`, extension methods now participate in SRTP constraint resolution.

### Defining Extension Operators

```fsharp
open System

type String with
    static member (*) (s: string, n: int) = String.replicate n s

let inline multiply (x: ^T) (n: int) = x * n
let result = multiply "ha" 3  // "hahaha"
```

### Tuple Type Extensions

Type extensions can be written directly on tuple types using tuple syntax. The tuple type is
rewritten to its underlying named type โ€” `System.Tuple<...>` for reference tuples and
`System.ValueTuple<...>` for struct tuples:

```fsharp
// Reference tuple: rewritten to System.Tuple<'T1, 'T2>
type ('T1 * 'T2) with
    static member inline (<*>) ((a, f), (b, x)) = (a, f x)

let r = (1, string) <*> (2, 3)   // (1, "3")

// Struct tuple: rewritten to System.ValueTuple<'T1, 'T2>
type struct ('T1 * 'T2) with
    static member Fst (struct (a, _b)) = a
```

This capability is gated behind the same preview flag as extension constraint solutions (see
[Feature Flag](#feature-flag)); below preview it is rejected with a feature-availability error.

Only **static** members and operators are supported on a tuple type extension. An **instance**
member (`member x.Foo = ...`) can be declared but cannot be invoked through dot-notation on a
tuple value โ€” resolution fails with `error FS0039: The field, constructor or member 'Foo' is not
defined`. Use a static member or operator (as above) instead.

### Resolution Priority

When solving an SRTP constraint:
1. **Built-in solutions** for primitive operators (e.g. `int + int`) are applied when the types match precisely, regardless of whether extension methods exist
2. **Overload resolution** considers both intrinsic and extension members. Extension members are lower priority in the resolution order (same as in regular F# overload resolution)
3. Among extensions, standard F# name resolution rules apply (later `open` shadows earlier)

### Accessibility

An SRTP constraint may only be solved by a **public** member. A `private`, `internal`, or
`protected` member is never a valid witness โ€” even one that is visible at the point where the
inline function is defined. The inline function carries its solution to arbitrary call sites,
so a non-public witness would be inlined into scopes where it is inaccessible (a runtime
`MethodAccessException`). The compiler therefore rejects it at definition time:

```fsharp
module A =
    type System.Int32 with
        static member private Secret (x: int) = x + 1
    // error FS0001: ... 'Secret' ... is not public
    let inline useSecret (x: ^T) = (^T : (static member Secret: ^T -> ^T) x)
```

This applies to every witness kind โ€” named methods, operators, property accessors, and
`op_Implicit`/`op_Explicit` conversions โ€” and holds across assembly boundaries: an `internal`
member is not a valid witness in a referencing assembly even with `InternalsVisibleTo`.

### Scope Capture

With `--langversion:preview`, extrinsic extension members (defined on a type from another assembly) participate in SRTP constraint resolution when they are in scope where the inline function is defined:

```fsharp
module StringOps =
    // System.String has no built-in (*) โ€” this extension is extrinsic.
    type System.String with
        static member (*) (s: string, n: int) = System.String.Concat(Array.replicate n s)

module GenericLib =
    open StringOps

    // multiply captures the SRTP constraint with String.(*) in scope.
    // The extension is recorded in the constraint at this definition site.
    let inline multiply (x: ^T) (n: int) = x * n

module Consumer =
    open GenericLib
    // StringOps is NOT opened here, but the extension was captured when
    // multiply was defined. It travels with the constraint and resolves here.
    let r = multiply "ha" 3  // "hahaha"
```

**Definition-site capture is intra-assembly only.** The capture shown above travels with the
constraint *within a single assembly*. It is deliberately **not** serialized into compiled
metadata (see [Binary compatibility](#binary-compatibility)), so when `multiply` lives in a
*referenced* assembly the captured `StringOps` extension does not travel to the consumer.
Across assemblies, unresolved SRTP constraints use the **consumer's** scope.
The consumer must have the extension in scope (`open StringOps` / `open type`) at each call site.
Already-resolved witnesses retain their selected member.

```fsharp
// GenericLib compiled into library.dll (opens StringOps at its definition site)
// Consumer.fs references library.dll:
open GenericLib
// error FS0001: None of the types support the operator '*'
//   โ€” StringOps is not in scope here, and cross-assembly capture is not serialized.
let r = multiply "ha" 3

// Fix: bring the extension into the consumer's scope.
open StringOps
let ok = multiply "ha" 3  // "hahaha"
```

### Known Limitations

- **FSharpPlus compatibility**: Code using return types as support types in SRTP constraints may fail to compile. See workarounds below.

### Binary Compatibility

Captured extension scopes are **not** written into compiled metadata.
Their candidate indexes and access rights exist only while the compiler checks the assembly.
Selected witnesses use the existing trait-solution metadata and hidden `$W` methods.
This preserves the pickle format and witness ABI.

The practical consequence is the intra- vs cross-assembly split described under
[Scope Capture](#scope-capture): within one assembly an inline function carries its
definition-site extensions.
A consumer of a *compiled* inline function resolves any unsolved SRTP constraints from its own scope.
The consumer must have the relevant extensions in scope.

## Weak Resolution Changes

With `--langversion:preview`, inline code no longer eagerly resolves SRTP constraints via weak resolution when true overload resolution is involved:

```fsharp
// Before: f1 inferred as DateTime -> TimeSpan -> DateTime (non-generic, because op_Addition
//         had only one overload and weak resolution eagerly picked it)
// After:  f1 stays generic: DateTime -> ^a -> ^b (because weak resolution no longer forces
//         overload resolution for inline code)
let inline f1 (x: DateTime) y = x + y
```

### Workarounds for Breaking Changes

If existing inline code breaks:

1. **Add explicit type annotations:**
   ```fsharp
   let inline f1 (x: DateTime) (y: TimeSpan) : DateTime = x + y
   ```

2. **Use sequentialization** to force resolution order

3. **Sequentialize nested calls** when using FSharpPlus-style patterns with return types in support types. If nesting `InvokeMap` calls directly produces errors, sequentialize with a let-binding (see the sequentialization example above). Do NOT remove return types from support types unless you understand the impact on overload resolution โ€” return types are the fundamental mechanism for return-type-driven resolution in type-class encodings.

## Feature Flag

Enable with: `--langversion:preview`  
Feature name: `ExtensionConstraintSolutions`

This feature is gated at the preview language version and will be stabilized in a future F# release.

## AllowOverloadOnReturnType Attribute

The `[<AllowOverloadOnReturnType>]` attribute (in `FSharp.Core`) enables return-type-based overload resolution for any method, extending behavior previously reserved for `op_Explicit` and `op_Implicit`:

```fsharp
type Converter =
    [<AllowOverloadOnReturnType>]
    static member Convert(x: string) : int = int x
    [<AllowOverloadOnReturnType>]
    static member Convert(x: string) : float = float x

let resultInt: int = Converter.Convert("42")       // resolves to int overload
let resultFloat: float = Converter.Convert("42")   // resolves to float overload
```

Without the attribute, these overloads would produce an ambiguity error. Note that the call site must provide enough type context (e.g., a type annotation) for the compiler to select the correct overload.

## Design Intent: Aspirational Patterns

The patterns below describe RFC design goals. Only the examples marked as supported compile.
Some cross-type operator extensions work. Other combinations fail when a built-in solution requires both operands to have one type.

### Numeric Widening via Extension Operators (LIMITED)

The following extension supports integer-to-floating-point addition:

```fsharp
type System.Int32 with
    static member inline widen_to_double (a: int32) : double = double a

let inline widen_to_double (x: ^T) : double = (^T : (static member widen_to_double : ^T -> double) (x))

type System.Double with
    static member inline (+)(a: double, b: 'T) : double = a + widen_to_double b
    static member inline (+)(a: 'T, b: double) : double = widen_to_double a + b

let result = 1 + 2.0  // 3.0
```

Built-in `float + float` still takes priority over the extensions.
Built-in integer addition rejects mixed integer operands such as `1 + 2L`.

### Defining op_Implicit via Extension Members

Public extension `op_Implicit`/`op_Explicit` conversions **do** participate in SRTP resolution
when the target type is determined at the call site:

```fsharp
module A =
    type Wrap = { X: int }
    type Wrap with
        static member op_Implicit (w: Wrap) : int = w.X
    let inline conv (x: ^T) : int = ((^T) : (static member op_Implicit : ^T -> int) x)

let r = A.conv { A.Wrap.X = 5 }  // 5
```

What is **not** supported is the return-type-polymorphic form the RFC describes โ€” a single
`(^T or ^U)` conversion function backed by several `op_Implicit` overloads that differ only by
return type:

```fsharp
// โš ๏ธ ASPIRATIONAL โ€” does not compile (error FS0001: None of the types support the operator 'op_Implicit')
let inline implicitConv (x: ^T) : ^U = ((^T or ^U) : (static member op_Implicit : ^T -> ^U) (x))

type System.Int32 with
    static member inline op_Implicit (a: int32) : int64 = int64 a
    static member inline op_Implicit (a: int32) : double = double a
```

> **Note**: Even where supported, these conversions are explicit in F# code
> (you must call the conversion function), not implicit as in C#.

---

### Tooling Features (`docs/tooling-features.md`)

---
title: Overview
category: Language Service Internals
categoryindex: 300
index: 100
---
# Overview

The F# Language Service (FSharp.Editor, using FSharp.Compiler.Service) is designed to support tooling in Visual Studio and other IDEs. This document gives an overview of the features supported and notes on their technical characteristics.

## Kinds of data processed and served in F# tooling

The following tables are split into two categories: syntactic and semantic. They contain common kinds of information requested, the kind of data that is involved, and roughly how expensive the operation is in terms of expected memory allocation and CPU processing.

### IDE actions based on syntax

|  Action                                | Data inspected | Data returned | Expected CPU/Allocations (S/M/L/XL) |
|----------------------------------------|----------------|---------------|-----------------|
| Syntactic Classification               | Current doc's source text | Text span and classification type for each token in the document | S |
| Breakpoint Resolution                  | Current doc's syntax tree | Text span representing where breakpoints were resolved | S |
| Debugging data tip info                | Current doc's source text | Text span representing the token being inspected | S |
| Brace pair matching                    | Current doc's source text | Text spans representing brace pairs that match in the input document | S |
| "Smart" indentation                    | Current doc's source text | Indentation location in a document | S |
| Code fixes operating only on syntax    | Current doc's source text | Small text change for document | S |
| XML doc template generation            | Current doc's syntax tree | Small (usually) text change for document | S |
| Brace pair completion                  | Current doc's source text | Additional brace pair inserted into source text | S |
| Source document navigation             | Current doc's syntax tree | "Navigation Items" with optional child navigation items containing ranges in source code | S |
| Code outlining                         | Current doc's source text | Text spans representing blocks of F# code that are collapsable as a group | S - M |
| Editor formatting                      | Current doc's source text | New source text for the document | S - L |
| Syntax diagnostics                     | Current doc's source text | List of diagnostic data including the span of text corresponding to the diagnostic | S |
| Global construct search and navigation | All syntax trees for all projects | All items that match a user's search pattern with spans of text that represent where a given item is located | S-L |

You likely noticed that nearly all of the syntactical operations are marked `S`. Aside from extreme cases, like files with 50k lines or higher, syntax-only operations typically finish very quickly. In addition to being computationally inexpensive, they are also run asynchronously and free-threaded.

Editor formatting is a bit of an exception. Most IDEs offer common commands for format an entire document, and although they also offer commands to format a given text selection, users typically choose to format the whole document. This means an entire document has to be inspected and potentially rewritten based on often complex rules. In practice this isn't bad when working with a document that has already been formatted, but it can be expensive for larger documents with strange stylistic choices.

Most of the syntax operations require an entire document's source text or parse tree. It stands to reason that this could be improved by operating on a diff of a parse tree instead of the whole thing. This is likely a very complex thing to implement though, since none of the F# compiler infrastructure works in this way today.

### IDE actions based on semantics

|  Action | Data inspected | Data returned | Expected CPU/Allocations (S/M/L/XL) |
|---------|---------------|---------------|-----------------|
| Most code fixes | Current document's typecheck data | Set (1 or more) of suggested text replacements | S-M |
| Semantic classification | Current document's typecheck data | Spans of text with semantic classification type for all constructs in a document | S-L |
| Code generation / refactorings | Current document's typecheck data and/or current resolved symbol/symbols | Text replacement(s) | S-L |
| Code completion | Current document's typecheck data and currently-resolved symbol user is typing at | List of all symbols in scope that are "completable" based on where completion is invoked | S-L |
| Editor tooltips | Current document's typecheck data and resolved symbol where user invoked a tooltip | F# tooltip data based on inspecting a type and its declarations, then pretty-printing them | S-XL |
| Diagnostics based on F# semantics | Current document's typecheck data | Diagnostic info for each symbol with diagnostics to show, including the range of text associated with the diagnostic | M-XL |
| Symbol highlighting in a document | Current document's typecheck data and currently-resolved symbol where user's caret is located | Ranges of text representing instances of that symbol in the document | S-M |
| Semantic navigation (for example, Go to Definition) | Current document's typecheck data and currently-resolved symbol where the user invoked navigation | Location of a symbol's declaration | S-M |
| Rename | Graph of all projects that use the symbol that rename is triggered on and the typecheck data for each of those projects | List of all uses of all symbols that are to be renamed | S-XL |
| Find all references | Graph of all projects that Find References is triggered on and the typecheck data for each of those projects | List of all uses of all symbols that are found | S-XL |
| Unused value/symbol analysis | Typecheck data for the current document | List of all symbols that aren't a public API and are unused | S-M |
| Unused `open` analysis | Typecheck data for the current document and all symbol data brought into scope by each `open` declaration | List of `open` declarations whose symbols it exposes aren't used in the current document | S-L |
| Missing `open` analysis | Typecheck data for the current document, resolved symbol with an error, and list of available namespaces or modules | List of candidate namespaces or modules that can be opened | S-M |
| Misspelled name suggestion analysis | Typecheck data for the current document and resolved symbol with an error | List of candidates that are in scope and best match the misspelled name based on a string distance algorithm | S-M |
| Name simplification analysis | Typecheck data for the current document and all symbol data brought into scope by each `open` declaration | List of text changes available for any fully- or partially-qualified symbol that can be simplified | S-XL |

You likely noticed that every cost associated with an action has a range. This is based on two factors:

1. If the semantic data being operated on is cached
2. How much semantic data must be processed for the action to be completed

Most actions are `S` if they operate on cached data and the compiler determines that no data needs to be re-computed. The size of their range is influenced largely by the _kind_ of semantic operations each action has to do, such as:

* Typechecking a single document and processing the resulting data
* Typechecking a document and its containing project and then processing the resulting data
* Resolving a single symbol in a document
* Resolving the definition of a single symbol in a codebase
* Inspecting all symbols brought into scope by a given `open` declaration
* Inspecting all symbols in a document
* Inspecting all symbols in all documents contained in a graph of projects

For example, commands like Find All References and Rename can be cheap if a codebase is small, hence the lower bound being `S`. But if the symbol in question is used across many documents in a large project graph, they are very expensive because the entire graph must be crawled and all symbols contained in its documents must be inspected.

In contrast, actions like highlighting all symbols in a document aren't terribly expensive even for very large files. That's because the symbols to be inspected are ultimately only in a single document.

---

### .Github Docs State Machine (`.github/docs/state-machine.md`)

# dotnet/fsharp โ€” Agentic State Machine

> **16 workflows documented.** Source: `.github/workflows/` ยท FULL_REWRITE (generator `f107bba1a1cd61dc`).

This document maps the 16 GitHub Actions workflows and AI agents in this repository โ€” their triggers, control flow, side effects, and cross-workflow interactions. Audience: new engineers onboarding, PR reviewers, and automation auditors. Read top-down for context, or jump to the Handover Map for cross-workflow signals.

## Glossary

- **gh-aw** โ€” GitHub CLI extension (`gh aw`) compiling agentic `.md` workflows (frontmatter + prompt) into `.lock.yml` GitHub Actions files.
- **safe-outputs** โ€” gh-aw's permission/rate-limit framework constraining agent side effects per run (`max:`, `allowed-files:`, `labels:`).
- **noop** โ€” Safe-output ending a run with no side effects. `report-as-issue: false` = silent no-op.
- **CCA (Copilot Coding Agent)** โ€” Hosted coding agent invoked via `create-agent-session`.
- **state-store branch** โ€” Git branch used by repo-memory for persistent JSON storage between runs.
- **flaky-test-detector** โ€” Skill confirming flaky tests via โ‰ฅ3 distinct PR failure evidence.
- **Cat A/B/C** (RPS) โ€” Regression PR triage: A = review feedback, B = CI/conflict, C = healthy.
- **B0โ€“B4** (RPS Cat B) โ€” B0: conflict, B1: infra/flaky, B2: compile error, B3: bug NOT fixed, B4: other.
- **FCS** โ€” F# Compiler Service (compiler-as-library for IDEs).
- **`.lock.yml`** โ€” Compiled Actions YAML from `gh aw compile`; never hand-edited.

## Legend

| Prefix | Meaning |
|--------|---------|
| โฐ | Cron/schedule trigger |
| ๐Ÿ‘ค | Human-initiated (PR, issue, comment, dispatch) |
| โš™๏ธ | Workflow engine (job condition, step logic, push) |
| ๐Ÿค– | Bot/agent action |
| `<<choice>>` | Binary branch (exactly 2 outgoing edges) |
| `<<fork>>`/`<<join>>` | Parallel execution (overlapping guards) |

## Overview

| # | Workflow | Trigger | Inputs | Primary Actions |
|---|---------|---------|--------|-----------------|
| 1 | `agentic-state-machine.md` | โฐ weekly, ๐Ÿ‘ค dispatch | none | create-pull-request (.github/docs/**) |
| 2 | `aw-auto-update.md` | ๐Ÿ‘ค dispatch | none | noop or create-agent-session |
| 3 | `backport.yml` | ๐Ÿ‘ค issue_comment, โฐ daily | none | reusable workflow (dotnet/arcade) |
| 4 | `branch-merge.yml` | โš™๏ธ push (release/*, main) | none | reusable workflow (dotnet/arcade) |
| 5 | `check_release_notes.yml` | ๐Ÿ‘ค pull_request_target | none | PR comment (release notes check) |
| 6 | `commands.yml` | ๐Ÿ‘ค issue_comment | none | run CLI, apply patch, comment |
| 7 | `copilot-setup-steps.yml` | ๐Ÿ‘ค dispatch | none | setup environment (checkout, build, tools) |
| 8 | `add_to_project.yml` | ๐Ÿ‘ค issues/PR opened | none | add label, set milestone, cleanup runs |
| 9 | `labelops-flake-fix.md` | ๐Ÿ‘ค dispatch | failing_test, affected_prs, originating_pr | create-pull-request, create-issue, add-comment |
| 10 | `labelops-pr-maintenance.md` | โฐ every 3h, ๐Ÿ‘ค dispatch | none | push-to-PR, add-comment, add-labels, dispatch-workflow |
| 11 | `labelops-pr-security-scan.md` | ๐Ÿ‘ค pull_request_target (opened, synchronize), โฐ weekly cleanup | none | latest-head tooling labels, repo-memory dedup/category baseline |
| 12 | `msbuild-quality-review.md` | โฐ weekly, ๐Ÿ‘ค dispatch | none | create-issue, create-pull-request (draft) |
| 13 | `regression-pr-shepherd.md` | โฐ every 4h, ๐Ÿ‘ค dispatch | none | push-to-PR, add-comment, remove-labels |
| 14 | `repo-assist-scheduled.md` | โฐ every 12h, ๐Ÿ‘ค dispatch | none | create-pull-request, add-comment, add/remove-labels, create/update-issue, push-to-PR |
| 15 | `repository_lockdown_check.yml` | ๐Ÿ‘ค pull_request_target | none | PR comment (lockdown warning) |
| 16 | `skill-validation.yml` | ๐Ÿ‘ค PR, โš™๏ธ push (main), ๐Ÿ‘ค dispatch | none | validate skills/agents |

## Handover Map

Cross-workflow interactions (producer โ†’ consumer):

| Signal | Producer | Consumer | Mechanism |
|--------|----------|----------|-----------|
| `AI-Auto-Resolve-CI/Conflicts` labels | Human maintainer | `labelops-pr-maintenance` | Label filter on PR list |
| `AI-Issue-Regression-PR` label | `repo-assist-scheduled` | `regression-pr-shepherd` | Label filter on PR list |
| `AI-thinks-issue-fixed` label | `repo-assist-scheduled` | `regression-pr-shepherd` (remove) | Label on linked issue |
| `dispatch-workflow: labelops-flake-fix` | `labelops-pr-maintenance` | `labelops-flake-fix` | workflow_dispatch with inputs |
| `Flaky` label | `labelops-flake-fix` | Human triage | always-applied on PR/issue |
| `AI-needs-CI-fix-input` label | `labelops-pr-maintenance` | Human maintainer | escalation signal |
| `โš ๏ธ Affects-*` labels | `labelops-pr-security-scan` | Human reviewer | informational |
| `Needs-Triage` label | `add_to_project.yml` | Human triage | imperative on new issues |
| State-store `memory/labelops-pr-security-scan` | `labelops-pr-security-scan` | `labelops-pr-security-scan` | per-PR SHA/category shards; serialized native repo-memory writes |
| State-store `memory/repo-assist` | `repo-assist-scheduled` | `repo-assist-scheduled` | repo-memory persistence |

## Group A โ€” LabelOps Ecosystem

Workflows: `labelops-pr-maintenance` (LPM), `labelops-flake-fix` (LFF), `labelops-pr-security-scan` (LPSS).

LPM dispatches LFF when proven flakes are detected. LPSS serializes runs because repo-memory copies whole shard files. A queued event runs only if its PR remains open at that exact head, so stale queued heads no-op. Only new PR events trigger scans; there is no backlog scan or legacy-state import. Existing warning labels supply the first comment baseline. Weekly cleanup prunes closed PR records with safe outputs in staged mode, preventing comments and labels. It rewrites emptied shards because repo-memory does not delete missing files.

```
/* Detailed source-code truncated for AI context efficiency. */
```

## Group B โ€” Regression Test Pipeline

Workflows: `repo-assist-scheduled` (RA), `regression-pr-shepherd` (RPS).

RA creates regression test PRs and labels issues. RPS shepherds those PRs to merge.

```mermaid
stateDiagram-v2
  direction LR

  state "repo-assist-scheduled" as RA {
    [*] --> RA_FetchData : โฐ every 12h / ๐Ÿ‘ค dispatch
    RA_FetchData --> RA_Task1 : โš™๏ธ task selection
    RA_Task1 --> RA_Task3 : โš™๏ธ issue investigation
    RA_Task3 --> RA_Task2 : โš™๏ธ windows-only revisit
    state RA_TestResult <<choice>>
    RA_Task2 --> RA_TestResult : โš™๏ธ regression test verification
    RA_TestResult --> RA_CreatePR : โš™๏ธ test passes
    RA_TestResult --> RA_RemoveLabel : โš™๏ธ test fails (bug not fixed)
    RA_CreatePR --> RA_Final : ๐Ÿค– create-pull-request
    RA_RemoveLabel --> RA_Final : ๐Ÿค– remove-labels (AI-thinks-issue-fixed)
    RA_Final --> [*]
  }

  state "regression-pr-shepherd" as RPS {
    [*] --> RPS_ListPRs : โฐ every 4h / ๐Ÿ‘ค dispatch
    RPS_ListPRs --> RPS_Noop : โš™๏ธ no eligible PRs
    RPS_Noop --> [*]
    RPS_ListPRs --> RPS_Categorize : โš™๏ธ โ‰ค3 PRs
    state RPS_Category <<choice>>
    RPS_Categorize --> RPS_Category : โš™๏ธ triage
    RPS_Category --> RPS_Feedback : โš™๏ธ Cat A (review feedback)
    RPS_Category --> RPS_CIFix : โš™๏ธ Cat B (CI/conflict)
    RPS_Feedback --> RPS_Push : ๐Ÿค– push-to-pull-request-branch
    RPS_Push --> [*]
    state RPS_BType <<choice>>
    RPS_CIFix --> RPS_BType : โš™๏ธ failure type
    RPS_BType --> RPS_Rebase : โš™๏ธ B0 (conflict)
    RPS_BType --> RPS_FixCode : โš™๏ธ B2 (compile error)
    RPS_Rebase --> [*]
    RPS_FixCode --> [*]
  }

  RA_CreatePR --> RPS_ListPRs : ๐Ÿค– async via label AI-Issue-Regression-PR
```

## Group C โ€” Traditional Workflows (PR Lifecycle)

Workflows triggered by PRs/issues: `check_release_notes`, `repository_lockdown_check`, `add_to_project`, `commands`, `backport`.

```
/* Detailed source-code truncated for AI context efficiency. */
```

## Group D โ€” Infrastructure & Maintenance

Workflows: `branch-merge`, `copilot-setup-steps`, `skill-validation`, `agentic-state-machine`, `aw-auto-update`, `msbuild-quality-review`.

```
/* Detailed source-code truncated for AI context efficiency. */
```

## Safe-Output Signatures

gh-aw safe-output defaults (suppressed below): `target: "*"`, `noop.report-as-issue: false`, `draft: false`.

| Workflow | Output | Max | Key Constraints |
|----------|--------|-----|-----------------|
| `agentic-state-machine` | `create-pull-request` | 1 | title `[Agentic State Machine] `, allowed-files `.github/docs/**` |
| `aw-auto-update` | `create-agent-session` | 1 | base: main |
| `labelops-flake-fix` | `create-pull-request` | 1 | title `[LabelOps Flake] `, labels: automation+Flaky+NO_RELEASE_NOTES, protected-files: fallback-to-issue |
| `labelops-flake-fix` | `create-issue` | 1 | title `[LabelOps Flake] `, labels: Flaky+automation |
| `labelops-flake-fix` | `add-comment` | 1 | โ€” |
| `labelops-pr-maintenance` | `push-to-pull-request-branch` | 5 | protected-files: allowed |
| `labelops-pr-maintenance` | `add-comment` | 5 | hide-older-comments: true |
| `labelops-pr-maintenance` | `add-labels` | 3 | allowed: AI-needs-CI-fix-input |
| `labelops-pr-maintenance` | `dispatch-workflow` | 3 | workflows: labelops-flake-fix |
| `labelops-pr-security-scan` | `add-labels` / `add-comment` | 11 / 1 | triggering PR only; output filter suppresses unchanged/removal-only categories |
| `msbuild-quality-review` | `create-issue` | 1 | title `[msbuild-quality] `, labels: automation+Area-ProjectsAndBuild |
| `msbuild-quality-review` | `create-pull-request` | 1 | draft: true, title `[msbuild-quality] `, protected-files: fallback-to-issue |
| `regression-pr-shepherd` | `push-to-pull-request-branch` | 10 | allowed-files: tests/**, vsintegration/tests/** |
| `regression-pr-shepherd` | `add-comment` | 5 | hide-older-comments: true |
| `regression-pr-shepherd` | `remove-labels` | 5 | allowed: AI-thinks-issue-fixed |
| `repo-assist-scheduled` | `create-pull-request` | 10 | title `Add regression test: `, labels: NO_RELEASE_NOTES+AI-Issue-Regression-PR, reviewers: abonie+T-Gro, auto-merge: true |
| `repo-assist-scheduled` | `add-comment` | 10 | hide-older-comments: true |
| `repo-assist-scheduled` | `add-labels` | 30 | allowed: AI-thinks-issue-fixed, AI-thinks-windows-only |
| `repo-assist-scheduled` | `remove-labels` | 10 | allowed: AI-thinks-issue-fixed, AI-thinks-windows-only |
| `repo-assist-scheduled` | `create-issue` | 4 | title `[Repo Assist] `, labels: automation+repo-assist |
| `repo-assist-scheduled` | `push-to-pull-request-branch` | 4 | title `[Repo Assist] `, protected-files: fallback-to-issue |

## Label Index

| Label | Type | Added by | Removed by | Read by | Notes |
|-------|------|----------|------------|---------|-------|
| `Needs-Triage` | imperative | add_to_project | Human | Human | Applied to new issues |
| `NO_RELEASE_NOTES` | filter | Human | โ€” | check_release_notes | Opts out of release notes check |
| `AI-Auto-Resolve-CI` | filter | Human | โ€” | LPM | Opts PR into CI auto-fix |
| `AI-Auto-Resolve-Conflicts` | filter | Human | โ€” | LPM | Opts PR into conflict auto-resolve |
| `AI-needs-CI-fix-input` | agent-add | LPM | Human | LPM (ci_blocked) | Escalation when CI unfixable |
| `AI-Issue-Regression-PR` | always-applied | RA (on PR) | โ€” | RPS | Links regression test PRs |
| `AI-thinks-issue-fixed` | agent-add + agent-remove | RA | RA, RPS | RA (Task 2) | Issue believed fixed |
| `AI-thinks-windows-only` | agent-add + agent-remove | RA | RA | RA (Task 3) | Issue believed VS-only |
| `Flaky` | always-applied | LFF | โ€” | Human | Marks flaky test PR/issue |
| `automation` | always-applied | LFF, MQR, RA | โ€” | Human | Generic automation tag |
| `AI-Tooling-Check-Scanned-Clean` | agent-add | LPSS | โ€” | Human | Fork PR scanned, no flags |
| `AI-Tooling-Check-Bypassed` | agent-add | LPSS | โ€” | Human | Non-fork PR bypassed |
| `โš ๏ธ Affects-*` family (7 labels) | agent-add | LPSS | โ€” | Human | Build-Infra, Compiler-Output, Bootstrap, Restore, Design-Time, Test-Tooling, Agent-Config |
| `โš ๏ธ Suspicious-Prompting` | agent-add | LPSS | โ€” | Human | Prompt injection detected |
| `โš ๏ธ Scope-Review-Needed` | agent-add | LPSS | โ€” | Human | PR scope exceeds stated purpose |
| `repo-assist` | always-applied | RA | โ€” | Human | Tags RA-created issues |
| `Area-ProjectsAndBuild` | always-applied | MQR | โ€” | Human | Tags MSBuild quality issues |

---

> generator-version: f107bba1a1cd61dc ยท source-shas: 06e56c52,149f0bbe,1af951a0,36b2b857,3775b51d,420b9d6e,49b2989b,5e9a1344,7dca5b8f,9285c8a0,98d92f32,acf12bdf,b5c04ea8,d3e496db,ec5fa486,f107bba1,

---

### Postmortems Near Miss Core Language Feature Gate Skew (`docs/postmortems/near-miss-core-language-feature-gate-skew.md`)

---
title: Core language-feature gate skew
category: Postmortems
categoryindex: 550
index: 600
---
# Near miss: FSharp.Core used a feature gated beyond its build language

## Summary

[#20422](https://github.com/dotnet/fsharp/pull/20422) made `[<OptimizeClosureIfNotInlined>]` part of FSharp.Core and applied it to inline List and Array functions, while the new compiler allowed declarations using the attribute only under `LangVersion=preview`. The .NET VMR stage-2 build then used that compiler to rebuild Proto FSharp.Core at F# 11 and failed with FS3350. VMR CI caught the regression before release; no released SDK, NuGet package, or user workload was affected.

## Error Manifestation

In VMR build 1601188, `SB_CentOSStream10_Offline_CurrentSourceBuiltSdk_x64` passed stage 1 and the vertical builds, then failed when stage 2 rebuilt Proto FSharp.Core with the newly source-built compiler:

```text
list.fs(242,71): error FS3350: Feature 'optimize a curried closure argument when its inlining fails' is not available in F# 11.0. Please use language version 'PREVIEW' or greater.
```

The source line was the unconditional `[]` annotation on `List.iteri`. Other annotated List and Array functions had the same incompatible compiler/Core version contract.

## Root Cause

The change crossed a self-hosting boundary that was treated as two independent concerns:

1. FSharp.Core defined `OptimizeClosureIfNotInlinedAttribute`, made affected List and Array functions inline, and attached the attribute to their callback parameters. This made the attribute part of the library surface and its optimization metadata for every FSharp.Core build.
2. The compiler recognized the attribute, stored it in a `ValFlags` bit, and used it to hoist one `OptimizedClosures.Adapt` when an opaque callback could not be inlined. Declaration validation separately mapped `LanguageFeature.OptimizeClosureIfNotInlined` to `previewVersion`.

`Configuration=Proto` does not select preview. Once FSharp.Core used the attribute unconditionally, its declaration gate had to be available in every language version used to build that source, including F# 11.

The asymmetry explained the apparently contradictory results. A stage-1 compiler predating #20422 did not recognize `OptimizeClosureIfNotInlined` as a well-known attribute, so it treated the annotation as ordinary metadata and compiled FSharp.Core. The newly built stage-2 compiler recognized the attribute and ran `checkLanguageFeatureError` while checking its declaration, so the same source failed at F# 11.

Consumer language version was not the problem. Imported metadata does not run the declaration check, and optimizer consumption of the `OptimizeClosureIfNotInlined` flag is not language-version gated. The component test that compiles the attributed library at F# 11 and consumes it at F# 8 proves that an old-language consumer can still receive the optimization.

Compiler/Core binary skew also remains correct. `ValFlags` is serialized as a fixed-width `int64`; a compiler predating this flag reads the same field and ignores the unknown bit, so the metadata layout remains aligned and the inline function still executes correctly. That older compiler cannot perform the new hoisted-`Adapt` transform, however, so an opaque callback can retain per-call curried dispatch inside the newly inline List or Array loop. The skew is therefore a performance compatibility concern, not a correctness or metadata compatibility failure.

The violated invariant was: **when FSharp.Core unconditionally adopts compiler-recognized syntax, attributes, or optimization metadata, the compiler feature gate and every compiler generation that builds Core must agree on the minimum supported language version.**

## Why It Escaped

The positive and structural component tests compiled their attributed sources with `withLangVersionPreview`, so they proved the feature implementation without proving the F# 11 boundary required by Proto. The availability test rejected F# 8 but did not assert that F# 11 was the first accepted version.

The ordinary bootstrap also masked the defect. Stage 1 used an older compiler that did not recognize the new attribute, while the normal non-Proto product build could use preview. Only the VMR stage-2 flow combined the newly built compiler with a Proto FSharp.Core rebuild at F# 11. fsharp CI did not exercise that exact fresh-compiler/Proto-language combination before the change forward-flowed.

The failure appeared in FSharp.Core, but the incompatible decision was in the compiler's language-feature table. That separation made each side look locally valid while violating their shared self-hosting contract.

## Fix

[#20571](https://github.com/dotnet/fsharp/pull/20571) maps `LanguageFeature.OptimizeClosureIfNotInlined` to `languageVersion110`. Positive optimization tests and structural-validation tests now compile at F# 11, the boundary test rejects F# 10 with FS3350, and the cross-assembly test compiles the attributed library at F# 11 while retaining its F# 8 consumer.

The matching language release note moved from preview to F# 11. Validation rebuilt Proto FSharp.Core at F# 11 with the freshly built Release compiler, reproducing the VMR stage-2 compiler/Core pairing.

## Timeline

| Date | Event |
| --- | --- |
| 2026-09-16 | #20422 merged as `ec437d5ac2f03c27eb3b4a9ec14fb829570baadf`, adding the attribute, compiler optimization, and unconditional List/Array usage. |
| 2026-09-17 | VMR build 1601188 passed stage 1 and vertical builds, then failed the offline current-source-built-SDK stage-2 Proto FSharp.Core rebuild with FS3350. |
| 2026-09-18 | #20571 assigned the feature to F# 11, moved the release note, pinned the F# 11/F# 10/F# 8 boundaries, and passed a fresh-compiler Proto FSharp.Core rebuild. |

## Prevention

`.github/instructions/CoreCompilerFeatureCoupling.instructions.md` scopes the shared contract to the FSharp.Core declarations/usages, compiler feature gate and metadata machinery, optimizer, and regression tests involved in this class of change.

For any compiler-recognized construct or optimization metadata adopted unconditionally by FSharp.Core:

- assign its language feature to the lowest language version used to build that Core source, or make the Core usage conditional;
- test successful declaration at that version and FS3350 at the preceding version;
- test imported metadata with an older consumer language version when consumption is intentionally version-independent;
- rebuild Proto FSharp.Core at that language version with the freshly built Release compiler, not only the stage-1 compiler;
- preserve cross-version metadata layout and correctness, and assess the performance fallback when an older compiler ignores new optimization metadata.

---

### Postmortems Regression Fs0229 Bstream Misalignment (`docs/postmortems/regression-fs0229-bstream-misalignment.md`)

---
title: FS0229 B-stream misalignment
category: Postmortems
categoryindex: 550
index: 200
---
# Regression: FS0229 B-Stream Misalignment in TypedTreePickle

## Summary

A metadata unpickling regression causes `FS0229` errors when the F# compiler (post-nullness-checking merge) reads metadata from assemblies compiled with `LangVersion < 9.0`. The root cause is a stream alignment bug in `TypedTreePickle.fs` where the secondary metadata stream ("B-stream") gets out of sync between writer and reader.

## Error Manifestation

```
error FS0229: Error reading/writing metadata for assembly '<AssemblyName>': 
  The data read from the stream is inconsistent, reading past end of resource, 
  u_ty - 4/B  OR  u_ty - 1/B, byte = <N>
```

This error occurs when consuming metadata from an assembly where:
1. The assembly was compiled by the current compiler (which writes B-stream data)
2. The compilation used `LangVersion 8.0` or earlier (which disables `langFeatureNullness`)
3. The assembly references BCL types whose type parameters carry `NotSupportsNull` or `AllowsRefStruct` constraints

**Affected real-world library**: [FsToolkit.ErrorHandling](https://github.com/demystifyfp/FsToolkit.ErrorHandling), which uses `<LangVersion>8.0</LangVersion>` for `netstandard2.0`/`netstandard2.1` TFMs.

## Root Cause

### Dual-Stream Metadata Format

F# compiler metadata uses two serialization streams:
- **Stream A** (main): Type tags, type constructor references, type parameter references, etc.
- **Stream B** (secondary): Nullness information per type + newer constraint data (e.g., `NotSupportsNull`, `AllowsRefStruct`)

These streams are written in parallel during pickling and read in parallel during unpickling. The invariant is: **every byte written to stream B by the writer must have a corresponding read in the reader**.

### The Bug

In `p_ty2` (the type pickle function), nullness information is written to stream B **conditionally**:

```fsharp
// BEFORE FIX (buggy)
| TType_app(tc, tinst, nullness) ->
    if st.oglobals.langFeatureNullness then
        match nullness.Evaluate() with
        | NullnessInfo.WithNull -> p_byteB 12 st
        | NullnessInfo.WithoutNull -> p_byteB 13 st
        | NullnessInfo.AmbivalentToNull -> p_byteB 14 st
    // No else branch - B-stream byte skipped when langFeatureNullness = false!
    p_byte 2 st
    p_tcref "typ" tc st
    p_tys tinst st
```

But in `u_ty` (the type unpickle function), the B-stream byte is read **unconditionally**:

```fsharp
| 2 ->
    let tagB = u_byteB st   // Always reads, regardless of langFeatureNullness at compile time
    let tcref = u_tcref st
    let tinst = u_tys st
    match tagB with
    | 0 -> TType_app(tcref, tinst, KnownAmbivalentToNull)
    | 12 -> TType_app(tcref, tinst, KnownWithNull)
    ...
```

This affects type tags 1 (TType_app no args), 2 (TType_app), 3 (TType_fun), and 4 (TType_var).

Meanwhile, `p_tyar_constraints` **unconditionally** writes constraint data to B-stream:

```fsharp
let p_tyar_constraints cxs st =
    let cxs1, cxs2 = cxs |> List.partition (function
        | TyparConstraint.NotSupportsNull _ | TyparConstraint.AllowsRefStruct _ -> false
        | _ -> true)
    p_list p_tyar_constraint cxs1 st
    p_listB p_tyar_constraintB cxs2 st  // Always writes to B, regardless of langFeatureNullness
```

### Misalignment Cascade

When `langFeatureNullness = false`:

1. Writer processes types โ†’ skips B-bytes for each type tag 1-4
2. Writer processes type parameter constraints โ†’ writes `NotSupportsNull` data to B-stream (value `0x01`)
3. Reader processes types โ†’ reads B-stream expecting nullness tags โ†’ gets constraint data instead
4. Constraint byte `0x01` is not a valid nullness tag (valid values: 0, 9-20) โ†’ `ufailwith "u_ty - 4/B"` or similar

The misalignment cascades: once one byte is read from the wrong position, all subsequent B-stream reads are shifted.

## Fix

Added `else p_byteB 0 st` to all four type cases in `p_ty2`, ensuring a B-byte is always written regardless of `langFeatureNullness`:

```fsharp
// AFTER FIX
| TType_app(tc, tinst, nullness) ->
    if st.oglobals.langFeatureNullness then
        match nullness.Evaluate() with
        | NullnessInfo.WithNull -> p_byteB 12 st
        | NullnessInfo.WithoutNull -> p_byteB 13 st
        | NullnessInfo.AmbivalentToNull -> p_byteB 14 st
    else
        p_byteB 0 st  // Keep B-stream aligned
    p_byte 2 st
    p_tcref "typ" tc st
    p_tys tinst st
```

Value `0` means "no nullness info / AmbivalentToNull" and is already handled by all reader match cases.

## Timeline

| Date | PR | Change |
|------|-----|--------|
| Jul 2024 | [#15181](https://github.com/dotnet/fsharp/pull/15181) | Nullness checking: introduced B-stream for nullness bytes, conditional write in `p_ty2` |
| Aug 2024 | [#15310](https://github.com/dotnet/fsharp/pull/15310) | Nullness checking applied to codebase |
| Sep 2024 | [#17706](https://github.com/dotnet/fsharp/pull/17706) | `AllowsRefStruct`: added constraint data to B-stream unconditionally via `p_listB` |

The bug was latent from #15181 but only manifested when #17706 added unconditional B-stream writes for constraints. Before #17706, the B-stream was empty when `langFeatureNullness = false`, so the reader's unconditional reads would hit the end-of-stream sentinel (returning 0) harmlessly. After #17706, constraint data appeared in the B-stream even without nullness, causing the misalignment.

## Regression Tests

Two tests added in `tests/FSharp.Compiler.ComponentTests/Import/ImportTests.fs`:

1. **Basic test**: Compiles a library with `LangVersion=8.0` containing generic types with BCL constraints (e.g., `IEquatable<'T>`), then references it from another compilation and verifies no FS0229 error.

2. **Stress test**: Multiple type parameters with various constraint patterns, function types, and nested generics โ€” all compiled at `LangVersion=8.0` and successfully consumed.

## Reproduction

To reproduce the original bug (before fix):

1. Clone [FsToolkit.ErrorHandling](https://github.com/demystifyfp/FsToolkit.ErrorHandling)
2. Inject the pre-fix compiler via `UseLocalCompiler.Directory.Build.props`
3. Build `netstandard2.0` TFM (uses `LangVersion=8.0`)
4. Build `net9.0` TFM that references the `netstandard2.0` output
5. The `net9.0` build fails with `FS0229: u_ty - 4/B`

## Files Changed

- `src/Compiler/TypedTree/TypedTreePickle.fs` โ€” Added `else p_byteB 0 st` to four locations in `p_ty2`
- `tests/FSharp.Compiler.ComponentTests/Import/ImportTests.fs` โ€” Two regression tests
- `tests/FSharp.Compiler.ComponentTests/FSharp.Compiler.ComponentTests.fsproj` โ€” Added `ImportTests.fs` include (was missing since migration)

---

### Postmortems Regression Legacy Inline Metadata Dynamic Invocation (`docs/postmortems/regression-legacy-inline-metadata-dynamic-invocation.md`)

---
title: Legacy inline metadata dynamic invocation
category: Postmortems
categoryindex: 550
index: 300
---
# Regression: Legacy inline metadata decoded as non-inline, breaking cross-assembly SRTP

## Summary

Adding a new `ValInline.InlinedDefinition` case reused the serialized inline-flag bit pattern `0x00`, which already meant "required inline" in assemblies compiled by F# 5.0 and earlier. A newer compiler reading those older assemblies decoded the value as *not* inlined, dropped the inline body at the call site, and emitted a direct call to a dynamic-invocation stub. Cross-assembly SRTP APIs such as Aether 8.3.1 then threw `System.NotSupportedException` at runtime. Shipped in .NET SDK 10.0.400.

## Error Manifestation

A program that consumes an inline SRTP API from a pre-F#6 library compiles cleanly but throws at runtime, including in optimized Release builds:

```text
Unhandled exception. System.NotSupportedException:
Dynamic invocation of op_HatEquals is not supported
   at Aether.Optic.set[a,b,c](a optic, b value)
```

The same source built with SDK 10.0.303 prints the expected result. `--always-inline+` does not help; rebuilding the referenced library with a current compiler does.

## Root Cause

`ValFlags` packs a value's inline declaration into two bits of an `int64` that is serialized verbatim into assembly metadata. The bit patterns are a permanent on-disk contract.

In F# 5.0 and earlier the field had a `PseudoVal` case โ€” "must always be inlined, no IL body needed" โ€” encoded as `0x00` with `ShouldInline = true`:

```fsharp
match (flags &&& 0b110000L) with
| 0b000000L -> ValInline.PseudoVal   // ShouldInline = true
| 0b010000L -> ValInline.Always
| ...
```

PR #6811 (July 2021, F# 6) removed `PseudoVal` and folded it into `Always`. Crucially, `0x00` kept decoding to a `ShouldInline = true` value, so libraries built before the removal continued to import correctly.

PR #19548 introduced `ValInline.InlinedDefinition` and reused the now-"free-looking" `0x00` bit pattern for it โ€” but with the *opposite* semantics, `ShouldInline = false`. The reader was changed so `0x00` decoded to `InlinedDefinition`. That silently reinterpreted every `0x00` inline value already sitting in shipped DLLs: a required-inline definition from an old library now imported as non-inline, so the consuming compiler emitted a direct call to the SRTP dynamic-invocation stub instead of inlining the resolved witness.

The violated assumption is the "tag values are forever" rule: a bit pattern that already has a meaning in shipped metadata cannot be given a new, incompatible meaning.

## Why It Escaped

PR #19548 *did* add write-side normalization so a current compiler serializes `InlinedDefinition` as `Always` (`0x10`), keeping fresh round-trips correct. That protection is exactly what hid the bug:

- Any in-repo test compiles the producer library **with the new compiler**, which never writes `0x00` for an inline value. So no test that builds its own fixtures could reproduce it โ€” the poisoned byte only exists in binaries produced by an F# 5.0-or-earlier compiler.
- The `CompilerCompat` cross-version suite exercises recent SDKs (9 โ†” current), not pre-2021 F# 5 binaries, so the format generation that still emits `0x00` inline bits was outside its matrix.

The gap was read-side: the new meaning was applied to old bytes, and nothing in CI reads bytes written by a 2021-era compiler.

## Fix

PR #20260 adds `ValFlags.OfPickledBits`, used by `u_ValData` when importing metadata. Because the write side always normalizes `InlinedDefinition` to `Always`, a serialized `0x00` inline field can only originate from a legacy compiler, where it meant required inline. `OfPickledBits` therefore maps legacy `0x00` back to `Always` on import. The serialized byte layout is unchanged; only interpretation of the legacy pattern is restored.

## Timeline

| Date | Event |
|---|---|
| โ‰ค 2021 | F# 5.0 and earlier encode required-inline values (`PseudoVal`) as inline bits `0x00`, `ShouldInline = true`. |
| 2021-07-19 | PR #6811 removes `PseudoVal`; `0x00` still decodes to a `ShouldInline = true` value. Old libraries keep working. |
| 2026-04-16 | Commit `761c8635b8` adds write-side normalization for the upcoming `InlinedDefinition` (`0x00` โ†’ `0x10` on pickle). |
| 2026-07-02 | PR #19548 merges: `InlinedDefinition` reuses `0x00` with `ShouldInline = false`; reader decodes `0x00` โ†’ `InlinedDefinition`. Latent regression for legacy binaries. |
| ~2026-08 | Ships in .NET SDK 10.0.400. |
| 2026-08-13 | Issue #20253 filed: Aether 8.3.1 SRTP call throws `NotSupportedException` under 10.0.400. |
| โ€” | PR #20260 adds read-side normalization (`OfPickledBits`), restoring the invariant. |

## Prevention

The generalized rule โ€” flag bit patterns baked into pickled metadata are permanent, and a pattern that already has a meaning in shipped DLLs must never be reinterpreted โ€” is encoded in [`.github/instructions/TypedTreePickle.instructions.md`](../../.github/instructions/TypedTreePickle.instructions.md), whose `applyTo` covers the flag-encoding types in `src/Compiler/TypedTree/TypedTree.{fs,fsi}` and the pickle path. When adding a case to a serialized flag enum, either allocate an unused bit pattern or add read-side normalization that maps legacy patterns to their original semantics โ€” write-side normalization alone only protects future binaries, never the ones already in the wild.

---

### Postmortems Regression Parse Tree Fidelity Return Attributes (`docs/postmortems/regression-parse-tree-fidelity-return-attributes.md`)

---
title: Parse tree fidelity of return attributes
category: Postmortems
categoryindex: 550
index: 500
---
# Regression: `[<return: X>]` Attributes Disappeared From the Untyped Syntax Tree

## Summary

A semantic lowering that had always run in the type checker was moved into the parser, so `SynBinding.attributes` stopped reporting `[<return: X>]` attributes that were visibly present in the source. Tools that read the untyped tree โ€” formatters, analyzers, source generators โ€” were handed a tree that no longer matched the file it came from. Fantomas silently deleted every `[<return: Struct>]` partial active pattern it formatted.

## Error Manifestation

No error. No warning. No diagnostic anywhere.

Given source that visibly carries an attribute:

```fsharp
[<return: Struct>]
let (|Foo|_|) x = ValueNone
```

`SynBinding.attributes` was `[]`. A round-tripping tool read the binding, found nothing to print, and wrote the file back without the attribute:

```fsharp
let (|Foo|_|) x = ValueNone     // attribute gone, file still compiles, meaning changed
```

The failure is silent by construction: the consumer cannot detect an absence it was never told about. Fantomas' own code base contains 34 such active patterns, and self-formatting would have stripped all of them.

## Root Cause

`[<return: X>]` on a binding is written in front of the binding but targets the method's return value. Routing it to `SynValInfo.retInfo` is correct for the type checker, IL emit and the Symbols API. The mistake was *where* the routing happened.

[PR #19738](https://github.com/dotnet/fsharp/pull/19738) moved the rotation into `mkSynBinding` in `SyntaxTreeOps.fs`, a parser-stage constructor. Before that, the rotation lived in `TcNormalizedBinding` and patched a *local* `valSynData`; the `SynBinding` itself was never touched, so the parse tree stayed faithful to the source.

The violated invariant:

> **The untyped syntax tree describes where the user wrote things. Semantic relocation belongs downstream of it.**

`SynBinding` has exactly one contract โ€” report the source. It is not a type checker input in disguise; it is the public output of `FSharpParseFileResults`, and it is the *only* view some consumers have. Once the parser rewrites a node, no consumer can recover the original, because the parser is the last stage that saw the source.

The rotation was lossy in two ways that made recovery impossible even for a consumer that knew about it:

- The attribute list's range narrowed from the `[< >]` span to the attribute alone, so the brackets the user typed were no longer represented anywhere in the tree.
- Every return attribute was collected into one synthesized `SynAttributeList`, so `[<return: A; return: B>]` and `[<return: A>][<return: B>]` produced identical trees. Neither can be printed back to its original form.

## Why It Escaped

The change was reviewed as a type-checker fix, and as a type-checker fix it was correct โ€” both reported bugs (#17904, #19020) were genuinely fixed, and the tests added with it all passed:

- `AttributeCheckingTests.fs` โ€” diagnostics
- `Symbols.fs` โ€” `mfv.ReturnParameter.Attributes` via the FCS Symbols API

All of them observe the *typed* tree. None observes the parse tree. The blast radius of editing `mkSynBinding` โ€” every untyped-tree consumer in the ecosystem โ€” was never in view.

The `tests/service/data/SyntaxTree` baseline corpus is exactly the mechanism that catches this: it pretty-prints the parse tree to a `.bsl` file, so any change to tree shape shows up as a baseline diff a reviewer must accept. At the time of #19738, **not one file in that corpus contained a `return:` attribute**. The corpus was silent because the case did not exist in it, and a silent corpus reads the same as a passing one.

It shipped to nuget.org in `FSharp.Compiler.Service 43.13.101-preview7.26381.103` (2026-08-11). It was found downstream by [fsprojects/fantomas#3400](https://github.com/fsprojects/fantomas/pull/3400) while bumping vendored compiler sources โ€” caught before any Fantomas release carried it, but only because that bump walked one upstream commit at a time. Fantomas' own suite stayed green throughout: its tests covered the return *type annotation* form (`let f x : [<return: A>] int = x`), which never went through this rotation, and not the prefix form, which did.

## Fix

[PR #20356](https://github.com/dotnet/fsharp/pull/20356) moves the rotation to `BindingNormalization.NormalizeBinding` in `CheckExpressions.fs` โ€” the single funnel from `SynBinding` to `NormalizedBinding`, and already a lowering step. Every consumer of the rotated form (`TcNormalizedBinding`, `AnalyzeAndMakeAndPublishRecursiveValue`, the object-expression paths) reads `NormalizeBinding`'s output, so both fixes from #19738 are unchanged and `retInfo` remains the single source of truth for the checker.

`NormalizedBinding` holds a flat `SynAttribute list`, so `RotateReturnAttributes` now takes and returns that instead of `SynAttributes`. The list-splicing that flattened attribute grouping is gone with it โ€” there is no grouping left to destroy at that layer.

`SynBinding` again carries the attribute with its full `[< >]` range, and `retInfo` is empty at parse time.

## Timeline

| Date | PR | Change |
|---|---|---|
| 2026-05-20 | [#19738](https://github.com/dotnet/fsharp/pull/19738) | Rotation moved from `TcNormalizedBinding` (local `valSynData` patch) into `mkSynBinding`. Fixes #17904 and #19020; parse tree starts diverging from source. |
| 2026-08-11 | โ€” | Ships to nuget.org in `FSharp.Compiler.Service 43.13.101-preview7.26381.103`. |
| 2026-08-21 | [fantomas#3400](https://github.com/fsprojects/fantomas/pull/3400) | Fantomas bumps vendored compiler sources, finds `[<return: Struct>]` silently deleted, works around it with `restoreRotatedReturnAttributes`, and raises the layering question upstream. |
| 2026-08-25 | [#20356](https://github.com/dotnet/fsharp/pull/20356) | Rotation moved to `BindingNormalization.NormalizeBinding`. Parse tree faithful again; grouping and ranges preserved. |

## Prevention

- **Rule encoded** in [`.github/instructions/SyntaxTree.instructions.md`](../../.github/instructions/SyntaxTree.instructions.md): the parser must not perform semantic relocation, and any change to parse-tree shape needs `tests/service/data/SyntaxTree` coverage.
- **Baseline coverage added** in `tests/service/data/SyntaxTree/Attribute/`: `ReturnTargetedAttributeStaysOnBinding.fs` pins the attribute to `SynBinding.attributes` with its `[< >]` range, and `ReturnTargetedAttributeGroupingIsPreserved.fs` pins `[<return: A>][<return: A>]` and `[<return: A; return: A>]` to distinct trees.

The generalizable lesson is about *which* tests a change needs, not about attributes. A fix that is correct in the type checker can still be wrong in the parser, and only a parse-tree baseline will say so.

---

### Postmortems Regression Sourcebuild Cpm Runtime Version Floor (`docs/postmortems/regression-sourcebuild-cpm-runtime-version-floor.md`)

---
title: Source-build CPM runtime version floor
category: Postmortems
categoryindex: 550
index: 400
---
# Regression: renamed CPM runtime-package pins broke VMR source-build

## Summary

To satisfy CPM transitive pinning, the central versions of the dotnet/runtime packages that MSBuild and Roslyn pull (`System.Collections.Immutable`, `System.Reflection.Metadata`, `System.Composition`, `System.Diagnostics.DiagnosticSource`, `System.Security.Cryptography.Xml`) were routed through computed `$(System*CentralVersion)` properties with a version floor, instead of the darc-named `$(System*Version)` properties. Source-build injects live runtime versions by overriding the darc-named properties, so the renamed, eagerly-floored pins froze at the floor value and the override never reached CPM. In the .NET 11 RC1 VMR the floor demanded `10.0.10` while source-build supplied the live `10.0.9`, producing prebuilt packages and `NU1109` downgrade errors that blocked the fsharp forward-flow (dotnet/dotnet#8413, #8414, #8415). Caught at the VMR source-only gate; it never shipped.

## Error Manifestation

The `VMR Source-Only Build SB_CentOSStream10_Online_MsftSdk_x64` leg failed two ways.

Prebuilt detection:

```text
eng/finish-source-only.proj(141,5): error : Prebuilt packages are not allowed in source-only builds.
Detected 1 prebuilt package(s):
 - System.Diagnostics.DiagnosticSource.10.0.10
```

And, once the floor was partially relaxed, a downgrade in `fsc`/`fsi`:

```text
error NU1109: Detected package downgrade: System.Collections.Immutable from 10.0.10 to centrally defined 10.0.9.
  fsi -> Microsoft.Build.Framework 18.11.0-1.26417.9 -> System.Collections.Immutable (>= 10.0.10)
  fsi -> System.Collections.Immutable (>= 10.0.9)
```

fsharp's own PR/CI legs were green throughout โ€” the failure appeared only downstream in dotnet/dotnet.

## Root Cause

fsharp declares these `System.*` packages in `eng/Version.Details.xml` with an empty `<Sha>` and the note *"Necessary for source-build. This allows the live version of the package to be used by source-build."* When the VMR builds fsharp it passes `/p:DotNetPackageVersionPropsPath=โ€ฆ/PackageVersions.fsharp.props`, a generated file that **unconditionally overrides the darc-named properties** โ€” `$(SystemCollectionsImmutableVersion)`, etc. โ€” with the live runtime version it just source-built. Whatever CPM reads must be that exact property for the override to take effect.

The CPM-transitive-pinning work introduced an indirection instead:

```xml

<SystemCollectionsImmutableCentralVersion>$(SystemCollectionsImmutableVersion)</SystemCollectionsImmutableCentralVersion>
<SystemCollectionsImmutableCentralVersion
    Condition="...VersionLessThan($(SystemCollectionsImmutableVersion), $(SystemRuntimeCentralFloorVersion))">
  $(SystemRuntimeCentralFloorVersion)   
</SystemCollectionsImmutableCentralVersion>
```

`eng/Packages.props` then pinned `Version="$(SystemCollectionsImmutableCentralVersion)"`. Two mistakes combined:

1. **Rename.** Source-build overrides `$(SystemCollectionsImmutableVersion)`, not `$(...CentralVersion)`. The alias is a different property that source-build has no knowledge of.
2. **Eager floor.** `$(...CentralVersion)` is computed once, before arcade imports `PackageVersions.fsharp.props`, and is `max(flowed, floor)`. Even in source-build it evaluated to the floor.

So the central pin froze at the floor (`10.0.10`). Source-build only produces the live runtime (`10.0.9`), which cannot satisfy a `10.0.10` pin from within the source tree, so restore reached an external feed โ†’ prebuilt; and where the graph also carried a `10.0.9` constraint, `NU1109` downgrade.

The violated assumption: **a source-build "live version" package must be consumed through the exact darc-named `$(System*Version)` property; any renamed or computed intermediate silently defeats the override.** roslyn's `eng/Packages.props` does the correct thing โ€” it references `$(SystemCollectionsImmutableVersion)` directly.

## Why It Escaped

fsharp's PR/CI restores are allowed to pull from nuget.org, where `10.0.10` exists. The floored pin resolved cleanly there, so every fsharp leg โ€” including its own `Source-Build (Managed)` legs โ€” was green. The only environment that rejects an out-of-tree package is the VMR source-**only** prebuilt gate (`SB_CentOSStream10_Online_MsftSdk_x64`), which fsharp CI does not run. The defect was therefore structurally invisible until the change forward-flowed into dotnet/dotnet, roughly a day later.

It also recurred: the same source-build/runtime coherency was fought three times โ€” the MSBuild pin (#20073), the CPM floor (#20084, extended by #20100), and the MSBuild unpin (#20278) โ€” before the direct-property pattern fixed it.

## Fix

[#20290](https://github.com/dotnet/fsharp/pull/20290): reference `$(System*Version)` directly in `eng/Packages.props` (matching roslyn), delete the `$(...CentralVersion)`/`SystemRuntimeCentralFloorVersion` scaffolding from `eng/Versions.props`, and declare the packages at `10.0.10` in `eng/Version.Details.{xml,props}` so product/PR restore meets the MSBuild 18.11 / Roslyn 5.11 transitive minimum. In source-build, `PackageVersions.fsharp.props` overrides those same properties down to the live runtime version, so the pin always tracks what the VMR actually builds. Verified by feeding a simulated `PackageVersions.fsharp.props` through `/p:DotNetPackageVersionPropsPath`: with the fix CPM resolves the injected `10.0.9`; with the old alias it stayed frozen at `10.0.10`.

## Timeline

| Date (2026) | PR | Event |
| --- | --- | --- |
| 08-04 | #20084 | CPM + transitive pinning introduces `$(System*CentralVersion)` + floor (latent). |
| 08-13 | #20073 | MSBuild pinned at `18.10.0-1.26370.18` (kept net10 assets for the then-net10 product). |
| 08-18 | #20100 | Floor extended to the Roslyn-pulled `System.*` packages. |
| 08-18 | #20278 | MSBuild unpinned to `18.11` line; floor bumped to `10.0.10`, expanded to 5 packages. |
| 08-19 | dotnet/dotnet#8413/#8414/#8415 | Forward-flow source-only build fails: prebuilt `DiagnosticSource 10.0.10` + `NU1109`. |
| 08-19 | #20290 | Direct `$(System*Version)`, floor removed, declared `10.0.10`. |

## Prevention

`.github/instructions/EngVersioning.instructions.md` scoped to `eng/Packages.props`, `eng/Versions.props`, and `eng/Version.Details.{xml,props}` encodes the rule: consume source-build "live version" packages through the darc-named `$(System*Version)` property directly; never route them through a renamed or computed central alias/floor. When a transitive consumer (MSBuild, Roslyn) needs a higher minimum than the flowed baseline, raise the declared version in `eng/Version.Details.*` rather than adding a floor โ€” source-build overrides it back down to the live runtime version. fsharp CI cannot reproduce the VMR source-only prebuilt gate, so these files must be reasoned about against source-build, not just a green PR restore.

---

### Release Notes About (`docs/release-notes/About.md`)

---
category: Release Notes
categoryindex: 600
index: 1
title: About
---

# About

The release notes for the [F\# language](./Language.html), [FSharp.Core](./FSharp.Core.html) and [FSharp.Compiler.Service](./FSharp.Compiler.Service.html) are based on the [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format.  
The target audience of these release notes are the respective end-users.

## Writing a changelog entry

In order to keep the change logs consistent the following format was proposed for each entry:

```md
* <Informative description>. ([PR #16106](https://github.com/dotnet/fsharp/pull/16106))
```

Some tips:

* Use valid [Markdown](https://www.markdownguide.org/).
* Use `*` as bullet point symbol. We don't want to mix `*` and `-`.
* Start your description with a capital and end the sentence with a dot.
* **Always** include a link to your pull request before the closing `)`, `([PR #16106](https://github.com/dotnet/fsharp/pull/16106))`.
* Optionally, include a link to an issue on `dotnet/fsharp` use `Issue #number` before the link to the pull request.  

Example:

```md
* Correctly handle assembly imports with public key token of 0 length. ([Issue #16359](https://github.com/dotnet/fsharp/issues/16359), [PR #16363](https://github.com/dotnet/fsharp/pull/16363))
```

* Optionally, include a link to a language suggestion from  `dotnet/fsharp` use `Language suggestion #number` before the link to the pull request.

Example:

```md
* `while!` ([Language suggestion #1038](https://github.com/fsharp/fslang-suggestions/issues/1038), [PR #14238](https://github.com/dotnet/fsharp/pull/14238))
```

* Choose the right section for your type of change. (`## Added`, `## Changed`, `## Deprecated`, `## Removed`, `## Fixed` or `## Security`).
* Ensure your description makes it clear what the change is about. The reader should be informed on a high level without needing to click through the pull request link and find out in the code what actually changed.
* Maintainers or other contributors might rewrite your changelog entry in the future. This might be done when multiple pull requests can be consolidated under the same umbrella.
* Related pull requests can be listed in the same entry when it makes sense.

Example:

```md
* Miscellaneous fixes to parentheses analysis. ([PR #16262](https://github.com/dotnet/fsharp/pull/16262), [PR #16391](https://github.com/dotnet/fsharp/pull/16391), [PR #16370](https://github.com/dotnet/fsharp/pull/16370))
```

## The release process

### General

How does it work? Different stages/phases?

#### FSharp.Compiler.Service

Perhaps add some specific info if available?

---

### Release Notes PENDING BREAKING CHANGES (`docs/release-notes/PENDING_BREAKING_CHANGES.md`)

---
title: Pending breaking changes
category: Release Notes
categoryindex: 600
index: 5
---
# Breaking Changes in Query Expression Fixes

## AnonymousObject Structural Equality ๐Ÿ”ด

`AnonymousObject<T>` types now implement `Equals`/`GetHashCode` with structural equality.

**Impact:** Code using these as Dictionary keys with identity semantics will see different behavior.

```fsharp
let a = AnonymousObject(1, 2)
let b = AnonymousObject(1, 2)
a.Equals(b)  // Was: false, Now: true
```

**Why:** Fixes #7885, #47 - join/groupBy with tuple keys now work correctly.

---

## Expression Tree Structure Changes ๐ŸŸก

### Let-bindings use Block instead of Lambda.Invoke

```fsharp
<@ let x = 1 in x + 1 @>
// Was: Lambda(x => x+1).Invoke(1)
// Now: Block({ x = 1; x + 1 })
```

**Migration:** If you detected let-bindings via `Lambda.Invoke`:
```csharp
// Old: if (node.Method.Name == "Invoke" && node.Object is LambdaExpression)
// New: if (node.NodeType == ExpressionType.Block)
```

### Array access uses ArrayIndex

```fsharp
<@ arr.[0] @>
// Was: Call(GetArray, arr, 0)
// Now: ArrayIndex(arr, 0)
```

**Migration:**
```csharp
// Old: if (node.Method.Name == "GetArray")
// New: if (node.NodeType == ExpressionType.ArrayIndex)
```

---

## IQueryable Type Preservation ๐ŸŸก

Tuple projections now preserve the provider's `IQueryable` type.

```fsharp
query { for p in db.Products do select (p.Id, p.Name) }
// Was: EnumerableQuery<_>
// Now: Provider's IQueryable<_>
```

**Impact:** Code checking `is EnumerableQuery<_>` may need adjustment.

---

## FCS API: Query Variables ๐ŸŸข

Query variables now report `IsCompilerGenerated = true`. Fixes #422 (FS1182 false positives).

---

### Release Notes .FSharp.Compiler.Service 10.0.100 (`docs/release-notes/.FSharp.Compiler.Service/10.0.100.md`)

### Added
* Symbols: add Mfv.ApparentEnclosingType ([PR #17494](https://github.com/dotnet/fsharp/pull/17494))
* Add opt-in warning attribute not valid for union case with fields [PR #18532](https://github.com/dotnet/fsharp/pull/18532))
* Add support for `when 'T : Enum` library-only static optimization constraint. ([PR #18546](https://github.com/dotnet/fsharp/pull/18546))
* Add support for tail calls in computation expressions ([PR #18804](https://github.com/dotnet/fsharp/pull/18804))
* Add `--typecheck-only` flag support for F# Interactive (FSI) scripts to type-check without execution. ([Issue #18686](https://github.com/dotnet/fsharp/issues/18686), [PR #18687](https://github.com/dotnet/fsharp/pull/18687))
* Diagnostics: add extended data for 'No constructors' error ([PR #18863](https://github.com/dotnet/fsharp/pull/18863))
* FSharpType.Format: support top-level prefix generic types style. ([PR #18897](https://github.com/dotnet/fsharp/pull/18897))
* FCS: allow getting captured types ([PR #18878](https://github.com/dotnet/fsharp/pull/18878))

### Fixed

* Fix F# compiler to prevent tail call emission when pinned locals are present ([PR #18893](https://github.com/dotnet/fsharp/pull/18893))
* Fix SignatureHash to include constant values in hash computation ([Issue #18758](https://github.com/dotnet/fsharp/issues/18758), [PR #18771](https://github.com/dotnet/fsharp/pull/18771))
* Fix parsing errors using anonymous records and units of measures ([PR #18543](https://github.com/dotnet/fsharp/pull/18543))
* Fix parsing errors using anonymous records and code quotations ([PR #18603](https://github.com/dotnet/fsharp/pull/18603))
* Better error message for attribute targets. ([PR #18641](https://github.com/dotnet/fsharp/pull/18641))
* Fixed: Allow `return`, `return!`, `yield`, `yield!` type annotations without parentheses ([PR #18533](https://github.com/dotnet/fsharp/pull/18533))
* Allow `let!`, `use!`, `and!` type annotations without requiring parentheses (([PR #18508](https://github.com/dotnet/fsharp/pull/18508) and [PR #18682](https://github.com/dotnet/fsharp/pull/18682)))
* Fix find all references for F# exceptions ([PR #18565](https://github.com/dotnet/fsharp/pull/18565))
* Shorthand lambda: fix completion for chained calls and analysis for unfinished expression ([PR #18560](https://github.com/dotnet/fsharp/pull/18560))
* Completion: fix previous namespace considered opened [PR #18609](https://github.com/dotnet/fsharp/pull/18609)
* Fix active pattern typechecking regression. ([Issue #18638](https://github.com/dotnet/fsharp/issues/18638), [PR #18642](https://github.com/dotnet/fsharp/pull/18642))
* Fix nullness warnings when casting non-nullable values to `IEquatable<T>` to match C# behavior. ([Issue #18759](https://github.com/dotnet/fsharp/issues/18759), [PR #18770](https://github.com/dotnet/fsharp/pull/18770))
* Error on invalid declarations in type definitions.([Issue #10066](https://github.com/dotnet/fsharp/issues/10066), [PR #18813](https://github.com/dotnet/fsharp/pull/18813))
* Fix IsByRefLikeAttribute types being incorrectly suppressed in completion lists. Types like `Span<T>` and `ReadOnlySpan<T>` now appear correctly in IntelliSense. ([PR #18784](https://github.com/dotnet/fsharp/pull/18784))
* Fix SRTP nullness constraint resolution for types imported from older assemblies. AmbivalentToNull types now use legacy F# nullness rules instead of always satisfying `'T : null` constraints. ([Issue #18390](https://github.com/dotnet/fsharp/issues/18390), [Issue #18344](https://github.com/dotnet/fsharp/issues/18344), [PR #18785](https://github.com/dotnet/fsharp/pull/18785))
* Fix Show XML doc for enum fields in external metadata ([Issue #17939](https://github.com/dotnet/fsharp/issues/17939#issuecomment-3137410105), [PR #18800](https://github.com/dotnet/fsharp/pull/18800))
* Fix nullable types formatting in `FSharpType.Format` and tooltips to include parentheses. ([PR #18842](https://github.com/dotnet/fsharp/pull/18842))
* TypeMismatchDiagnosticExtendedData: fix expected and actual types calculation. ([PR #18851](https://github.com/dotnet/fsharp/pull/18851))
* Format top-level generic types using a prefix style in inherit/interface declarations and flexible type annotations. ([PR #18897](https://github.com/dotnet/fsharp/pull/18897))
* Parser: fix range for computed binding expressions ([PR #18903](https://github.com/dotnet/fsharp/pull/18903))
* Tests: set test source for range debug printing ([PR #18879](https://github.com/dotnet/fsharp/pull/18879))
* Checker: fix declaring type for abbreviated types extensions ([PR #18909](https://github.com/dotnet/fsharp/pull/18909))
* Caches: type subsumption cache key perf regression ([Issue #18925](https://github.com/dotnet/fsharp/issues/18925) [PR #18926](https://github.com/dotnet/fsharp/pull/18926))
* Ensure that line directives are applied to source identifiers (issue [#18908](https://github.com/dotnet/fsharp/issues/18908), PR [#18918](https://github.com/dotnet/fsharp/pull/18918))
* Fix expected and actual types in ErrorFromAddingTypeEquation message and extended diagnostic data. ([PR #18915](https://github.com/dotnet/fsharp/pull/18915))
* Editor: Fix Record fields completion in update record with partial field name. ([PR #18946](https://github.com/dotnet/fsharp/pull/18946))

### Changed
* Use `errorR` instead of `error` in `CheckDeclarations.fs` when possible. ([PR #18645](https://github.com/dotnet/fsharp/pull/18645))
* Parser: Capture named fields block separators. ([PR #18857](https://github.com/dotnet/fsharp/pull/18857))
* Type checker: use inner expr range in upcast constraints errors ([PR #18850](https://github.com/dotnet/fsharp/pull/18850))
* Import `IEnumerable` as `seq`. ([PR #18865](https://github.com/dotnet/fsharp/pull/18865))

### Breaking Changes

* Scoped Nowarn: Add the #warnon compiler directive ([Language suggestion #278](https://github.com/fsharp/fslang-suggestions/issues/278), [RFC FS-1146 PR](https://github.com/fsharp/fslang-design/pull/782), [PR #18049](https://github.com/dotnet/fsharp/pull/18049) and [PR #18637](https://github.com/dotnet/fsharp/pull/18637))
* Simplify creation of `FSharpDiagnostics`. In a few cases, errors without ranges were assigned to the currently checked file, while in other cases they carried an empty range. The latter is now true in all cases. In a few cases, ranges at eof were corrected, while in others they were not. They are now always left uncorrected. This is a prerequisit for [#18553](https://github.com/dotnet/fsharp/issues/18553). ([PR #18610](https://github.com/dotnet/fsharp/pull/18610)).
* `SynExprRecordField` now includes a `range` field ([PR #18617](https://github.com/dotnet/fsharp/pull/18617))
* Mark `Range.Zero` as obsolete in favor of `Range.range0` ([PR #18664](https://github.com/dotnet/fsharp/pull/18664))
* Use `Synbinding` to model `and!` ([PR #18805](https://github.com/dotnet/fsharp/pull/18805))
* Redesign #line processing. The original positions (unaffected by #line directives) are now kept in the AST, and `__LINE__` and `__SOURCE_LINE__` show the original line numbers / file names. However, all diagnostics and debug information stays the same (shows the position transformed by the #line directives). ([Issue #18553](https://github.com/dotnet/fsharp/issues/18553), [PR #18699](https://github.com/dotnet/fsharp/pull/18699), [PR 18828](https://github.com/dotnet/fsharp/pull/18828), [PR 18829](https://github.com/dotnet/fsharp/pull/18829))
* Unify `let`, `let!`, `use` and `use!` AST representation. ([PR #18825](https://github.com/dotnet/fsharp/pull/18825))[^1]

### Migration Guidance for AST Users

**Note:** The unified AST introduces two new boolean fields:
- `isFromSource`: Indicates if the binding comes from user-written code (`true`) or is compiler-generated (`false`)
- `isBang`: Distinguishes computation expression bindings (`let!`/`use!` = `true`) from regular bindings (`let`/`use` = `false`)

### 1. Pattern Matching Updates

**Before:**
```fsharp
match expr with
| SynExpr.LetOrUse(isRec, isUse, bindings, body, range, trivia) ->
    // Handle regular let/use
| SynExpr.LetOrUseBang(spBind, isUse, isFromSource, pat, rhs, andBangs, body, range, trivia) ->
    // Handle let!/use!
```

**After:**
```fsharp
match expr with
| SynExpr.LetOrUse(isRec, isUse, isFromSource, isBang, bindings, body, range, trivia) ->
    if isBang then
        // This is a let!/use! expression
        match bindings with
        | firstBinding :: andBangs ->
            match firstBinding with
            | SynBinding(headPat = pat; expr = rhs) ->
                // pat and rhs extracted from first binding
                // andBangs contains the and! bindings
        | [] -> // error case
    else
        // This is a regular let/use expression
```

### 2. Construction Updates

**Before:**
```fsharp
// Creating a let! expression
SynExpr.LetOrUseBang(
    bindDebugPoint,
    false,  // isUse
    true,   // isFromSource
    pat,
    rhsExpr,
    andBangs,
    bodyExpr,
    range,
    trivia
)
```

**After:**
```fsharp
// Creating a let! expression
let firstBinding = SynBinding(
    accessibility = None,
    kind = SynBindingKind.Normal,
    isInline = false,
    isMutable = false,
    attributes = [],
    xmlDoc = PreXmlDoc.Empty,
    valData = SynInfo.emptySynValData,
    headPat = pat,           // Pattern moved here
    returnInfo = None,
    expr = rhsExpr,          // RHS moved here
    range = range,
    debugPoint = bindDebugPoint,  // Debug point moved here
    trivia = bindingTrivia
)
SynExpr.LetOrUse(
    false,  // isRecursive
    false,  // isUse
    true,   // isFromSource
    true,   // isBang (indicates let!)
    firstBinding :: andBangs,  // All bindings in single list
    bodyExpr,
    range,
    trivia
)
```

### 3. Common Migration Patterns

**Checking for computation expressions:**
```fsharp
// Before
match expr with
| SynExpr.LetOrUseBang _ -> true
| _ -> false

// After
match expr with
| SynExpr.LetOrUse(isBang = true) -> true
| _ -> false
```

**Extracting pattern and expression from let!:**
```fsharp
// Before
| SynExpr.LetOrUseBang(_, _, _, pat, rhs, _, _, _, _) ->
    processBinding pat rhs

// After
| SynExpr.LetOrUse(isBang = true; bindings = binding :: _) ->
    match binding with
    | SynBinding(headPat = pat; expr = rhs) ->
        processBinding pat rhs
    | _ -> // error
```

**Processing and! bindings:**
```fsharp
// Before
| SynExpr.LetOrUseBang(_, _, _, firstPat, firstRhs, andBangs, _, _, _) ->
    processFirst firstPat firstRhs
    for andBang in andBangs do
        processAndBang andBang

// After
| SynExpr.LetOrUse(isBang = true; bindings = bindings) ->
    match bindings with
    | first :: rest ->
        processBinding first
        for andBang in rest do
            processAndBang andBang
    | [] -> // error
```

[^1]: See [Migration Guidance for AST Users](#migration-guidance-for-ast-users) section for detailed information on how to update your code to work with the unified AST representation.

---

### Release Notes .FSharp.Compiler.Service 10.0.200 (`docs/release-notes/.FSharp.Compiler.Service/10.0.200.md`)

### Fixed

* Fixed SRTP resolution regression causing FS0030 value restriction errors with FSharpPlus curryN-style patterns in .NET 9 SDK. ([PR #19218](https://github.com/dotnet/fsharp/pull/19218))
* Fix FS3261 nullness warning when implementing INotifyPropertyChanged or ICommand CLIEvent properties. ([Issue #18361](https://github.com/dotnet/fsharp/issues/18361), [Issue #18349](https://github.com/dotnet/fsharp/issues/18349), [PR #19221](https://github.com/dotnet/fsharp/pull/19221))
* Type relations cache: optimize key generation ([Issue #19116](https://github.com/dotnet/fsharp/issues/18767)) ([PR #19120](https://github.com/dotnet/fsharp/pull/19120))
* Fixed QuickParse to correctly handle optional parameter syntax with `?` prefix, resolving syntax highlighting issues. ([Issue #11008753](https://developercommunity.visualstudio.com/t/F-Highlighting-fails-on-optional-parame/11008753)) ([PR #19162](https://github.com/dotnet/fsharp/pull/19162))
* Fix `--preferreduilang` switch leaking into `fsi.CommandLineArgs` when positioned after script file ([PR #19151](https://github.com/dotnet/fsharp/pull/19151))
* Optimize empty string pattern matching to use null-safe .Length check instead of string equality comparison for better performance. ([PR #19189](https://github.com/dotnet/fsharp/pull/19189))
* Fixed runtime crash when using interfaces with unimplemented static abstract members as constrained type arguments. ([Issue #19184](https://github.com/dotnet/fsharp/issues/19184), [PR #19185](https://github.com/dotnet/fsharp/pull/19185))
* Fix delegates with `[<OptionalArgument>]` and caller info attributes failing to compile. ([Issue #18868](https://github.com/dotnet/fsharp/issues/18868), [PR #19069](https://github.com/dotnet/fsharp/pull/19069))
* Type checker: mark generated event tree nodes as synthetic ([PR #19213](https://github.com/dotnet/fsharp/pull/19213))
* Nullness: Fix nullness refinement in match expressions to correctly narrow type to non-null after matching null case. ([Issue #18488](https://github.com/dotnet/fsharp/issues/18488), [PR #18852](https://github.com/dotnet/fsharp/pull/18852))
* Scripts: Fix resolving the dotnet host path when an SDK directory is specified. ([PR #18960](https://github.com/dotnet/fsharp/pull/18960))
* Fix excessive StackGuard thread jumping ([PR #18971](https://github.com/dotnet/fsharp/pull/18971))
* Adjust conservative method-overload duplicate detection rules for nativeptr types ([PR #18911](https://github.com/dotnet/fsharp/pull/18911))
* Checking: Fix checking nested fields for records and anonymous ([PR #18964](https://github.com/dotnet/fsharp/pull/18964))
* Fix name is bound multiple times is not reported in 'as' pattern ([PR #18984](https://github.com/dotnet/fsharp/pull/18984))
* Syntax Tree: fix return type info for let! / and! / use! ([PR #19004](https://github.com/dotnet/fsharp/pull/19004))
* Fix: warn FS0049 on upper union case label. ([PR #19003](https://github.com/dotnet/fsharp/pull/19003))
* Type relations cache: handle potentially "infinite" types ([PR #19010](https://github.com/dotnet/fsharp/pull/19010))
* Disallow recursive structs with lifted type parameters ([Issue #18993](https://github.com/dotnet/fsharp/issues/18993), [PR #19031](https://github.com/dotnet/fsharp/pull/19031))
* Fix units-of-measure changes not invalidating incremental builds. ([Issue #19049](https://github.com/dotnet/fsharp/issues/19049), [PR #19050](https://github.com/dotnet/fsharp/pull/19050))
* Fix race in graph checking of type extensions. ([PR #19062](https://github.com/dotnet/fsharp/pull/19062))
* Type relations cache: handle unsolved type variables ([Issue #19037](https://github.com/dotnet/fsharp/issues/19037)) ([PR #19040](https://github.com/dotnet/fsharp/pull/19040))
* Fix insertion context for modules with multiline attributes. ([Issue #18671](https://github.com/dotnet/fsharp/issues/18671), [PR #19066](https://github.com/dotnet/fsharp/pull/19066))
* Fix `--typecheck-only` for scripts stopping after processing `#load`-ed script ([PR #19048](https://github.com/dotnet/fsharp/pull/19048))
* Fix object expressions in struct types generating invalid IL with byref fields causing TypeLoadException at runtime. ([Issue #19068](https://github.com/dotnet/fsharp/issues/19068), [PR #19070](https://github.com/dotnet/fsharp/pull/19070))
* Fix duplicate .cctor issue for discriminated unions with generic statics ([Issue #18767](https://github.com/dotnet/fsharp/issues/18767), [PR #18801](https://github.com/dotnet/fsharp/pull/18801))
* Fix early/unconditional execution of PackageFSharpDesignTimeTools target. ([Issue #18924](https://github.com/dotnet/fsharp/issues/18924), [Issue #12320](https://github.com/dotnet/fsharp/issues/12320), [PR #18929](https://github.com/dotnet/fsharp/pull/18929))

### Added

* Detect and error on static extension members extending types with the same simple name but different namespaces in the same module. ([PR #18821](https://github.com/dotnet/fsharp/pull/18821))
* FSharpDiagnostic: add default severity ([#19152](https://github.com/dotnet/fsharp/pull/19152))
* Add warning FS3879 for XML documentation comments not positioned as first non-whitespace on line. ([PR #18891](https://github.com/dotnet/fsharp/pull/18891))
* FsiEvaluationSession.ParseAndCheckInteraction: add keepAssemblyContents optional parameter ([#19155](https://github.com/dotnet/fsharp/pull/19155))
* Add FSharpCodeCompletionOptions ([PR #19030](https://github.com/dotnet/fsharp/pull/19030))
* Type checker: recover on checking binding parameter constraints ([#19046](https://github.com/dotnet/fsharp/pull/19046))
* Debugger: provide breakpoint ranges for short lambdas ([#19067](https://github.com/dotnet/fsharp/pull/19067))
* Add support for triple quoted ASCII byte string ([#19182](https://github.com/dotnet/fsharp/pull/19182))

### Changed

* Parallel compilation features: ref resolution, graph based checking, ILXGen and optimization enabled by default ([PR #18998](https://github.com/dotnet/fsharp/pull/18998))
* Make graph based type checking and parallel optimizations deterministic ([PR #19028](https://github.com/dotnet/fsharp/pull/19028))
* Centralize compiler's target framework moniker logic into a single source of truth. ([PR #19251](https://github.com/dotnet/fsharp/pull/19251))

### Breaking Changes

* `SynExpr.LetOrUse` holds `SynLetOrUse`. ([PR #19090](https://github.com/dotnet/fsharp/pull/19090))
* `SynExprLetOrUseTrivia` is now `SynLetOrUseTrivia`. ([PR #19090](https://github.com/dotnet/fsharp/pull/19090))
* `SynMemberDefn.LetBindings` has trivia. ([PR #19090](https://github.com/dotnet/fsharp/pull/19090))
* `SynModuleDecl.Let` has trivia. ([PR #19090](https://github.com/dotnet/fsharp/pull/19090))
* Removed support for `.ml` and `.mli` source files. ([PR #19143](https://github.com/dotnet/fsharp/pull/19143))
* Removed `#light` and `#indent` directives (they are now a no-op; combined with `off` they give an error). ([PR #19143](https://github.com/dotnet/fsharp/pull/19143))
* Removed `--light`, `--indentation-syntax`, `--no-indendation-syntax`, `--ml-keywords` and `--mlcompatibility` compiler/fsi flags. ([PR #19143](https://github.com/dotnet/fsharp/pull/19143))
* Removed parsing support for long-deprecated ML (non-light) constructs. ([PR #19143](https://github.com/dotnet/fsharp/pull/19143))

---

### Release Notes .FSharp.Compiler.Service 10.0.300 (`docs/release-notes/.FSharp.Compiler.Service/10.0.300.md`)

### Fixed

* Fix strong name signature size to align with Roslyn for public signing ([Issue #17451](https://github.com/dotnet/fsharp/issues/17451), [PR #19242](https://github.com/dotnet/fsharp/pull/19242))
* Nullness: Fix UoM ToString returning `string | null` for value types. ([Issue #17539](https://github.com/dotnet/fsharp/issues/17539), [PR #19262](https://github.com/dotnet/fsharp/pull/19262))
* Nullness: Fix pipe operator nullness warning location to point at nullable argument. ([Issue #18013](https://github.com/dotnet/fsharp/issues/18013), [PR #19262](https://github.com/dotnet/fsharp/pull/19262))
* Nullness: Fix false positive warning when passing non-null AllowNullLiteral constructor result. ([Issue #18021](https://github.com/dotnet/fsharp/issues/18021), [PR #19262](https://github.com/dotnet/fsharp/pull/19262))
* Nullness: Allow `not null` constraint on type extensions. ([Issue #18334](https://github.com/dotnet/fsharp/issues/18334), [PR #19262](https://github.com/dotnet/fsharp/pull/19262))
* Nullness: Simplify tuple null elimination to prevent over-inference of non-null. ([Issue #19042](https://github.com/dotnet/fsharp/issues/19042), [PR #19262](https://github.com/dotnet/fsharp/pull/19262))
* Fixed Find All References not correctly finding active pattern cases in signature files. ([Issue #19173](https://github.com/dotnet/fsharp/issues/19173), [Issue #14969](https://github.com/dotnet/fsharp/issues/14969), [PR #19252](https://github.com/dotnet/fsharp/pull/19252))
* Fixed Rename not correctly handling operators containing `.` (e.g., `-.-`). ([Issue #17221](https://github.com/dotnet/fsharp/issues/17221), [Issue #14057](https://github.com/dotnet/fsharp/issues/14057), [PR #19252](https://github.com/dotnet/fsharp/pull/19252))
* Fixed Find All References not correctly applying `#line` directive remapping. ([Issue #9928](https://github.com/dotnet/fsharp/issues/9928), [PR #19252](https://github.com/dotnet/fsharp/pull/19252))
* Fixed `SynPat.Or` pattern variables (non-left-most) incorrectly classified as bindings instead of uses. ([Issue #5546](https://github.com/dotnet/fsharp/issues/5546), [PR #19252](https://github.com/dotnet/fsharp/pull/19252))
* Fixed Find All References not finding discriminated union types defined inside modules. ([Issue #5545](https://github.com/dotnet/fsharp/issues/5545), [PR #19252](https://github.com/dotnet/fsharp/pull/19252))
* Fixed synthetic event handler values appearing in Find All References results. ([Issue #4136](https://github.com/dotnet/fsharp/issues/4136), [PR #19252](https://github.com/dotnet/fsharp/pull/19252))
* Fixed Find All References not finding all usages of C# extension methods. ([Issue #16993](https://github.com/dotnet/fsharp/issues/16993), [PR #19252](https://github.com/dotnet/fsharp/pull/19252))
* Fixed Find All References on discriminated union cases not including case tester properties (e.g., `.IsCase`). ([Issue #16621](https://github.com/dotnet/fsharp/issues/16621), [PR #19252](https://github.com/dotnet/fsharp/pull/19252))
* Fixed Find All References on record types not including copy-and-update expressions. ([Issue #15290](https://github.com/dotnet/fsharp/issues/15290), [PR #19252](https://github.com/dotnet/fsharp/pull/19252))
* Fixed Find All References on constructor definitions not finding all constructor usages. ([Issue #14902](https://github.com/dotnet/fsharp/issues/14902), [PR #19252](https://github.com/dotnet/fsharp/pull/19252))
* Fixed Find All References producing corrupted duplicate constructor symbol references with shifted ranges, and removed duplicate attribute symbols from type definition error-reporting. ([Issue #19336](https://github.com/dotnet/fsharp/issues/19336), [PR #19358](https://github.com/dotnet/fsharp/pull/19358))
* Fixed semantic classification regression where copy-and-update record fields were colored as type names, and union case tester dot was colored as union case. ([PR #19311](https://github.com/dotnet/fsharp/pull/19311))
* Fix false FS1182 (unused variable) warning for query expression variables used in where, let, join, and select clauses. ([Issue #422](https://github.com/dotnet/fsharp/issues/422))
* Fix FS0229 B-stream misalignment when reading metadata from assemblies compiled with LangVersion < 9.0, introduced by [#17706](https://github.com/dotnet/fsharp/pull/17706). ([PR #19260](https://github.com/dotnet/fsharp/pull/19260))
* Fix FS3356 false positive for instance extension members with same name on different types, introduced by [#18821](https://github.com/dotnet/fsharp/pull/18821). ([PR #19260](https://github.com/dotnet/fsharp/pull/19260))
* Fix graph-based type checking incorrectly resolving dependencies when the same module name is defined across multiple files in the same namespace. ([PR #19280](https://github.com/dotnet/fsharp/pull/19280))
* F# Scripts: Fix default reference paths resolving when an SDK directory is specified. ([PR #19270](https://github.com/dotnet/fsharp/pull/19270))
* Improve static compilation of state machines. ([PR #19297](https://github.com/dotnet/fsharp/pull/19297))
* Fix a bug where `let!` and `use!` were incorrectly allowed outside computation expressions. [PR #19347](https://github.com/dotnet/fsharp/pull/19347)
* Fix TypeLoadException when creating delegate with voidptr parameter. (Issue [#11132](https://github.com/dotnet/fsharp/issues/11132), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
* Suppress tail calls when localloc (NativePtr.stackalloc) is used. (Issue [#13447](https://github.com/dotnet/fsharp/issues/13447), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
* Fix TypeLoadException in Release builds with inline constraints. (Issue [#14492](https://github.com/dotnet/fsharp/issues/14492), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
* Fix nativeptr in interfaces leads to TypeLoadException. (Issue [#14508](https://github.com/dotnet/fsharp/issues/14508), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
* Fix box instruction for literal upcasts. (Issue [#18319](https://github.com/dotnet/fsharp/issues/18319), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
* Fix Decimal Literal causes InvalidProgramException in Debug builds. (Issue [#18956](https://github.com/dotnet/fsharp/issues/18956), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))

### Added
* FSharpType: add ImportILType ([PR #19300](https://github.com/dotnet/fsharp/pull/19300))
* Type checker: recover on argument/overload checking ([PR #19314](https://github.com/dotnet/fsharp/pull/19314))
* FSharpType: add more predefined type checks ([PR #19325](https://github.com/dotnet/fsharp/pull/19325))
* Introduced a separate `NotifyRelatedSymbolUse` sink and `[<Flags>] RelatedSymbolUseKind` enum for related symbol lookups (union case testers, copy-and-update record types). `GetUsesOfSymbolInFile` and `GetSemanticClassification` accept an optional `relatedSymbolKinds` parameter to opt in. ([PR #19361](https://github.com/dotnet/fsharp/pull/19361))

* Support `#exit;;` as alias to `#quit;;` in F# Interactive. ([PR #19329](https://github.com/dotnet/fsharp/pull/19329))

* FCS: capture additional types during analysis ([PR #19305](https://github.com/dotnet/fsharp/pull/19305))

### Changed

* Centralized product TFM (Target Framework Moniker) into MSBuild props file `eng/TargetFrameworks.props`. Changing the target framework now only requires editing one file, and it integrates with MSBuild's `--getProperty` for scripts.
* Overload resolution results are now cached, providing compile time improvements for code with repeated method calls. ([Issue #18807](https://github.com/dotnet/fsharp/issues/18807))
* Symbols: safer qualified name getting ([PR #19298](https://github.com/dotnet/fsharp/pull/19298))


### Breaking Changes

---

### Release Notes .FSharp.Compiler.Service 11.0.100 (`docs/release-notes/.FSharp.Compiler.Service/11.0.100.md`)

### Fixed

* Fix internal error "no 'value__' field found for enumeration type" when an `enum` constraint is checked against an enum declared in the same recursive group (`module rec` or an `and` group). The constraint is now solved once the representations of the group are established. ([Issue #14580](https://github.com/dotnet/fsharp/issues/14580), [PR #20454](https://github.com/dotnet/fsharp/pull/20454))
* Better diagnostic for a bare `enum` constraint: `'T : enum` now reports FS0699 "An 'enum' constraint must be of the form 'enum<type>'" instead of "Unexpected identifier: 'enum (4)'", and parser messages for invalid constraints no longer leak internal ` (2)`/` (3)`/` (4)` markers. ([Issue #14580](https://github.com/dotnet/fsharp/issues/14580), [PR #20454](https://github.com/dotnet/fsharp/pull/20454))
* Fix FSI `--multiemit+ --debug-` unnecessarily generating and loading portable PDB data for every submission. ([Issue #17306](https://github.com/dotnet/fsharp/issues/17306), [PR #20394](https://github.com/dotnet/fsharp/pull/20394), [downstream report](https://github.com/bryanedds/Nu/issues/1090))
* Fix reference-assembly MVID collisions when a public member is renamed in an early file of a large (>~32 file) project under `--optimize-`. The per-file signature hashes were folded with a left-shift-by-one combiner over a 32-bit `Hash`, which truncated the contribution of any file more than ~32 positions from the end of the compile order, so MSBuild's `CopyRefAssembly` saw an unchanged MVID and kept a stale reference assembly (surfacing as `FS0039` downstream). The combiner is now an FNV-1a multiply mix over a 64-bit `Hash`. ([Issue #20389](https://github.com/dotnet/fsharp/issues/20389))
* Fix internal error FS0192 "Iterate2D" when a `[<ReflectedDefinition(true)>]` parameter auto-quotes an argument that captures a not-yet-generalized use of an inferred generically-recursive function. The auto-quoted (`Expr.WithValue`) copy now keeps a fresh link to the recursive-value use so it receives the same inferred type arguments as the executable expression at the letrec point. ([Issue #20379](https://github.com/dotnet/fsharp/issues/20379))
* Fix `StackOverflowException` when checking a long `seq { ... }` body. ([PR #20480](https://github.com/dotnet/fsharp/pull/20480))
* Fix `NativePtr.stackalloc` nested in a larger expression (e.g. a call argument or the right of an assignment) producing an assembly that throws `InvalidProgramException` at load. ([Issue #8083](https://github.com/dotnet/fsharp/issues/8083), [PR #20302](https://github.com/dotnet/fsharp/pull/20302))
* Fix internal error "Unexpected generalized type variables when compiling an active pattern" when an active pattern is used in a `let` binding whose right-hand side is a generic value, e.g. `let (T) = id`. Such a binding is now checked like the equivalent `match` and is not generalized. ([Issue #16856](https://github.com/dotnet/fsharp/issues/16856), [PR #20383](https://github.com/dotnet/fsharp/pull/20383))
* Fix Release-only (`--optimize+`) `System.InvalidProgramException` from `Seq.collect` / `yield!` over a value-type (struct) collection implementing `seq<'T>` (e.g. `ImmutableArray<_>`) when materialised with `List.ofSeq` / `Seq.toList` / `Seq.toArray` or a list/array comprehension. The collector lowering now boxes a struct sub-collection to `seq<'T>` before calling `AddMany`/`AddManyAndClose` (matching the coercion the type checker already inserts for `yield!`), and uses `unit` as the try/finally result type instead of the body type (removing a spurious `ldnull` store). ([Issue #20203](https://github.com/dotnet/fsharp/issues/20203))
* Fix recursive inline SRTP resolution being truncated by one currying level (e.g. FSharpPlus `memoizeN`), a regression from the function-domain unification order change in [PR #15181](https://github.com/dotnet/fsharp/pull/15181); the contravariant domain now keeps the inference variable that still carries the pending member constraint. ([PR #20247](https://github.com/dotnet/fsharp/pull/20247))
* Fix exponential (2^N) compile time in pattern matching with shared guards and partial active patterns. ([Issue #18425](https://github.com/dotnet/fsharp/issues/18425), [PR #20244](https://github.com/dotnet/fsharp/pull/20244))
* Fix incorrect Debug lowering of inline builders that compose low-level resumable state machines. ([Issue #20466](https://github.com/dotnet/fsharp/issues/20466), [PR #20469](https://github.com/dotnet/fsharp/pull/20469))
* Fix incorrect `StructLayout(Size = 1)` emission for data-less struct unions where the compiler-generated tag field makes the actual runtime size larger. ([PR #19759](https://github.com/dotnet/fsharp/pull/19759))
* Embed F# metadata-removal rules before resource collection, preserve custom rules, and combine generated rules when linking assemblies. ([PR #20527](https://github.com/dotnet/fsharp/pull/20527))
* Fix FS0750 "This construct may only be used within computation expressions" incorrectly raised for `let!`/`use!`/`do!` appearing in the right-hand side of a plain `let` binding inside a computation expression. The right-hand side is now desugared as a nested computation of the same builder whose result is bound with `let!`, keeping its bindings correctly scoped. ([Issue #19457](https://github.com/dotnet/fsharp/issues/19457), [PR #19868](https://github.com/dotnet/fsharp/pull/19868))
* Fix `use` bindings for `IAsyncDisposable` in `dotnet fsi` task expressions by shipping the `net10.0` FSharp.Core in the SDK toolset. ([Issue #14454](https://github.com/dotnet/fsharp/issues/14454), [PR #20555](https://github.com/dotnet/fsharp/pull/20555))
* Stop leaking a `System.Diagnostics.Metrics.MeterListener` per `Cache` in DEBUG builds. Each cache created a `CacheMetrics.CacheMetricsListener` (which starts a `MeterListener` registered in the process-global metrics registry) and never disposed it, so listeners accumulated for the lifetime of the process. Because every cache hit/miss/add published to all registered listeners, the per-operation cost grew linearly with the number of leaked listeners, so repeated checks (and Debug FCS test runs) slowed down over time. The per-cache `CacheMetricsListener` and the per-instance `cacheId` tag are removed; `DebugDisplay` and tests now read the existing name-aggregated stats populated by the single `ListenToAll` listener, so no per-cache listener is created and no per-operation cost is added. ([PR #19995](https://github.com/dotnet/fsharp/pull/19995))
* Fix state machine lowering dropping the side-effectful receiver of an unused unit-typed member access (e.g. inside `task { (effectful()).UnitProp }`). ([Issue #13099](https://github.com/dotnet/fsharp/issues/13099), [PR #19885](https://github.com/dotnet/fsharp/pull/19885))
* `--deterministic` Release builds now produce byte-identical `FSharp.Compiler.Service.dll` under `--parallelcompilation+` and `--parallelcompilation-`, so it is restored to the determinism gate (now also checked sequential-vs-parallel). Code generation runs the same deferred per-file drain in both modes, with type/member/field emit-order keys and generated names derived from the file being emitted rather than thread-scheduling order. ([Issue #19928](https://github.com/dotnet/fsharp/issues/19928), [PR #19929](https://github.com/dotnet/fsharp/pull/19929))
* Fix `[<CompiledName>]` silently producing duplicate IL entries (FS0192/FS2014) when applied to a multi-value let-binding (e.g. `let a, b = 1, 2`); now emits FS0755 at type-check time. ([Issue #6131](https://github.com/dotnet/fsharp/issues/6131), [PR #19924](https://github.com/dotnet/fsharp/pull/19924))
* Fix FS3236 when taking the address of an untyped generalized `let` binding (e.g. `let ffff = ValueNone`) passed to an `inref` parameter. ([Issue #19608](https://github.com/dotnet/fsharp/issues/19608), [PR #19948](https://github.com/dotnet/fsharp/pull/19948))
* Fix IntelliSense not suggesting later named arguments after the first one is autocompleted, for methods (including overloaded methods like `Task.Factory.StartNew`). ([Issue #19906](https://github.com/dotnet/fsharp/issues/19906), [PR #19940](https://github.com/dotnet/fsharp/pull/19940))
* Restore packaging of an F# design-time type provider that is activated via a `ProjectReference` carrying `IsFSharpDesignTimeProvider="true"`. The provider assembly is again included under `fsharp41` when packing (including `pack --no-build`); `PackageFSharpDesignTimeTools` now resolves the provider via `GetTargetPath`, which works in `dotnet pack`'s `BuildProjectReferences=false` content build without forcing an early `ResolveReferences`. ([Issue #18924](https://github.com/dotnet/fsharp/issues/18924), [PR #19979](https://github.com/dotnet/fsharp/pull/19979))
* Provided types used from multiple files no longer produce spurious FS0001 type mismatches under parallel compilation; provided-type entities are now interned so every file linking a given provided type shares one entity. ([PR #19969](https://github.com/dotnet/fsharp/pull/19969))
* TypeProviders-SDK providers now load under an unoptimized compiler; the `systemRuntimeContainsType` closure field the SDK reflects on (`tcImports`) is captured stably regardless of optimization settings. ([PR #19969](https://github.com/dotnet/fsharp/pull/19969))
* Provided namespaces are now interned and appended atomically alongside provided types, completing the #19969 fix: under parallel compilation two files linking the same provided namespace no longer build disjoint subtrees that strand the provided types under them (spurious FS0001/FS0039). ([Issue #20020](https://github.com/dotnet/fsharp/issues/20020), [PR #20021](https://github.com/dotnet/fsharp/pull/20021))
* Fixed: Inheriting from an undefined type now reports `FS0039` exactly once instead of three times. Phase 1F and Phase 2A of inherit-clause type-checking now skip re-resolving a syntactic clause whose Phase 1D resolution already failed with `UndefinedName`, eliminating both the duplicate diagnostic and the redundant work. ([Issue #16432](https://github.com/dotnet/fsharp/issues/16432), [PR #19862](https://github.com/dotnet/fsharp/pull/19862))
* Fix several F# editor semantic-classification errors: F# delegate declarations no longer highlight the `delegate of โ€ฆ` syntax as a method, computation-expression builders inside list/array comprehensions are classified as `ComputationExpression`, the closing `]` of an open-ended slice (e.g. `xs[0..]`) is no longer classified as `Function`/`Method`, and `open type T` is no longer reported as unused when its imported members (static members, static fields, or DU union cases) are used. ([Issue #19905](https://github.com/dotnet/fsharp/issues/19905), [PR #19960](https://github.com/dotnet/fsharp/pull/19960))
* Diagnostic FS0027 now emits a parameter-specific message (suggesting a `let mutable x = x` shadow or `byref<_>`) instead of the illegal `let mutable x = expression` shadow when the assignment target is a function or method parameter. ([Issue #15803](https://github.com/dotnet/fsharp/issues/15803), [PR #19866](https://github.com/dotnet/fsharp/pull/19866))
* Recursive `inline` functions and members now emit a single clear error (FS3890) instead of a misleading FS1113/FS1114 optimizer cascade. ([Issue #17991](https://github.com/dotnet/fsharp/issues/17991), [PR #19803](https://github.com/dotnet/fsharp/pull/19803))
* Report `FS0037 Duplicate definition of type or module` at type-check time when two sibling modules in a `module rec` / `namespace rec` group share a name, instead of letting the duplicate slip through to IL emit where it surfaced as the cryptic `FS2014: duplicate entry . in type index table`. ([Issue #6694](https://github.com/dotnet/fsharp/issues/6694), [PR #19913](https://github.com/dotnet/fsharp/pull/19913))
* Unused `Unchecked.defaultof<'T>` bindings are now eliminated under optimization at their use sites. This removes redundant `initobj`/`ldnull;pop` sequences left behind after inlining SRTP helpers that use `Unchecked.defaultof` as dummy arguments to drive static-member-constraint resolution. Such bindings are preserved inside `inline` bodies (whose optimized form is pickled as cross-assembly optimization info and re-inlined by consumers) and whenever the erased type still references unsolved type variables, so no `FS0073` regression is introduced. ([Issue #18128](https://github.com/dotnet/fsharp/issues/18128), [PR #19758](https://github.com/dotnet/fsharp/pull/19758))
* Semantic classification no longer marks recursive object self-references (`as this`, `let rec` self-refs) as mutable. ([Issue #5229](https://github.com/dotnet/fsharp/issues/5229))
* Fix `MethodAccessException` under `--realsig+` when a closure (inner `let rec`, `task`/`async` state machine, or quotation splice) inside a member defined in an intrinsic type augmentation (`type C with member ...`) accesses a `private` member of `C`. The synthesized closure is now nested inside the declaring type instead of beside it in the module class. ([Issue #19933](https://github.com/dotnet/fsharp/issues/19933), [PR #19955](https://github.com/dotnet/fsharp/pull/19955))
* Preserve source range for type errors on empty-bodied computation expressions (e.g. `foo {}`) in pipelines, function arguments, and type-annotated contexts, instead of reporting `unknown(1,1)`. ([Issue #19550](https://github.com/dotnet/fsharp/issues/19550), [PR #19849](https://github.com/dotnet/fsharp/pull/19849))
* Fix multiline nested type arguments failing to parse when the closing `>` aligns with the opening type name's column. ([Issue #15171](https://github.com/dotnet/fsharp/issues/15171))
* Remove misleading FS0193 when record fields differ in order between a signature and its implementation, while retaining FS0312. ([Issue #20410](https://github.com/dotnet/fsharp/issues/20410), [PR #20559](https://github.com/dotnet/fsharp/pull/20559))
* Preserve outer rethrows and emit legal IL for nested runtime-async `try/with` after suspension. ([Issue #20575](https://github.com/dotnet/fsharp/issues/20575), [PR #20586](https://github.com/dotnet/fsharp/pull/20586))
* Tooltip "Full name" now shows demangled companion module names (e.g. `MyType.func` instead of `MyTypeModule.func`). ([Issue #17335](https://github.com/dotnet/fsharp/issues/17335), [PR #19867](https://github.com/dotnet/fsharp/pull/19867))
* Fix optional indexer setter arguments, including omitted defaults and caller information. ([Issue #20046](https://github.com/dotnet/fsharp/issues/20046), [PR #20570](https://github.com/dotnet/fsharp/pull/20570))
* Fix spurious FS0410 accessibility error when tuple-deconstructing bindings use private types in the same module scope. ([Issue #4161](https://github.com/dotnet/fsharp/issues/4161), [PR #19947](https://github.com/dotnet/fsharp/pull/19947))
* Fix internal error (FS0193) when calling an indexed property setter with a named argument that matches an indexer parameter. ([Issue #16034](https://github.com/dotnet/fsharp/issues/16034), [PR #19851](https://github.com/dotnet/fsharp/pull/19851))
* Fix missing FS1182 ("unused binding") warning for unused `let` function bindings inside class types. ([Issue #13849](https://github.com/dotnet/fsharp/issues/13849), [PR #19805](https://github.com/dotnet/fsharp/pull/19805))
* Fix internal compiler error FS1110 in `task { let! }` (and other computation expressions) when a generic IL extension method whose `this`-parameter is a method-level type variable is in scope (e.g. `open ReactiveUI`). Regression from PR #19536. ([Issue #19936](https://github.com/dotnet/fsharp/issues/19936))
* Fix inner mutually-recursive `let rec ... and ...` functions under `--realsig+` not being lifted to top-level static methods (TLR), causing `FSharpFunc` closure allocations and loss of `tail.` opcodes โ€” the large struct-mutual-recursion perf regression reported in [Issue #17607](https://github.com/dotnet/fsharp/issues/17607). ([PR #19882](https://github.com/dotnet/fsharp/pull/19882))
* Fix `TypeLoadException` ("Specialize tried to implicitly override a method with weaker type parameter constraints") and the related CLR crash with constrained inline calls by stripping constraints from closure-class typars in `EraseClosures.convIlxClosureDef`. ([Issue #14492](https://github.com/dotnet/fsharp/issues/14492), [Issue #19075](https://github.com/dotnet/fsharp/issues/19075), [PR #19882](https://github.com/dotnet/fsharp/pull/19882))
* Fix FS0073 during code generation when closure generic arguments contain an unsolved type variable. ([PR #20229](https://github.com/dotnet/fsharp/pull/20229))
* Fix `FieldAccessException` at runtime when the optimizer relocates a read of a `protected` (family) base-class field into a method outside the field's family (e.g. a trivial member inlined into module/startup code under `--optimize+`). Protected (family) IL field access is no longer hoisted out of its declaring family by inlining or method-splitting. ([Issue #19963](https://github.com/dotnet/fsharp/issues/19963), [PR #19964](https://github.com/dotnet/fsharp/pull/19964))
* Suppress hover/symbol resolution for wildcard `_` patterns inside `member _.โ€ฆ` bodies that incorrectly showed `val _: T` tooltip. ([PR #19760](https://github.com/dotnet/fsharp/pull/19760))
* Report `seq { }` implicit-yield diagnostics and format specifier locations once instead of twice: the classification pass that decides whether the body is a statement or a yielded element no longer double-reports to the sink or diagnostics. ([Issue #16419](https://github.com/dotnet/fsharp/issues/16419), [PR #19791](https://github.com/dotnet/fsharp/pull/19791), [PR #19895](https://github.com/dotnet/fsharp/pull/19895))
* Fix false FS3261 nullness warnings for union types in recursive declaration groups. Preserve overload selection and valid warnings when representation attributes use same-group type abbreviations, without reporting deferred warnings again for annotations already checked. ([Issue #20211](https://github.com/dotnet/fsharp/issues/20211), [PR #20562](https://github.com/dotnet/fsharp/pull/20562))
* Stabilize codegen order under `--parallelcompilation+` so `--deterministic` Release builds produce byte-identical IL across rebuilds: optimizer Val iteration, IlxGen type/method/field/event emit order, anonymous-record extra-binding drain, and `FileIndex` assignment now follow source position rather than thread-scheduling order. ([Issue #19732](https://github.com/dotnet/fsharp/issues/19732), [PR #19810](https://github.com/dotnet/fsharp/pull/19810))
* Reject non-function bindings for single-case and partial active pattern names with FS1209, matching the existing multi-case behavior. ([PR #19763](https://github.com/dotnet/fsharp/pull/19763))
* Fix FS0421 "The address of the variable cannot be used at this point" incorrectly raised for the discard pattern `let _ = &expr` when `let x = &expr` compiles. ([Issue #18841](https://github.com/dotnet/fsharp/issues/18841), [PR #19811](https://github.com/dotnet/fsharp/pull/19811))
* Fix double `Dispose` call when a `use` binding aliases its value via an `as` pattern or rebinds an existing `use`-bound value. ([Issue #12300](https://github.com/dotnet/fsharp/issues/12300), [PR #19858](https://github.com/dotnet/fsharp/pull/19858))
* Honor `--nowarn` and `--warnaserror` for warnings emitted during command-line option parsing ([Issue #19576](https://github.com/dotnet/fsharp/issues/19576), [PR #19776](https://github.com/dotnet/fsharp/pull/19776))
* Bitwise operators (`|||`, `&&&`, `^^^`) applied to enums with a non-integral underlying type (e.g. `char`) are now rejected at compile time with FS0001 instead of compiling and failing at runtime with `NotSupportedException`. Gated behind the `ErrorOnBitwiseOpsOnNonIntegralEnums` language feature (F# 11.0). ([Issue #11785](https://github.com/dotnet/fsharp/issues/11785), [PR #20322](https://github.com/dotnet/fsharp/pull/20322))
* Fix `[<return: X>]` prefix attributes being silently dropped on class members, and fix false-positive `AllowMultiple=false` errors when `[<X>]` and `[<return: X>]` are applied to the same binding. ([Issue #17904](https://github.com/dotnet/fsharp/issues/17904), [Issue #19020](https://github.com/dotnet/fsharp/issues/19020), [PR #19738](https://github.com/dotnet/fsharp/pull/19738))
* Fix `=` adjacent to an interpolated string (e.g. `C(Name=$"value")`) being lexed as the invalid operator `=$` instead of an assignment followed by an interpolated string. ([Issue #16696](https://github.com/dotnet/fsharp/issues/16696))
* Extend the `=` adjacent to an interpolated string fix to the verbatim (`=$@"โ€ฆ"`, `=@$"โ€ฆ"`) and extended multi-dollar (`=$$"""โ€ฆ"""`) interpolated-string forms. ([Issue #16696](https://github.com/dotnet/fsharp/issues/16696), [PR #19984](https://github.com/dotnet/fsharp/pull/19984))
* Preserve type abbreviations (`string`, user-defined aliases) in the refined type of bindings introduced after a `| null` pattern in a `match` expression. ([Issue #19646](https://github.com/dotnet/fsharp/issues/19646), [PR #19745](https://github.com/dotnet/fsharp/pull/19745))
* Fix attributes on return type of unparenthesized tuple methods being silently dropped from IL. ([Issue #462](https://github.com/dotnet/fsharp/issues/462), [PR #19714](https://github.com/dotnet/fsharp/pull/19714))
* Fix cross-assembly calls to inline SRTP functions from libraries compiled with F# 4.7 and earlier. ([Issue #20253](https://github.com/dotnet/fsharp/issues/20253), [PR #20260](https://github.com/dotnet/fsharp/pull/20260))
* Fix enum values losing their type when used in a custom attribute argument of type `obj` (they were stored as the underlying integer instead of the enum). ([Issue #995](https://github.com/dotnet/fsharp/issues/995), [PR #19975](https://github.com/dotnet/fsharp/pull/19975))
* Fix false-positive nullness warning (FS3261) when pattern matching narrows nullness inside seq/list/array comprehensions. ([Issue #19644](https://github.com/dotnet/fsharp/issues/19644), [PR #19743](https://github.com/dotnet/fsharp/pull/19743))
* Fix internal error FS0073 "Undefined or unsolved type variable" in IlxGen when nested inline SRTP functions with multiple overloads leave unsolved typars in the non-witness codegen path. ([Issue #19709](https://github.com/dotnet/fsharp/issues/19709), [PR #19710](https://github.com/dotnet/fsharp/pull/19710))
* Fix internal error FS0073 "Undefined or unsolved type variable" in IlxGen `GenGenericArgs` when a phantom typar reaches a closure's generic actuals in the emitted fallback body of a nested inline SRTP member. ([PR #19602](https://github.com/dotnet/fsharp/pull/19602))
* Fix NRE when calling virtual Object methods on value types through inline SRTP functions. ([Issue #8098](https://github.com/dotnet/fsharp/issues/8098), [PR #19511](https://github.com/dotnet/fsharp/pull/19511))
* Narrow overload-resolution error ranges to the method name only instead of covering the entire expression. ([Issue #14284](https://github.com/dotnet/fsharp/issues/14284), [PR #19505](https://github.com/dotnet/fsharp/pull/19505))
* Fix attributes not resolved from opened namespaces in `namespace rec` / `module rec` scopes. ([Issue #7931](https://github.com/dotnet/fsharp/issues/7931), [PR #19502](https://github.com/dotnet/fsharp/pull/19502))
* Fix DU case names matching IWSAM member names no longer cause duplicate property entries. (Issue [#14321](https://github.com/dotnet/fsharp/issues/14321), [PR #19341](https://github.com/dotnet/fsharp/pull/19341))
* Recover per symbol instead of per file in `GetSemanticClassification`, so one unresolvable symbol no longer drops the whole file's classification, and fail fast in the pickle writer when an entity was never linked. ([Issue #20269](https://github.com/dotnet/fsharp/issues/20269), [PR #20274](https://github.com/dotnet/fsharp/pull/20274))
* Fix DefaultAugmentation(false) duplicate entry in method table. (Issue [#16565](https://github.com/dotnet/fsharp/issues/16565), [PR #19341](https://github.com/dotnet/fsharp/pull/19341))
* Fix abstract event accessors now have SpecialName flag. (Issue [#5834](https://github.com/dotnet/fsharp/issues/5834), [PR #19341](https://github.com/dotnet/fsharp/pull/19341))
* Fix warning 20 ("expression is implicitly ignored") pointing at the wrong range when the last expression in a sequential block (e.g. inside `for`, `while` loops) is non-unit. The squiggle now correctly highlights only the offending expression. ([Issue #5418](https://github.com/dotnet/fsharp/issues/5418), [PR #19504](https://github.com/dotnet/fsharp/pull/19504))
* Extend the warning 20 range fix to also walk through `let`/`use` bindings, so the squiggle lands on the offending expression when it is the body of a `let`/`use`. ([Issue #5418](https://github.com/dotnet/fsharp/issues/5418), [PR #19896](https://github.com/dotnet/fsharp/pull/19896))
* Fix missing "No implementation was given" error when F# class inherits from a C# class with `abstract override` members without providing an implementation. ([Issue #7776](https://github.com/dotnet/fsharp/issues/7776), [PR #19503](https://github.com/dotnet/fsharp/pull/19503))
* Fix CLIEvent properties to be correctly recognized as events: `IsEvent` returns `true` and `XmlDocSig` uses `E:` prefix instead of `P:`. ([Issue #10273](https://github.com/dotnet/fsharp/issues/10273), [PR #18584](https://github.com/dotnet/fsharp/pull/18584))
* Fix extra sequence point at the end of match expressions. ([Issue #12052](https://github.com/dotnet/fsharp/issues/12052), [PR #19278](https://github.com/dotnet/fsharp/pull/19278))
* Fix wrong sequence point range for `return`/`yield`/`return!`/`yield!` inside computation expressions. ([Issue #19248](https://github.com/dotnet/fsharp/issues/19248), [PR #19278](https://github.com/dotnet/fsharp/pull/19278))
* Fix extra out-of-order sequence point for `use` in `task` computation expressions. ([Issue #19255](https://github.com/dotnet/fsharp/issues/19255), [PR #19278](https://github.com/dotnet/fsharp/pull/19278))
* Fix debug points failing to bind in body of `[ for x in xs -> body ]` comprehensions. ([Issue #13504](https://github.com/dotnet/fsharp/issues/13504), [PR #19278](https://github.com/dotnet/fsharp/pull/19278))
* Fix outref parameter compiled as byref. (Issue [#13468](https://github.com/dotnet/fsharp/issues/13468), [PR #19340](https://github.com/dotnet/fsharp/pull/19340))
* Fix static abstract interface members with byref params. (Issue [#18135](https://github.com/dotnet/fsharp/issues/18135), [PR #19340](https://github.com/dotnet/fsharp/pull/19340))
* Fix codegen to produce IL passing ILVerify: specialized stelem/ldelem for primitives, callvirtโ†’call on value types, castclass at interface join points, filterโ†’catch inside finally handlers, witness field alignment in state machine structs. ([PR #19372](https://github.com/dotnet/fsharp/pull/19372))
* Fix object expressions in struct types no longer generate invalid IL with byref fields. (Issue [#19068](https://github.com/dotnet/fsharp/issues/19068), [PR #19339](https://github.com/dotnet/fsharp/pull/19339))
* Avoid duplicate parameter names in closure constructors. (Issue [#17692](https://github.com/dotnet/fsharp/issues/17692), [PR #19339](https://github.com/dotnet/fsharp/pull/19339))
* Improve let-rec codegen: reorder bindings to allocate lambda closures before non-lambda values that reference them. ([PR #19339](https://github.com/dotnet/fsharp/pull/19339))
* Fix `YieldFromFinal`/`ReturnFromFinal` being incorrectly called in non-tail positions (`for`, `use`, `use!`, `try/with` handler). ([Issue #19402](https://github.com/dotnet/fsharp/issues/19402), [PR #19403](https://github.com/dotnet/fsharp/pull/19403))
* Preserve commas in embedded resource paths, including generated trimming substitutions under custom intermediate directories. ([PR #20527](https://github.com/dotnet/fsharp/pull/20527))
* Fixed how the source ranges of warn directives are reported (as trivia) in the parser output (by not reporting leading spaces). ([Issue #19405](https://github.com/dotnet/fsharp/issues/19405), [PR #19408]((https://github.com/dotnet/fsharp/pull/19408)))
* Fix UoM value type `ToString()` returning garbage values when `--checknulls+` is enabled, caused by double address-taking in codegen. ([Issue #19435](https://github.com/dotnet/fsharp/issues/19435), [PR #19440](https://github.com/dotnet/fsharp/pull/19440))
* Fix accessibility and type-matching for extension method lookups. ([Issue #19349](https://github.com/dotnet/fsharp/issues/19349), [PR #19536](https://github.com/dotnet/fsharp/pull/19536))
* Fix completion inconsistently showing some obsolete members (fields and events) while hiding others (methods and properties). All obsolete members are now consistently hidden by default. ([Issue #13512](https://github.com/dotnet/fsharp/issues/13512), [PR #19506](https://github.com/dotnet/fsharp/pull/19506))
* Explicit generic type arguments are now unified in constraint-dependency order, so a subtype constraint that references a later type parameter (e.g. `Register<'a, 'b when 'a :> I<'b>>` called as `<Foo, int>`) no longer fails with FS0001 when the argument implements the interface at several instantiations. ([Issue #20103](https://github.com/dotnet/fsharp/issues/20103), [PR #20342](https://github.com/dotnet/fsharp/pull/20342))
* Fix O(n) `TypeStructure.GetHashCode` performance regression causing sustained high CPU in IDE mode with generative type providers. ([Issue #18925](https://github.com/dotnet/fsharp/issues/18925), [PR #19369](https://github.com/dotnet/fsharp/pull/19369))
* Fix TypeLoadException when creating delegate with voidptr parameter. (Issue [#11132](https://github.com/dotnet/fsharp/issues/11132), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
* Suppress tail calls when localloc (NativePtr.stackalloc) is used. (Issue [#13447](https://github.com/dotnet/fsharp/issues/13447), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
* Fix stack overflow when graph-checking long statement sequences on platforms with large stack frames (e.g. Mono ppc64le), by flattening `SynExpr.Sequential` chains in `FileContentMapping`. ([Issue #19988](https://github.com/dotnet/fsharp/issues/19988), [PR #20028](https://github.com/dotnet/fsharp/pull/20028))
* Fix TypeLoadException in Release builds with inline constraints. (Issue [#14492](https://github.com/dotnet/fsharp/issues/14492), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
* Fix nativeptr in interfaces leads to TypeLoadException. (Issue [#14508](https://github.com/dotnet/fsharp/issues/14508), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
* Fix box instruction for literal upcasts. (Issue [#18319](https://github.com/dotnet/fsharp/issues/18319), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
* Fix Decimal Literal causes InvalidProgramException in Debug builds. (Issue [#18956](https://github.com/dotnet/fsharp/issues/18956), [PR #19338](https://github.com/dotnet/fsharp/pull/19338))
* Fix `AttributeUsage.AllowMultiple` not being inherited for attributes subclassed in C#. ([Issue #17107](https://github.com/dotnet/fsharp/issues/17107), [PR #19315](https://github.com/dotnet/fsharp/pull/19315))
* Fix signature generation: recursive module `do` binding leaking compiler-generated val. ([Issue #13832](https://github.com/dotnet/fsharp/issues/13832), [PR #19586](https://github.com/dotnet/fsharp/pull/19586))
* Fix signature generation: literal values in attribute arguments now preserve literal identifier name. ([Issue #13810](https://github.com/dotnet/fsharp/issues/13810), [PR #19586](https://github.com/dotnet/fsharp/pull/19586))
* Fix signature generation: struct types with non-comparable/non-equatable fields now include `[<NoComparison>]`/`[<NoEquality>]`. ([Issue #15339](https://github.com/dotnet/fsharp/issues/15339), [PR #19586](https://github.com/dotnet/fsharp/pull/19586))
* Fix `MemoryMappedFileViewStream` copying from non-array-backed `ReadOnlyMemory` values by falling back to the safe `Stream.Write` path instead of the unsupported `PositionPointer` fast path. ([Issue #20263](https://github.com/dotnet/fsharp/issues/20263), [PR #20271](https://github.com/dotnet/fsharp/pull/20271))
* Fix signature generation: backtick escaping for identifiers containing backticks. ([Issue #15389](https://github.com/dotnet/fsharp/issues/15389), [PR #19586](https://github.com/dotnet/fsharp/pull/19586))
* Fix signature generation: `private` keyword placement for prefix-style type abbreviations. ([Issue #15560](https://github.com/dotnet/fsharp/issues/15560), [PR #19586](https://github.com/dotnet/fsharp/pull/19586))
* Fix signature generation: missing `[<Class>]` attribute for types without visible constructors. ([Issue #16531](https://github.com/dotnet/fsharp/issues/16531), [PR #19586](https://github.com/dotnet/fsharp/pull/19586))
* Fix F# exception serialization now preserves fields (gated behind `--langversion:11`). (Issue [#878](https://github.com/dotnet/fsharp/issues/878), [PR #19342](https://github.com/dotnet/fsharp/pull/19342), [PR #19746](https://github.com/dotnet/fsharp/pull/19746))
* Fix computation expressions desugaring final-position `do!` as `return!`/`yield!`; it now uses `Bind` while explicit `return!`/`yield!` retain final methods. ([Issue #20448](https://github.com/dotnet/fsharp/issues/20448), [PR #20449](https://github.com/dotnet/fsharp/pull/20449))
* Fix methods being tagged as `Member` instead of `Method` in tooltips. ([Issue #10540](https://github.com/dotnet/fsharp/issues/10540), [PR #19507](https://github.com/dotnet/fsharp/pull/19507))
* Fix Debug-mode compilation when mixing resumable and standard computation expressions. ([Issue #19625](https://github.com/dotnet/fsharp/issues/19625), [PR #19630](https://github.com/dotnet/fsharp/pull/19630))
* IlxGen: fix missing CompilationMapping attribute for generic values ([PR #19643](https://github.com/dotnet/fsharp/pull/19643))
* Fix internal error `FS0192: encodeCustomAttrElemType` when using arrays of user-defined types as custom attribute arguments. Empty arrays (e.g. `[<DefaultValue([||] : A[])>]`) now compile successfully; non-empty arrays of unencodable types report a proper diagnostic (FS3887) instead of an internal error. ([Issue #12796](https://github.com/dotnet/fsharp/issues/12796), [PR #19472](https://github.com/dotnet/fsharp/pull/19472))
* Cleanup in IL base-call checking: skip `resolveILMethodRefWithRescope` when the method is not declared on the immediate IL type, instead of relying on the `try/with` around a `failwith`. `FS1201` still applies when the abstract member is declared on the immediate IL base. ([Issue #20264](https://github.com/dotnet/fsharp/issues/20264), [PR #20272](https://github.com/dotnet/fsharp/pull/20272))
* Fix internal compiler error in `use` bindings when a C#-style `Dispose` extension method is in scope alongside `IDisposable.Dispose`. ([Issue #19552](https://github.com/dotnet/fsharp/issues/19552), [PR #19568](https://github.com/dotnet/fsharp/pull/19568))
* Fix `MethodImpl` and `PreserveSig` attributes on property accessors to emit method implementation flags instead of real custom attributes. ([Issue #20288](https://github.com/dotnet/fsharp/issues/20288), [PR #20558](https://github.com/dotnet/fsharp/pull/20558))
* Fix signature generation: single-case struct DU gets spurious bar causing FS0300. ([Issue #19597](https://github.com/dotnet/fsharp/issues/19597), [PR #19609](https://github.com/dotnet/fsharp/pull/19609))
* Fix signature generation: backticked active pattern case names lose escaping. ([Issue #19592](https://github.com/dotnet/fsharp/issues/19592), [PR #19609](https://github.com/dotnet/fsharp/pull/19609))
* Fix signature generation: `namespace global` header dropped from generated signature. ([Issue #19593](https://github.com/dotnet/fsharp/issues/19593), [PR #19609](https://github.com/dotnet/fsharp/pull/19609))
* Fix signature generation: SRTP constraints use postfix syntax that fails conformance, now uses explicit type param declarations. ([Issue #19594](https://github.com/dotnet/fsharp/issues/19594), [PR #19609](https://github.com/dotnet/fsharp/pull/19609))
* Fix signature generation: type params with special characters missing backtick escaping. ([Issue #19595](https://github.com/dotnet/fsharp/issues/19595), [PR #19609](https://github.com/dotnet/fsharp/pull/19609))
* Fix internal error when using custom attribute with `[<Optional>]` value type parameter and no `[<DefaultParameterValue>]`. ([Issue #8353](https://github.com/dotnet/fsharp/issues/8353), [PR #19484](https://github.com/dotnet/fsharp/pull/19484))
* Fix overload resolution of static member extension if one or more intrinsics candidates exist ([Issue #19664](https://github.com/dotnet/fsharp/issues/19664), [PR #19698](https://github.com/dotnet/fsharp/pull/19698))
* Fix parallel compilation of scripts ([PR #19649](https://github.com/dotnet/fsharp/pull/19649))
* Fix parser recovery, name resolution, and code completion for unfinished enum patterns ([PR #19708](https://github.com/dotnet/fsharp/pull/19708))
* Parser: fix unexpected diagnostics in debug builds, improve error messages ([PR #19730](https://github.com/dotnet/fsharp/pull/19730))
* Fix parser error for anonymous record type aliases with postfix type operators (e.g. `{| Id: Guid |} []`) when closing bracket is column-aligned with opening bracket. ([Issue #17407](https://github.com/dotnet/fsharp/issues/17407), [PR #19762](https://github.com/dotnet/fsharp/pull/19762))
* Fix internal error when resolving SRTP `get_Item` witness for `string` indexers (`unknown builtin witness 'get_ItemDynamic'`). ([Issue #18093](https://github.com/dotnet/fsharp/issues/18093), [PR #19757](https://github.com/dotnet/fsharp/pull/19757))
* Fix false FS1113 error on inline instance members when a class-scope self identifier (`as self`) is present. ([Issue #17899](https://github.com/dotnet/fsharp/issues/17899), [PR #19761](https://github.com/dotnet/fsharp/pull/19761))
* Allow `| null` nullable annotation on a `[<MeasureAnnotatedAbbreviation>]` over a reference type (e.g. the FSharp.UMX `type string<[<Measure>] 'm> = string` pattern). ([Issue #19657](https://github.com/dotnet/fsharp/issues/19657))
* Fix `[<Struct>] ?param` optional parameters could not be passed using the explicit `?param = expr` caller-side syntax with a `ValueOption` value. ([Issue #19711](https://github.com/dotnet/fsharp/issues/19711), [PR #19742](https://github.com/dotnet/fsharp/pull/19742))
* Fix signature conformance: overloaded member with unit parameter `M(())` now matches sig `member M: unit -> unit`. ([Issue #19596](https://github.com/dotnet/fsharp/issues/19596), [PR #19615](https://github.com/dotnet/fsharp/pull/19615))
* Fix `--quiet` not suppressing NuGet restore output on stdout in F# Interactive ([Issue #18086](https://github.com/dotnet/fsharp/issues/18086))
* Reference assembly MVIDs are now deterministic across compiler invocations. Previously, `--refout` / `true` produced a different MVID every build because the implied signature hash used .NET's randomized `String.GetHashCode()`. ([Issue #19751](https://github.com/dotnet/fsharp/issues/19751), [PR #19801](https://github.com/dotnet/fsharp/pull/19801))
* Fix recursive inline-member optimization dependencies so inline consumers in recursive groups are resolved reliably without changing static initialization order. ([Issue #20085](https://github.com/dotnet/fsharp/issues/20085), [PR #20111](https://github.com/dotnet/fsharp/pull/20111))
* Parser: recover on unfinished if and binary expressions ([PR #19724](https://github.com/dotnet/fsharp/pull/19724))
* Parser: recover on missing 'when' conditions ([PR ##20071](https://github.com/dotnet/fsharp/pull/#20071))
* Fix `SynExpr.shouldBeParenthesizedInContext` to report parentheses as required around `SynExpr.Sequential` expressions used as record or anonymous-record field values, so the IDE "remove unnecessary parentheses" analyzer no longer breaks code like `{| A = ((); B = 3) |}`. ([Issue #17826](https://github.com/dotnet/fsharp/issues/17826), [PR #19850](https://github.com/dotnet/fsharp/pull/19850))
* Fix semantic classification of `IDisposable` and other interface types in type-occurrence positions being incorrectly classified as `DisposableType` instead of `Interface`. ([Issue #16268](https://github.com/dotnet/fsharp/issues/16268), [PR #19809](https://github.com/dotnet/fsharp/pull/19809))
* Fix missing semantic classification on second and later type qualifiers in nested copy-and-update expressions like `{ p with Person.Info.X = 1; Person.Info.Y = 2 }`. ([Issue #17428](https://github.com/dotnet/fsharp/issues/17428), [PR #19878](https://github.com/dotnet/fsharp/pull/19878))
* Fix QuickInfo / `GetAllUsesOfAllSymbolsInFile` for overloaded CE `[<CustomOperation>]` keywords to report the actually-resolved overload instead of the first-registered one. ([Issue #11612](https://github.com/dotnet/fsharp/issues/11612), [Issue #15206](https://github.com/dotnet/fsharp/issues/15206), [PR #19865](https://github.com/dotnet/fsharp/pull/19865))
* Fix `open` declaration insertion in `.fsx`/`.fsscript` scripts being placed before `#r`/`#load` directives, producing invalid script files. ([Issue #16271](https://github.com/dotnet/fsharp/issues/16271), [PR #19879](https://github.com/dotnet/fsharp/pull/19879))
* Warn FS3888 when a compiler-semantic attribute on a value/member or type/module is present in the `.fs` but missing from the `.fsi`. Such attributes were previously ignored at the consumer side. Under the `ErrorOnMissingSignatureAttribute` preview language feature, FS3888 is an error. ([Issue #19560](https://github.com/dotnet/fsharp/issues/19560), [PR #19880](https://github.com/dotnet/fsharp/pull/19880))
* Emit debug points at a stack-empty position ([PR #19877](https://github.com/dotnet/fsharp/pull/19877))
* Fix spurious XmlDoc warnings (unknown parameter / no documentation for parameter) under `--warnon:3390` when a get/set property documents the full parameter set across both accessors. ([Issue #13684](https://github.com/dotnet/fsharp/issues/13684), [PR #19884](https://github.com/dotnet/fsharp/pull/19884))
* Replace internal compiler error FS0193 with a clear FS3891 diagnostic when a type abbreviation aliases a generic attribute type (e.g. `type B = A` then `[] ...`). Generic attributes remain unsupported in F#. ([Issue #7877](https://github.com/dotnet/fsharp/issues/7877), [PR #19915](https://github.com/dotnet/fsharp/pull/19915))
* Fix Go to Metadata rendering of IL literal (`const`) fields - they now appear with `[<Literal>]` and their constant value, e.g. `System.Char.MaxValue` no longer shows as a plain `static val`. ([Issue #11526](https://github.com/dotnet/fsharp/issues/11526), [PR #19922](https://github.com/dotnet/fsharp/pull/19922))
* FSI multi-assembly emit (`--multiemit+`) now attaches `System.Diagnostics.DebuggableAttribute(DisableOptimizations|Default)` to each submission's manifest when local optimizations are disabled (`--optimize-`), matching the single-emit and regular-compiler behavior so debuggers see submissions as unoptimized. ([Issue #14572](https://github.com/dotnet/fsharp/issues/14572), [PR #19921](https://github.com/dotnet/fsharp/pull/19921))
* Stop F# Interactive from mutating script arguments that follow `--`. Abbreviated flags like `-d`, `-r`, `-I` after the `--` separator are no longer colon-joined with their next token in `fsi.CommandLineArgs`. ([Issue #10819](https://github.com/dotnet/fsharp/issues/10819), [PR #19926](https://github.com/dotnet/fsharp/pull/19926))
* Fix Go-to-Definition for provided constructors that lack `TypeProviderDefinitionLocationAttribute`. Navigation now falls back to the declaring type instead of silently failing. ([Issue #5538](https://github.com/dotnet/fsharp/issues/5538), [PR #19917](https://github.com/dotnet/fsharp/pull/19917))
* Render the declaring type for C#-style extension methods in overload-resolution error messages so the suggestion no longer falsely attributes the extension to the receiver type. ([Issue #9838](https://github.com/dotnet/fsharp/issues/9838), [PR #19925](https://github.com/dotnet/fsharp/pull/19925))
* Fix FSI pretty printing to distinguish anonymous records (`{| ... |}`) from nominal records (`{ ... }`). ([Issue #6116](https://github.com/dotnet/fsharp/issues/6116), [PR #19919](https://github.com/dotnet/fsharp/pull/19919))
* Fix dot-completion after indexed expressions (`a.[0].Data.`, `a[0].Data.`, `[1;2].Length.`) returning unrelated global completions instead of expression-typings members. ([Issue #4966](https://github.com/dotnet/fsharp/issues/4966), [PR #19934](https://github.com/dotnet/fsharp/pull/19934))
* Quotations of `match s with "" -> _` no longer leak the `s <> null && s.Length = 0` lowering; the empty-string optimization moved from pattern-match compilation to the optimizer so quoted expressions keep `op_Equality(s, "")`. ([Issue #19873](https://github.com/dotnet/fsharp/issues/19873))
* Parser: recover on unfinished abstract members ([PR #20070](https://github.com/dotnet/fsharp/pull/20070))
* Fix #5795: Allow attributes defined in a `module rec` / `namespace rec` scope to be used on union cases, record fields, and generic type parameters of types in the same recursive scope. ([Issue #5795](https://github.com/dotnet/fsharp/issues/5795), [PR #19744](https://github.com/dotnet/fsharp/pull/19744))
* Import: Don't walk non-F# assemblies when labelling trait constraint sources (PR [#20090](https://github.com/dotnet/fsharp/pull/20090))
* Avoid per-instance lock object in InterruptibleLazy and DelayInitArrayMap (PR [#20088](https://github.com/dotnet/fsharp/pull/20088))
* IL: fix leaking binary view ([PR #20250](https://github.com/dotnet/fsharp/pull/20250))
* FCS: fix races that made the background builder repeat work ([PR #20481](https://github.com/dotnet/fsharp/pull/20481))
* Reject runtime-only Await calls inside ordinary task state-machine methods with FS3918, while preserving normal task composition inside runtime-async methods. ([Issue #20576](https://github.com/dotnet/fsharp/issues/20576), [PR #20584](https://github.com/dotnet/fsharp/pull/20584))
* Fix dependency ordering and stack safety for recursive inline bindings. ([PR #20111](https://github.com/dotnet/fsharp/pull/20111))
* Fix signature generation (`fsc --sig`, `GenerateSignature`, `GetValSignatureText`) dropping the parentheses around a destructured or pattern-annotated tuple parameter when a sibling argument in the same curried group is named, so `(int * float) * z: string` no longer prints as the flat 3-tuple `int * float * z: string`. ([Issue #20397](https://github.com/dotnet/fsharp/issues/20397), [PR #20589](https://github.com/dotnet/fsharp/pull/20589))

### Added

* FCS: add FSharpCheckFileResults.FileSignature ([PR #20478](https://github.com/dotnet/fsharp/pull/20478))
* Added the `ReraiseInComputationExpressions` language feature (`--langversion:preview`): `reraise ()` in the `with` handler of a computation expression is compiled to a rethrow through `ExceptionDispatchInfo` instead of being rejected with FS0413. ([Suggestion #660](https://github.com/fsharp/fslang-suggestions/issues/660), [RFC FS-1347](https://github.com/fsharp/fslang-design/pull/843), [PR #20405](https://github.com/dotnet/fsharp/pull/20405))
* Added a "most concrete" tiebreaker for overload resolution (`--langversion:preview`). ([RFC FS-1340](https://github.com/fsharp/fslang-design/pull/834), [PR #19277](https://github.com/dotnet/fsharp/pull/19277))
* Added support for `OverloadResolutionPriorityAttribute` in overload resolution (`--langversion:preview`). ([RFC FS-1338](https://github.com/fsharp/fslang-design/pull/828), [PR #19277](https://github.com/dotnet/fsharp/pull/19277))
* Added internal synthesized-name replay infrastructure for compiler-generated names, preserving normal compilation output while enabling future hot reload name stability work.
* Added `FSharpMemberOrFunctionOrValue.IsPropertyAccessor` convenience property that returns true for compiler-generated property accessors (`get_X` / `set_X`). ([Issue #18157](https://github.com/dotnet/fsharp/issues/18157), [PR #19883](https://github.com/dotnet/fsharp/pull/19883))
* Added warning FS3884 when a function or delegate value is used as an interpolated string argument. ([PR #19289](https://github.com/dotnet/fsharp/pull/19289))
* Symbols: add ObsoleteDiagnosticInfo ([PR #19359](https://github.com/dotnet/fsharp/pull/19359))
* FCS: add FSharpCheckFileResults.HasErrors ([PR #19892](https://github.com/dotnet/fsharp/pull/19892))
* Add preview compiler support for runtime-async `Task<'T>` methods through the `__runtimeAsyncReturn` intrinsic, including carrier validation, target-runtime capability checks, diagnostics for suspension calls outside runtime-async methods, diagnostics for byref, byref-like, or pinned values used after suspension, and recursive specialization of inline suspension fragments when optimization is disabled. ([PR #20235](https://github.com/dotnet/fsharp/pull/20235))
* Add `#version;;` directive to F# Interactive to display version and environment information. ([Issue #13307](https://github.com/dotnet/fsharp/issues/13307), [PR #19332](https://github.com/dotnet/fsharp/pull/19332))
* Add diagnostic FS3889 when a namespace and a type have the same fully-qualified name in the same assembly, replacing the misleading FS0247 "namespace and a module" error. ([Issue #17827](https://github.com/dotnet/fsharp/issues/17827), [PR #19802](https://github.com/dotnet/fsharp/pull/19802))
* Debug: rework for expressions stepping ([PR #19894](https://github.com/dotnet/fsharp/pull/19894))
* Debug: rework conditional erasure, fix stepping over literals ([PR #19897](https://github.com/dotnet/fsharp/pull/19897))
* Record spreads ([RFC FS-1151](https://github.com/fsharp/fslang-design/pull/805), [PR #18927](https://github.com/dotnet/fsharp/pull/18927), [PR #20206](https://github.com/dotnet/fsharp/pull/20206))
* Debug: fix if and match condition sequence points ([PR #19932](https://github.com/dotnet/fsharp/pull/19932))
* Name resolution: CheckIWSAM only needs the intrinsic methods ([PR #20490](https://github.com/dotnet/fsharp/pull/20490))
* Surface the synthesized all-fields constructor of F# record types to F# code under the `RecordConstructorSyntax` preview feature, via a new `MethInfo.RecdCtor` case. ([PR #19974](https://github.com/dotnet/fsharp/pull/19974))
* Methods and constructors annotated with `System.Diagnostics.CodeAnalysis.RequireNamedArgumentsAttribute` require named arguments under `--langversion:preview`, including provided methods. Recognition uses the full type name. Positional calls report FS3923. `nameof` remains allowed. ([PR #20340](https://github.com/dotnet/fsharp/pull/20340))
* Under `--reflectionfree`, discriminated unions, records and anonymous records now get a [generated `ToString`](../../reflectionfree-printing.md) (rendering each field like `Option` does) instead of falling back to the namespace-qualified type name. ([PR #19976](https://github.com/dotnet/fsharp/pull/19976))
* Support common types of `NotNullIfNotNullAttribute` usage. If a method parameter is marked with `NotNullIfNotNullAttribute`, the compiler will now honor this attribute and mark the return type as non-null. ([PR #19977](https://github.com/dotnet/fsharp/pull/19977))
* Checker: recover on checking language version ([PR ##19970](https://github.com/dotnet/fsharp/pull/19970))
* Implied argument names for function-to-delegate coercions now fall back to the delegate's `Invoke` parameter names when the function has no recoverable names (e.g. a partial application like `System.Func((+) 1)`), instead of synthetic `delegateArg0`, `delegateArg1`, โ€ฆ names. ([PR #20001](https://github.com/dotnet/fsharp/pull/20001))
* Add internal `ResetCompilerGeneratedNameState` to `CompilerGlobalState` name generators so warm-checker re-compilation can produce fresh-process-identical generated names. ([PR #20017](https://github.com/dotnet/fsharp/pull/20017))
* Publish generated ILVerify actual baselines as downloadable CI artifacts when verification fails. ([PR #20585](https://github.com/dotnet/fsharp/pull/20585))
* IL: map the short-lived metadata-only PE reader ([PR #20489](https://github.com/dotnet/fsharp/pull/20489))
* Add internal ECMA-335 Edit-and-Continue metadata delta writer to AbstractIL. ([PR #20019](https://github.com/dotnet/fsharp/pull/20019))
* Add Roslyn-format EnC CustomDebugInformation codec and portable PDB method CDI emission support to AbstractIL. ([PR #20018](https://github.com/dotnet/fsharp/pull/20018))
* Extension members (including operators) now participate in SRTP (statically-resolved type parameter) constraint resolution, so member constraints on `inline` code can be satisfied by extension members โ€” including operators defined on types you don't own via type extensions. Intrinsic members keep priority over extension members, and extension members solve SRTP constraints without satisfying nominal static-abstract interface (IWSAM) constraints. Also adds the `[<AllowOverloadOnReturnType>]` attribute for overloads that differ only by return type, and tuple type extensions (`type ('T1 * 'T2) with` / `type struct ('T1 * 'T2) with`). Requires `--langversion:preview`. ([RFC FS-1043](https://github.com/fsharp/fslang-design/blob/main/RFCs/FS-1043-extension-members-for-operators-and-srtp-constraints.md), [fslang-suggestions#230](https://github.com/fsharp/fslang-suggestions/issues/230), [PR #19602](https://github.com/dotnet/fsharp/pull/19602))
* Add internal hot reload baseline reading for recorded EnC state and synthesized-name snapshot PDB data. ([PR #20026](https://github.com/dotnet/fsharp/pull/20026))
* Support for the `` XML documentation tag: at compile time, documentation is copied from an external XML file selected by an XPath query and emitted into the generated documentation file. `` remains unsupported. ([Issue #19175](https://github.com/dotnet/fsharp/issues/19175), [PR #19186](https://github.com/dotnet/fsharp/pull/19186))
* Expand `` at tooling time. In IDE tooltips, completion, and signature help, documentation is inherited from base classes, interfaces, overridden members, and constructors (matched by parameter signature). The FCS Symbols API (`FSharpSymbol.XmlDoc`) additionally resolves explicit `cref` targets, but does not expand constructor inheritance. The compiler emits the tag verbatim into generated XML documentation files, matching C#; `` is not implemented. ([Issue #19175](https://github.com/dotnet/fsharp/issues/19175), [PR #19188](https://github.com/dotnet/fsharp/pull/19188))
* Prototype direct runtime-async sequences from statically known inline recipes, with asynchronous cleanup, direct interface dispatch, and preserved runtime call-await fusion.
* Add symbol and type highlighting to F# diagnostics ([PR #20097](https://github.com/dotnet/fsharp/pull/20097))
* IL: hold custom attributes in fields rather than a union case ([PR #20287](https://github.com/dotnet/fsharp/pull/20287))
* IL: reuse the cached ILTypeRef in ILTypeInfo.FromType ([PR #20255](https://github.com/dotnet/fsharp/pull/20255))
* IL: fix the per-reader string cache sizing ([PR #20261](https://github.com/dotnet/fsharp/pull/20261))
* Name resolution: group C#-style extension members per `open` and extended type ([PR #20298](https://github.com/dotnet/fsharp/pull/20298))
* Symbols: add `FSharpDisplayContext.WithNullnessAnnotations` ([PR #20507](https://github.com/dotnet/fsharp/pull/20507))

### Improved

* IL: cache C# extension methods per CCU ([PR #20256](https://github.com/dotnet/fsharp/pull/20256))
* Nullness warning FS3261 on dotted method or property access (e.g. `x.Member`) now underlines the receiver expression and includes the member name and (when known) the binding name in the message. ([Issue #19658](https://github.com/dotnet/fsharp/issues/19658), [PR #19814](https://github.com/dotnet/fsharp/pull/19814))
* Import: share assembly CCUs between projects ([PR #20296](https://github.com/dotnet/fsharp/pull/20296))
* IL: share the pickled references ([PR #20301](https://github.com/dotnet/fsharp/pull/20301))
* Direct delegate construction ([PR ##19993](https://github.com/dotnet/fsharp/pull/19993))
* IL: add `ILPreNamespace`, make `ILPreTypeDef` creation lazy ([PR #20092](https://github.com/dotnet/fsharp/pull/20092))
* IL: share ILCallingConv instances ([PR #20254](https://github.com/dotnet/fsharp/pull/20254))
* IL: intern the attributes and type references read from metadata ([PR #20486](https://github.com/dotnet/fsharp/pull/20486))
* IL: cache the ILTypeRef of a type def ([PR #20259](https://github.com/dotnet/fsharp/pull/20259))
* IL: use empty tables for members when possible ([PR #20249](https://github.com/dotnet/fsharp/pull/20249))
* Make Entity's adhoc members list lazy ([PR #20286](https://github.com/dotnet/fsharp/pull/20286/changes))
* Constraint solver: `TryD` is now `inline` with `[]` on its always-run continuation, so the argument closures are no longer allocated at the (very hot) constraint-solver call sites; `IgnoreFailedMemberConstraintResolution` is `inline` so its forwarded continuation stays a literal. ([PR #20367](https://github.com/dotnet/fsharp/pull/20367))
* `[<OptimizeClosureIfNotInlined>]`, available in F# 11, adapts opaque callbacks once instead of checking their arity on every call. Lifted recursive methods no longer trigger unrelated file initialization, which could deadlock. ([PR #20422](https://github.com/dotnet/fsharp/pull/20422), [PR #20571](https://github.com/dotnet/fsharp/pull/20571))
* `DelayedILModuleReader` no longer boxes its cached `ILModuleReader` on every read: the field is typed `ILModuleReader | null` and matched directly. ([PR #20413](https://github.com/dotnet/fsharp/pull/20413))
* Typed tree: create a type's augmentation on first use ([PR #20494](https://github.com/dotnet/fsharp/pull/20494))
* Optimizer: passing a partial application of a non-inline module-level function to an `[]` parameter (e.g. `xs |> Option.map (f a b)`) no longer allocates a per-call `FSharpFunc` closure when a captured argument is non-trivial (a field read, a call). Under optimization the argument is eta-expanded to a lambda with its captured evaluations floated above the binding, so the parameter's uses beta-reduce and the closure is eliminated. Captured arguments are still evaluated exactly once, in their original left-to-right order, and the binding keeps its sequence point. Partial applications of inline/SRTP functions and curried members can still allocate closures. ([PR #20487](https://github.com/dotnet/fsharp/pull/20487))
* IlxGen: `HashRangeSorted` returns immediately for an empty dictionary and otherwise sorts one exact-size array in place, rather than building three intermediate lists. Most type definitions carry no properties, so this takes the whole pipeline off the common path. ([PR #20456](https://github.com/dotnet/fsharp/pull/20456))

### Changed
* The `--warnaserror` option now ignores unrecognized diagnostic identifiers in warning lists while still applying recognized F# warning codes. ([PR #20246](https://github.com/dotnet/fsharp/pull/20246))

* Reduce allocations when inlining a generic function by fusing the optimizer's copy and type-instantiation passes into a single tree traversal instead of cloning the body twice. ([PR #20363](https://github.com/dotnet/fsharp/pull/20363))
* Improvements in error and warning messages: new error FS3885 when `let!`/`use!` is the final expression in a computation expression; new warning FS3886 when a list literal contains a single tuple element (likely missing `;` separator); improved wording for FS0003, FS0025, FS0039, FS0072, FS0247, FS0597, FS0670, FS3082, and SRTP operator-not-in-scope hints. ([PR #19398](https://github.com/dotnet/fsharp/pull/19398))
* Exception field serialization (`GetObjectData` and field-restoring constructor) is now gated behind `langversion:11` (`LanguageFeature.ExceptionFieldSerializationSupport`). With langversion โ‰ค10, exception codegen is unchanged from pre-#19342 behavior. ([PR #19746](https://github.com/dotnet/fsharp/pull/19746))
* `Async.RunImmediate` renamed and replaced with impl of `FSharp.Core`'s `Async.RunSynchronouslyImmediate`, wherein `Exception`s are unwrapped (i.e., no egregious `AggregateException` wrapping). ([Issue #1042](https://github.com/fsharp/fslang-suggestions/issues/1042), [PR #19804](https://github.com/dotnet/fsharp/pull/19804), [PR #20245](https://github.com/dotnet/fsharp/pull/20245))
* Lower string-typed interpolated strings to `System.String.Concat` rather than the reflection-based `printf` engine, making them trim- and NativeAOT-compatible. This generalizes and ungates the previous all-string `String.Concat` optimization, so it now applies to every string-typed interpolation. ([Language suggestion #1108](https://github.com/fsharp/fslang-suggestions/issues/1108), [PR #19971](https://github.com/dotnet/fsharp/pull/19971))
* Stabilized several `preview` language features into F# 11.0 (`--langversion:11.0`, enabled by default with a .NET 11 SDK): `MethodOverloadsCache`, `ErrorOnMissingSignatureAttribute`, `DirectDelegateConstruction`, `AccessProtectedBaseFieldFromClosure`, and `RecordSpreads`. `FromEndSlicing` intentionally remains in `preview`. ([PR #20199](https://github.com/dotnet/fsharp/pull/20199))
* F# build tasks use MSBuild's per-task environment for in-process multithreaded builds. The build host must provide `IMultiThreadableTask`, `TaskEnvironment`, and `ToolTask.TaskEnvironment`. ([PR #20506](https://github.com/dotnet/fsharp/pull/20506))
* Interpolated string holes (e.g. `$"{x}"`) are now formatted with invariant culture (via the `string` operator) instead of the current thread culture. ([PR #19971](https://github.com/dotnet/fsharp/pull/19971))
* Lines starting with `#:` are now ignored ([Language suggestion 1440](https://github.com/fsharp/fslang-suggestions/issues/1440), [RFC FS-1337](https://github.com/fsharp/fslang-design/pull/830), [PR #20212](https://github.com/dotnet/fsharp/pull/20212))
* `[<return: X>]` attributes written in front of a binding are again reported by `SynBinding.attributes` in the untyped syntax tree, with their original grouping and `[< >]` ranges. The rotation into `SynValInfo.retInfo` added by [PR #19738](https://github.com/dotnet/fsharp/pull/19738) now happens while normalizing a binding for checking instead of in the parser, so both fixes from that PR are unchanged while tools reading the parse tree (formatters, analyzers, source generators) again see what was written. ([PR #20356](https://github.com/dotnet/fsharp/pull/20356))
* Calculate Entity.PublicPath instead of storing ([PR #20285](https://github.com/dotnet/fsharp/pull/20285))
* Remove the always-on `NullableOptionalInterop` language feature flag and collapse its guarded code to the enabled path; `--disableLanguageFeature:NullableOptionalInterop` is no longer a recognized feature name. ([Issue #20154](https://github.com/dotnet/fsharp/issues/20154), [PR #20378](https://github.com/dotnet/fsharp/pull/20378))
* Remove the always-on `ErrorReportingOnStaticClasses` language feature flag. Static-class validation remains unchanged for all supported language versions. ([Issue #20180](https://github.com/dotnet/fsharp/issues/20180), [PR #20513](https://github.com/dotnet/fsharp/pull/20513))
* Remove trailing whitespace from source files. No functional change: whitespace inside string literals and inactive `#if` regions is preserved. ([PR #20355](https://github.com/dotnet/fsharp/pull/20355))

### Breaking Changes
* Add `ExtendedLayoutAttribute` support for future .NET runtime interop. `ILTypeDefLayout` has a new `Extended` case. ([Issue #19190](https://github.com/dotnet/fsharp/issues/19190), [PR #19194](https://github.com/dotnet/fsharp/pull/19194))

* `FSharp.Compiler.Syntax.SynInterpolatedStringPart.FillExpr` now carries a `SynInterpolationFormatting` value (separating .NET alignment/format from printf specifiers) instead of an `Ident option`. ([PR #19971](https://github.com/dotnet/fsharp/pull/19971))
* `FSharp.Compiler.Syntax.SynComponentInfo` now holds the type name as `synType: SynType option` instead of the previous `longId: LongIdent` field, so tuple-type extensions such as `type ('T1 * 'T2) with` can be represented. A `member LongIdent` compatibility property returns the long identifier for named types and an empty list for tuple or erroneous type names. AST consumers that pattern-matched on the `longId` field must switch to the `synType` field or the `LongIdent` member. ([PR #19602](https://github.com/dotnet/fsharp/pull/19602))
* Optimizer: don't inline named functions in debug builds ([PR #19548](https://github.com/dotnet/fsharp/pull/19548)
* LexFilter: drop non-strict mode ([PR #20106](https://github.com/dotnet/fsharp/pull/20106))

---

### Release Notes .FSharp.Compiler.Service 11.0.200 (`docs/release-notes/.FSharp.Compiler.Service/11.0.200.md`)

### Added

* F# Interactive gains a JSON-RPC server mode, `--fsi-server-jsonrpc:`, in which a host submits interactions over a named pipe and receives structured results โ€” diagnostics with positions, escaping exceptions, the values each interaction bound, and the session's own process id โ€” instead of recovering them by looking for a `SERVER-PROMPT>` marker in the output text. Program output continues to flow through the redirected console streams. The pipe admits only the user running the session; `--fsi-server-client-pid:` names the host process whose exit ends the session. `FsiEvaluationSession` exposes both options as `JsonRpcServerPipeName` and `JsonRpcClientProcessId`. The mode is part of the .NET fsi only. ([PR #20396](https://github.com/dotnet/fsharp/pull/20396))

### Fixed

* Capture diagnostics emitted when transparent project checking finishes a project, including FS0247 for namespace-module name collisions. ([Issue #6036](https://github.com/dotnet/fsharp/issues/6036), [PR #20706](https://github.com/dotnet/fsharp/pull/20706))
* Split Linux CoreCLR CI into two parallel test batches to reduce test turnaround time. ([PR #20587](https://github.com/dotnet/fsharp/pull/20587))
* Fix `NativePtr.stackalloc` used inside the `with` handler, filter or `finally` block of a `try` expression producing an assembly that throws `InvalidProgramException` at method load. Direct or mandatory-inline allocations are now reported at compile time as error FS3924; calls to non-inline allocation helpers, including handler-local helpers, remain legal and retain their method boundary during inlining and let-binding elimination. ([Issue #20295](https://github.com/dotnet/fsharp/issues/20295), [PR #20491](https://github.com/dotnet/fsharp/pull/20491))
* `FsiEvaluationSession.EvalInteraction` and `EvalInteractionNonThrowing` evaluate every `;;`-separated interaction in the text they are given, as their documentation has always said, instead of silently discarding everything after the first. ([PR #20396](https://github.com/dotnet/fsharp/pull/20396))
* Prevent `InvalidProgramException` when runtime async awaits a `Task<T>` upcast to `Task`, including through `ConfigureAwait`. ([Issue #20686](https://github.com/dotnet/fsharp/issues/20686), [PR #20726](https://github.com/dotnet/fsharp/pull/20726))
* Preserve constraint inference order when explicit generic arguments share a caller type parameter, avoiding spurious FS0001 errors with multiply implemented generic interfaces. ([PR #20491](https://github.com/dotnet/fsharp/pull/20491))
* Build parser diagnostic messages without runtime printf specialization or reflective function construction, so Native AOT hosts report indentation and invalid-character diagnostics instead of failing during formatting. Preserve `#r` reference tooltips while script project options remain alive, including after garbage collection or cache eviction. ([PR #20658](https://github.com/dotnet/fsharp/pull/20658))
* Preserve tupled lambda argument groups when reducing state-machine applications, avoiding invalid argument substitutions and runtime failures with inline formatters. ([Issue #20675](https://github.com/dotnet/fsharp/issues/20675), [PR #20700](https://github.com/dotnet/fsharp/pull/20700))
* Preserve SRTP extension scopes across source files and competing witnesses through imported inline calls. Compile generic extension forwarding, including quotations, in unoptimized builds. Preserve applicable built-in operator priority in imported inline quotations. Do not reuse SRTP specializations across different selected witnesses. ([Issue #20683](https://github.com/dotnet/fsharp/issues/20683), [Issue #20684](https://github.com/dotnet/fsharp/issues/20684), [Issue #20685](https://github.com/dotnet/fsharp/issues/20685), [PR #20717](https://github.com/dotnet/fsharp/pull/20717))

### Changed

* Assign seven previously preview-only features to [F# 11.2](https://fsharp.github.io/fsharp-compiler-docs/release-notes/Language.html#11.2), now the default language version. `default`, `latest`, and `latestmajor` select F# 11.2; explicit `11` and `11.0` retain F# 11.0 behavior. Preview SDK projects use the same compiler default unless `LangVersion` is explicitly set. `FromEndSlicing` remains preview-only. ([PR #20617](https://github.com/dotnet/fsharp/pull/20617))
* Add `SynExceptionDefnReprTrivia` with the range of the `exception` keyword to `SynExceptionDefnRepr`. ([Fantomas Issue #3483](https://github.com/fsprojects/fantomas/issues/3483), [PR #20612](https://github.com/dotnet/fsharp/pull/20612))
* Add `WhenKeyword` to `SynMatchClauseTrivia` with the range of the `when` keyword in match clauses. ([PR #20734](https://github.com/dotnet/fsharp/pull/20734))
* Add `OfKeyword` to `SynUnionCaseTrivia` with the range of the `of` keyword in union cases and exception definitions. ([PR #20635](https://github.com/dotnet/fsharp/pull/20635))

---

### Release Notes .FSharp.Compiler.Service 8.0.100 (`docs/release-notes/.FSharp.Compiler.Service/8.0.100.md`)

### Fixed

* Include the `get,set` keywords in the range of `SynMemberDefn.AutoProperty`. ([PR #15835](https://github.com/dotnet/fsharp/pull/15835))

---

### Release Notes .FSharp.Compiler.Service 8.0.200 (`docs/release-notes/.FSharp.Compiler.Service/8.0.200.md`)

### Fixed

* Correctly handle assembly imports with public key token of 0 length. ([Issue #16359](https://github.com/dotnet/fsharp/issues/16359), [PR #16363](https://github.com/dotnet/fsharp/pull/16363))
* Range of [SynField](../reference/fsharp-compiler-syntax-synfield.html) ([PR #16357](https://github.com/dotnet/fsharp/pull/16357))
* Limit a type to 65K methods, introduce a compile-time error if any class has over approx 64K methods in generated IL. ([Issue #16398](https://github.com/dotnet/fsharp/issues/16398), [#PR 16427](https://github.com/dotnet/fsharp/pull/16427))

### Added

* Raise a new error when interfaces with auto properties are implemented on constructor-less types. ([PR #16352](https://github.com/dotnet/fsharp/pull/16352))
* Allow usage of `[<TailCall>]` with older `FSharp.Core` package versions. ([PR #16373](https://github.com/dotnet/fsharp/pull/16373))
* Parser recovers on unfinished `as` patterns. ([PR #16404](https://github.com/dotnet/fsharp/pull/16404))
* Allow type-checking of unfinished object expressions. ([PR #16413](https://github.com/dotnet/fsharp/pull/16413))
* Parser recovers on unfinished enum case declarations. ([PR #16401](https://github.com/dotnet/fsharp/pull/16401))
* Parser recovers on unfinished record declarations. ([PR #16357](https://github.com/dotnet/fsharp/pull/16357))
* `MutableKeyword` to [SynFieldTrivia](../reference/fsharp-compiler-syntaxtrivia-synfieldtrivia.html) ([PR #16357](https://github.com/dotnet/fsharp/pull/16357))
* Added support for a new parameterless constructor for `CustomOperationAttribute`, which, when applied, will use method name as keyword for custom operation in computation expression builder. ([PR #16475](https://github.com/dotnet/fsharp/pull/16475), part of implementation for [fslang-suggestions/1250](https://github.com/fsharp/fslang-suggestions/issues/1250))
* Compiler service API for getting ranges of unnecessary parentheses. ([PR #16079](https://github.com/dotnet/fsharp/pull/16079) et seq.)

### Changed

* Speed up unused opens handling for empty results. ([PR #16502](https://github.com/dotnet/fsharp/pull/16502))

---

### Release Notes .FSharp.Compiler.Service 8.0.202 (`docs/release-notes/.FSharp.Compiler.Service/8.0.202.md`)

### Fixed

`nameof Module` expressions and patterns are processed to link files in `--test:GraphBasedChecking`. ([PR #16570](https://github.com/dotnet/fsharp/pull/16570), [PR #16747](https://github.com/dotnet/fsharp/pull/16747))

---

### Release Notes .FSharp.Compiler.Service 8.0.300 (`docs/release-notes/.FSharp.Compiler.Service/8.0.300.md`)

### Fixed

* Fix a false positive of the `[<TailCall>]` analysis in combination with `yield!`. ([PR #16933](https://github.com/dotnet/fsharp/pull/16933))
* Improve error reporting: ambiguous override method in object expression. ([PR #16985](https://github.com/dotnet/fsharp/pull/16985))
* Don't blow the stack when traversing deeply nested sequential expressions. ([PR #16882](https://github.com/dotnet/fsharp/pull/16882))
* Fix wrong range start of INTERP_STRING_END. ([PR #16774](https://github.com/dotnet/fsharp/pull/16774), [PR #16785](https://github.com/dotnet/fsharp/pull/16785))
* Fix missing warning for recursive calls in list comprehensions. ([PR #16652](https://github.com/dotnet/fsharp/pull/16652))
* Code generated files with > 64K methods and generated symbols crash when loaded. Use inferred sequence points for debugging. ([Issue #16399](https://github.com/dotnet/fsharp/issues/16399), [#PR 16514](https://github.com/dotnet/fsharp/pull/16514))
* `nameof Module` expressions and patterns are processed to link files in `--test:GraphBasedChecking`. ([PR #16550](https://github.com/dotnet/fsharp/pull/16550), [PR #16743](https://github.com/dotnet/fsharp/pull/16743))
* Graph Based Checking doesn't throw on invalid parsed input so it can be used for IDE scenarios ([PR #16575](https://github.com/dotnet/fsharp/pull/16575), [PR #16588](https://github.com/dotnet/fsharp/pull/16588), [PR #16643](https://github.com/dotnet/fsharp/pull/16643))
* Various parenthesization API fixes. ([PR #16578](https://github.com/dotnet/fsharp/pull/16578), [PR #16666](https://github.com/dotnet/fsharp/pull/16666), [PR #16901](https://github.com/dotnet/fsharp/pull/16901), [PR #16973](https://github.com/dotnet/fsharp/pull/16973), [PR #17012](https://github.com/dotnet/fsharp/pull/17012))
* Keep parens for problematic exprs (`if`, `match`, etc.) in `$"{(โ€ฆ):N0}"`, `$"{(โ€ฆ),-3}"`, etc. ([PR #16578](https://github.com/dotnet/fsharp/pull/16578))
* Fix crash in DOTNET_SYSTEM_GLOBALIZATION_INVARIANT mode [#PR 16471](https://github.com/dotnet/fsharp/pull/16471))
* Fix16572 - Fixed the preview feature enabling Is properties for union case did not work correctly with let .rec and .fsi files ([PR #16657](https://github.com/dotnet/fsharp/pull/16657))
* `[<CliEvent>]` member should not produce property symbol. ([Issue #16640](https://github.com/dotnet/fsharp/issues/16640), [PR #16658](https://github.com/dotnet/fsharp/pull/16658))
* Fix discriminated union initialization. ([#PR 16661](https://github.com/dotnet/fsharp/pull/16661))
* Allow calling method with both Optional and ParamArray. ([#PR 16688](https://github.com/dotnet/fsharp/pull/16688), [suggestions #1120](https://github.com/fsharp/fslang-suggestions/issues/1120))
* Return diagnostics that got suppressed by errors in previous files. ([PR #16719](https://github.com/dotnet/fsharp/pull/16719))
* Fix release inline optimization, which leads to MethodAccessException if used with `assembly:InternalsVisibleTo`` attribute. ([Issue #16105](https://github.com/dotnet/fsharp/issues/16105), ([PR #16737](https://github.com/dotnet/fsharp/pull/16737))
* Enforce AttributeTargets on let values and functions. ([PR #16692](https://github.com/dotnet/fsharp/pull/16692))
* Enforce AttributeTargets on union case declarations. ([PR #16764](https://github.com/dotnet/fsharp/pull/16764))
* Disallow using base to invoke an abstract base method. ([Issue #13926](https://github.com/dotnet/fsharp/issues/13926), [PR #16773](https://github.com/dotnet/fsharp/pull/16773))
* Parser: more unfinished member recovery ([PR #16835](https://github.com/dotnet/fsharp/pull/16835))
* Enforce AttributeTargets on implicit constructors. ([PR #16845](https://github.com/dotnet/fsharp/pull/16845/))
* Enforce AttributeTargets on structs and classes ([PR #16790](https://github.com/dotnet/fsharp/pull/16790))
* Parser: fix pattern range for idents with trivia ([PR #16824](https://github.com/dotnet/fsharp/pull/16824))
* Fix broken code completion after a record type declaration ([PR #16813](https://github.com/dotnet/fsharp/pull/16813))
* Enforce AttributeTargets on enums ([PR #16887](https://github.com/dotnet/fsharp/pull/16887))
* Completion: fix for unfinished record field decl ([PR #16893](https://github.com/dotnet/fsharp/pull/16893))
* Enforce AttributeTargets on delegates ([PR #16891](https://github.com/dotnet/fsharp/pull/16891))
* Obsolete attribute is ignored in constructor property assignment ([PR #16900](https://github.com/dotnet/fsharp/pull/16900))
* Completion: fix completion in empty dot lambda prefix ([#16829](https://github.com/dotnet/fsharp/pull/16829))
* Fix StackOverflow when checking non-recursive bindings in module or namespace in `fscAnyCpu`/`fsiAnyCpu`. ([PR #16908](https://github.com/dotnet/fsharp/pull/16908))
* Removes signature file adjacency check in Transparent Compiler [Issue #17082](https://github.com/dotnet/fsharp/issues/17082) [PR #17085](https://github.com/dotnet/fsharp/pull/17085)

### Added

* The stackguard depth for ILPdbWriter.unshadowScopes can be modified via the environment variable `FSHARP_ILPdb_UnshadowScopes_StackGuardDepth`([PR #16583](https://github.com/dotnet/fsharp/pull/16583))
* Parser recovers on complex primary constructor patterns, better tree representation for primary constructor patterns. ([PR #16425](https://github.com/dotnet/fsharp/pull/16425))
* Name resolution: keep type vars in subsequent checks ([PR #16456](https://github.com/dotnet/fsharp/pull/16456))
* Higher-order-function-based API for working with the untyped abstract syntax tree. ([PR #16462](https://github.com/dotnet/fsharp/pull/16462))
* PrettyNaming: make `DoesIdentifierNeedBackticks` public ([PR #16613](https://github.com/dotnet/fsharp/pull/16613))
* Add switch to generate types and members with  IL visibility that accurately represents their F# visibility. ([PR #15484](https://github.com/dotnet/fsharp/pull/15484)
* Allow returning bool instead of unit option for partial active patterns. ([Language suggestion #1041](https://github.com/fsharp/fslang-suggestions/issues/1041), [PR #16473](https://github.com/dotnet/fsharp/pull/16473))
* Symbols: Add GenericArguments to FSharpEntity ([PR #16470](https://github.com/dotnet/fsharp/pull/16470))
* Parser: more 'as' pattern recovery ([PR #16837](https://github.com/dotnet/fsharp/pull/16837))
* Add extended data for `DefinitionsInSigAndImplNotCompatibleAbbreviationsDiffer` (FS0318). ([PR #16811](https://github.com/dotnet/fsharp/pull/16811)))
* Checker/patterns: recover on unresolved long identifiers ([PR #16842](https://github.com/dotnet/fsharp/pull/16842))
* SynExprSequentialTrivia ([Issue #16914](https://github.com/dotnet/fsharp/issues/16914), [PR #16981](https://github.com/dotnet/fsharp/pull/16981))

### Changed

* Autogenerated .Is* members for unions skipped for single-case unions. ([PR 16571](https://github.com/dotnet/fsharp/pull/16571))
* `implicitCtorSynPats` in `SynTypeDefnSimpleRepr.General` is now `SynPat option` instead of `SynSimplePats option`. ([PR #16425](https://github.com/dotnet/fsharp/pull/16425))
* `SyntaxVisitorBase<'T>.VisitSimplePats` now takes `SynPat` instead of `SynSimplePat list`. ([PR #16425](https://github.com/dotnet/fsharp/pull/16425))
* Reduce allocations in compiler checking via `ValueOption` usage ([PR #16323](https://github.com/dotnet/fsharp/pull/16323), [PR #16567](https://github.com/dotnet/fsharp/pull/16567))
* Reverted [#16348](https://github.com/dotnet/fsharp/pull/16348) `ThreadStatic` `CancellationToken` changes to improve test stability and prevent potential unwanted cancellations. ([PR #16536](https://github.com/dotnet/fsharp/pull/16536))
* Refactored parenthesization API. ([PR #16461])(https://github.com/dotnet/fsharp/pull/16461))
* Optimize some interpolated strings by lowering to string concatenation. ([PR #16556](https://github.com/dotnet/fsharp/pull/16556))
* Speed up `for x in xs -> โ€ฆ` in list & array comprehensions in certain scenarios. ([PR #16948](https://github.com/dotnet/fsharp/pull/16948))
* Integral range optimizations. ([PR #16650](https://github.com/dotnet/fsharp/pull/16650), [PR #16832](https://github.com/dotnet/fsharp/pull/16832), [PR #16947](https://github.com/dotnet/fsharp/pull/16947))

---

### Release Notes .FSharp.Compiler.Service 8.0.400 (`docs/release-notes/.FSharp.Compiler.Service/8.0.400.md`)

### Fixed

* Enforce `AttributeTargets` on records. ([PR #17207](https://github.com/dotnet/fsharp/pull/17207))
* Fix a false positive of the `[<TailCall>]` analysis in combination with async. ([Issue #17237](https://github.com/dotnet/fsharp/issues/17237), [PR #17241](https://github.com/dotnet/fsharp/pull/17241))
* Extended #help directive in fsi to show documentation in the REPL. ([PR #17140](https://github.com/dotnet/fsharp/pull/17140))
* Fix internal error when dotting into delegates with multiple type parameters. ([PR #17227](https://github.com/dotnet/fsharp/pull/17227))
* Error for partial implementation of interface with static and non-static abstract members. ([Issue #17138](https://github.com/dotnet/fsharp/issues/17138), [PR #17160](https://github.com/dotnet/fsharp/pull/17160))
* Optimize simple mappings with preludes in computed collections. ([PR #17067](https://github.com/dotnet/fsharp/pull/17067))
* Improve error reporting for abstract members when used in classes. ([PR #17063](https://github.com/dotnet/fsharp/pull/17063))
* Improve error reporting when property has same name as DU case. ([Issue #16646](https://github.com/dotnet/fsharp/issues/16646), [PR #17088](https://github.com/dotnet/fsharp/pull/17088))
* Make typechecking of indexed setters with tuples on the right more consistent. ([Issue #16987](https://github.com/dotnet/fsharp/issues/16987), [PR #17017](https://github.com/dotnet/fsharp/pull/17017))
* Static abstract method on classes no longer yields internal error. ([Issue #17044](https://github.com/dotnet/fsharp/issues/17044), [PR #17055](https://github.com/dotnet/fsharp/pull/17055))
* Disallow calling abstract methods directly on interfaces. ([Issue #14012](https://github.com/dotnet/fsharp/issues/14012), [Issue #16299](https://github.com/dotnet/fsharp/issues/16299), [PR #17021](https://github.com/dotnet/fsharp/pull/17021))
* Various parenthesization API fixes. ([PR #16977](https://github.com/dotnet/fsharp/pull/16977)) 
* Files passed with -embed:relative/path/to/file are not embedded. ([Issue #16768](https://github.com/dotnet/fsharp/pull/17068))
* Fix bug in optimization of for-loops over integral ranges with steps and units of measure. ([Issue #17025](https://github.com/dotnet/fsharp/issues/17025), [PR #17040](https://github.com/dotnet/fsharp/pull/17040), [PR #17048](https://github.com/dotnet/fsharp/pull/17048))
* Fix calling an overridden virtual static method via the interface ([PR #17013](https://github.com/dotnet/fsharp/pull/17013))
* Fix state machines compilation, when big decision trees are involved, by removing code split when resumable code is detected ([PR #17076](https://github.com/dotnet/fsharp/pull/17076))
* Fix for exponential runtime in CE builders when using nested implicit yields [PR #17096](https://github.com/dotnet/fsharp/pull/17096)
* Fix several AND operator parser bugs and regressions ([Issue #16447](https://github.com/dotnet/fsharp/issues/16447), [Issue #17134](https://github.com/dotnet/fsharp/issues/17134), [Issue #16309](https://github.com/dotnet/fsharp/issues/16309), [PR #17113](https://github.com/dotnet/fsharp/pull/17113))
* Treat exceptions as types in a namespace for graph based type checking ([Issue #17262](https://github.com/dotnet/fsharp/issues/17262), [PR #17268](https://github.com/dotnet/fsharp/pull/17268))
* FS0243 - Unrecognized option: '--realsig-' #17561 ([Issue #17561](https://github.com/dotnet/fsharp/issues/17561), [PR #17268](https://github.com/dotnet/fsharp/pull/17562))

### Added

* Generate new `Equals` overload to avoid boxing for structural comparison ([PR #16857](https://github.com/dotnet/fsharp/pull/16857))
* Allow #nowarn to support the FS prefix on error codes to disable warnings ([Issue #17206](https://github.com/dotnet/fsharp/issues/16447), [PR #17209](https://github.com/dotnet/fsharp/pull/17209))
* Allow ParsedHashDirectives to have argument types other than strings ([Issue #17240](https://github.com/dotnet/fsharp/issues/16447), [PR #17209](https://github.com/dotnet/fsharp/pull/17209))
* Parser: better recovery for unfinished patterns ([PR #17231](https://github.com/dotnet/fsharp/pull/17231))
* Expose inner exception information of TypeProviders to help diagnostics in IDE ([PR #17251](https://github.com/dotnet/fsharp/pull/17251))
* Parser: recover on empty match clause ([PR #17233](https://github.com/dotnet/fsharp/pull/17233))
* Support empty-bodied computation expressions. ([Language suggestion #1232](https://github.com/fsharp/fslang-suggestions/issues/1232), [RFC FS-1144 (PR #774)](https://github.com/fsharp/fslang-design/pull/774), [PR #17352](https://github.com/dotnet/fsharp/pull/17352))

### Changed
* Enforce `AttributeTargets.Interface` ([PR #17173](https://github.com/dotnet/fsharp/pull/17173))
* Minor compiler perf improvements. ([PR #17130](https://github.com/dotnet/fsharp/pull/17130))
* Improve error messages for active pattern argument count mismatch ([PR #16846](https://github.com/dotnet/fsharp/pull/16846), [PR #17186](https://github.com/dotnet/fsharp/pull/17186))
* AsyncLocal diagnostics context. ([PR #16779](https://github.com/dotnet/fsharp/pull/16779))
* Reduce allocations in compiler checking via `ValueOption` usage ([PR #16822](https://github.com/dotnet/fsharp/pull/16822))
* Use AsyncLocal instead of ThreadStatic to hold Cancellable.Token ([PR #17156](https://github.com/dotnet/fsharp/pull/17156))
* Showing and inserting correct name of entities from unopened namespace/module ([Issue #14375](https://github.com/dotnet/fsharp/issues/14375), [PR #17261](https://github.com/dotnet/fsharp/pull/17261))
* Improve completion after method/property override ([PR #17292](https://github.com/dotnet/fsharp/pull/17292))
* Support lazy custom attributes calculation for `ILTypeDef` public API, improve `ExtensionAttribute` presence detecting perf. ([PR #16168](https://github.com/dotnet/fsharp/pull/16168))

---

### Release Notes .FSharp.Compiler.Service 9.0.100 (`docs/release-notes/.FSharp.Compiler.Service/9.0.100.md`)

### Fixed

* Fix wrong TailCall warning ([Issue #17604](https://github.com/dotnet/fsharp/issues/17604), [PR #17637](https://github.com/dotnet/fsharp/pull/17637))
* Compiler hangs when compiling inline recursive invocation ([Issue #17376](https://github.com/dotnet/fsharp/issues/17376), [PR #17394](https://github.com/dotnet/fsharp/pull/17394))
* Fix reporting IsFromComputationExpression only for CE builder type constructors and let bindings. ([PR #17375](https://github.com/dotnet/fsharp/pull/17375))
* Optimize simple mappings in comprehensions when the body of the mapping has `let`-bindings and/or sequential expressions before a single yield. ([PR #17419](https://github.com/dotnet/fsharp/pull/17419))
* C# protected property can be assigned in F# inherit constructor call. ([Issue #13299](https://github.com/dotnet/fsharp/issues/13299), [PR #17391](https://github.com/dotnet/fsharp/pull/17391))
* MethodAccessException on equality comparison of a record with private fields. ([Issue #17447](https://github.com/dotnet/fsharp/issues/17447), [PR #17391](https://github.com/dotnet/fsharp/pull/17467))
* Fix `function` implicit conversion. ([Issue #7401](https://github.com/dotnet/fsharp/issues/7401), [PR #17487](https://github.com/dotnet/fsharp/pull/17487))
* Compiler fails to recognise namespace in FQN with enabled GraphBasedChecking. ([Issue #17508](https://github.com/dotnet/fsharp/issues/17508), [PR #17510](https://github.com/dotnet/fsharp/pull/17510))
* Fix missing message for type error (FS0001). ([Issue #17373](https://github.com/dotnet/fsharp/issues/17373), [PR #17516](https://github.com/dotnet/fsharp/pull/17516))
* Nullness export - make sure option<> and other UseNullAsTrueValue types are properly annotated as nullable for C# and reflection consumers [PR #17528](https://github.com/dotnet/fsharp/pull/17528)
* MethodAccessException on equality comparison of a type private to module. ([Issue #17541](https://github.com/dotnet/fsharp/issues/17541), [PR #17548](https://github.com/dotnet/fsharp/pull/17548))
* Fixed checking failure when `global` namespace is involved with enabled GraphBasedChecking ([PR #17553](https://github.com/dotnet/fsharp/pull/17553))
* Add missing byte chars notations, enforce limits in decimal notation in byte char & string (Issues [#15867](https://github.com/dotnet/fsharp/issues/15867), [#15868](https://github.com/dotnet/fsharp/issues/15868), [#15869](https://github.com/dotnet/fsharp/issues/15869), [PR #15898](https://github.com/dotnet/fsharp/pull/15898))
* Parentheses analysis: keep extra parentheses around unit & tuples in method definitions. ([PR #17618](https://github.com/dotnet/fsharp/pull/17618))
* Fix IsUnionCaseTester throwing for non-methods/properties [#17301](https://github.com/dotnet/fsharp/pull/17634)
* Fix xmlc doc tooltip display for nullable types [#17741](https://github.com/dotnet/fsharp/pull/17741)
* Consider `open type` used when the type is an enum and any of the enum cases is used unqualified. ([PR #17628](https://github.com/dotnet/fsharp/pull/17628))
* Guard for possible StackOverflowException when typechecking non recursive modules and namespaces ([PR #17654](https://github.com/dotnet/fsharp/pull/17654))
* Nullable - fix for processing System.Nullable types with nesting ([PR #17736](https://github.com/dotnet/fsharp/pull/17736))
* Fixes for the optimization of simple mappings in array and list comprehensions. ([Issue #17708](https://github.com/dotnet/fsharp/issues/17708), [PR #17711](https://github.com/dotnet/fsharp/pull/17711))


### Added

* Support for nullable reference types ([PR #15181](https://github.com/dotnet/fsharp/pull/15181))
* Treat .ToString() on F# types as returning non-nullable string in --checknulls+ context ([PR #17547](https://github.com/dotnet/fsharp/pull/17547))
* Parser: recover on missing union case fields (PR [#17452](https://github.com/dotnet/fsharp/pull/17452))
* Parser: recover on missing union case field types (PR [#17455](https://github.com/dotnet/fsharp/pull/17455))
* Sink: report function domain type ([PR #17470](https://github.com/dotnet/fsharp/pull/17470))
* Allow access modifies to auto properties getters and setters ([Language suggestion #430](https://github.com/fsharp/fslang-suggestions/issues/430), [PR 16687](https://github.com/dotnet/fsharp/pull/16687), [PR 16861](https://github.com/dotnet/fsharp/pull/16861), [PR 17522](https://github.com/dotnet/fsharp/pull/17522))
* Render C# nullable-analysis attributes in tooltips ([PR #17485](https://github.com/dotnet/fsharp/pull/17485))
* Allow object expression without overrides. ([Language suggestion #632](https://github.com/fsharp/fslang-suggestions/issues/632), [PR #17387](https://github.com/dotnet/fsharp/pull/17387))
* Enable FSharp 9.0 Language Version ([Issue #17497](https://github.com/dotnet/fsharp/issues/17497)), [PR](https://github.com/dotnet/fsharp/pull/17500)))
* Enable LanguageFeature.EnforceAttributeTargets in F# 9.0. ([Issue #17514](https://github.com/dotnet/fsharp/issues/17558), [PR #17516](https://github.com/dotnet/fsharp/pull/17558))
* Parser: better recovery for unfinished patterns ([PR #17231](https://github.com/dotnet/fsharp/pull/17231), [PR #17232](https://github.com/dotnet/fsharp/pull/17232)))
* Enable consuming generic arguments defined as `allows ref struct` in C# ([Issue #17597](https://github.com/dotnet/fsharp/issues/17597), display them in tooltips [PR #17706](https://github.com/dotnet/fsharp/pull/17706))
* Trivia for SynTypeConstraint.WhereTyparNotSupportsNull. ([Issue #17721](https://github.com/dotnet/fsharp/issues/17721), [PR #17745](https://github.com/dotnet/fsharp/pull/17745))
* Trivia for SynType.WithNull. ([Issue #17720](https://github.com/dotnet/fsharp/issues/17720), [PR #17745](https://github.com/dotnet/fsharp/pull/17745))


### Changed

* Change compiler default setting realsig+ when building assemblies ([Issue #17384](https://github.com/dotnet/fsharp/issues/17384), [PR #17378](https://github.com/dotnet/fsharp/pull/17385))
* Change compiler default setting for compressedMetadata ([Issue #17379](https://github.com/dotnet/fsharp/issues/17379), [PR #17383](https://github.com/dotnet/fsharp/pull/17383))
* Treat `{ new Foo() }` as `SynExpr.ObjExpr` ([PR #17388](https://github.com/dotnet/fsharp/pull/17388))
* Optimize metadata reading for type members and custom attributes. ([PR #17364](https://github.com/dotnet/fsharp/pull/17364))
* Enforce `AttributeTargets` on unions. ([PR #17389](https://github.com/dotnet/fsharp/pull/17389))
* Applied nullable reference types to FSharp.Compiler.Service itself ([PR #15310](https://github.com/dotnet/fsharp/pull/15310))
* Ensure that isinteractive multi-emit backing fields are not public. ([Issue #17439](https://github.com/dotnet/fsharp/issues/17438)), ([PR #17439](https://github.com/dotnet/fsharp/pull/17439))
* Better error reporting for unions with duplicated fields. ([PR #17521](https://github.com/dotnet/fsharp/pull/17521))
* Better CE error reporting when using `use!` with `and!` ([PR #17671](https://github.com/dotnet/fsharp/pull/17671))
* Better error reporting for let bindings. ([PR #17601](https://github.com/dotnet/fsharp/pull/17601))
* Optimize ILTypeDef interface impls reading from metadata. ([PR #17382](https://github.com/dotnet/fsharp/pull/17382))
* Make ILTypeDef interface impls calculation lazy. ([PR #17392](https://github.com/dotnet/fsharp/pull/17392))
* Better error reporting for active patterns. ([PR #17666](https://github.com/dotnet/fsharp/pull/17666))
* Multiple fsi sessions use separate temporary directories ([PR #17760](https://github.com/dotnet/fsharp/pull/17760))

### Breaking Changes

---

### Release Notes .FSharp.Compiler.Service 9.0.200 (`docs/release-notes/.FSharp.Compiler.Service/9.0.200.md`)

### Fixed

* Fix Realsig+ generates nested closures with incorrect Generic  ([Issue #17797](https://github.com/dotnet/fsharp/issues/17797), [PR #17877](https://github.com/dotnet/fsharp/pull/17877))
* Fix missing TailCall warning in Sequential in use scope ([PR #17927](https://github.com/dotnet/fsharp/pull/17927))
* Fix false negatives for passing null to "obj" arguments. Only "obj | null" can now subsume any type ([PR #17757](https://github.com/dotnet/fsharp/pull/17757))
* Fix internal error when calling 'AddSingleton' and other overloads only differing in generic arity ([PR #17804](https://github.com/dotnet/fsharp/pull/17804))
* Fix extension methods support for non-reference system assemblies ([PR #17799](https://github.com/dotnet/fsharp/pull/17799))
* Ensure `frameworkTcImportsCache` mutations are threadsafe. ([PR #17795](https://github.com/dotnet/fsharp/pull/17795))
* Disallow abstract member with access modifiers in sig file. ([PR #17802](https://github.com/dotnet/fsharp/pull/17802))
* Fix concurrency issue in `ILPreTypeDefImpl`  ([PR #17812](https://github.com/dotnet/fsharp/pull/17812))
* Fix nullness inference for member val and other OO scenarios  ([PR #17845](https://github.com/dotnet/fsharp/pull/17845))
* Add warning when downcasting from nullable type to non-nullable ([PR #17965](https://github.com/dotnet/fsharp/pull/17965))
* Fix missing nullness warning in case of method resolution multiple candidates ([PR #17917](https://github.com/dotnet/fsharp/pull/17918))
* Fix failure to use bound values in `when` clauses of `try-with` in `seq` expressions ([PR #17990](https://github.com/dotnet/fsharp/pull/17990))
* Fix locals allocating for the special `copyOfStruct` defensive copy ([PR #18025](https://github.com/dotnet/fsharp/pull/18025))
* Fix lowering of computed array expressions when the expression consists of a simple mapping from a `uint64` or `unativeint` array. [PR #18081](https://github.com/dotnet/fsharp/pull/18081)
* Add missing nullable-metadata for C# consumers of records,exceptions and DU subtypes generated from F# code. [PR #18079](https://github.com/dotnet/fsharp/pull/18079)
* Reduce excess memory usage in TransparentCompiler. [PR #17543](https://github.com/dotnet/fsharp/pull/17543)
* Fix a race condition in file book keeping in the compiler service ([#18008](https://github.com/dotnet/fsharp/pull/18008))
* Fix trimming '%' characters when lowering interpolated string to a concat call [PR #18123](https://github.com/dotnet/fsharp/pull/18123)
* Completion: fix qualified completion in sequence expressions [PR #18111](https://github.com/dotnet/fsharp/pull/18111)
* Symbols: try to use ValReprInfoForDisplay in Mfv.CurriedParameterGroups ([PR #18124](https://github.com/dotnet/fsharp/pull/18124))
* Shim/file system: fix leaks of the shim [PR #18144](https://github.com/dotnet/fsharp/pull/18144)
* fsi: fix auto-loading of script file inside NuGet package ([PR #18177](https://github.com/dotnet/fsharp/pull/18177))
* Fix for `Obsolete` attribute warning/error not taken into account when used with a unit of measure [PR #18182](https://github.com/dotnet/fsharp/pull/18182)

### Added

* Let `dotnet fsi --help` print a link to the documentation website. ([PR #18006](https://github.com/dotnet/fsharp/pull/18006))
* Deprecate places where `seq` can be omitted. ([Language suggestion #1033](https://github.com/fsharp/fslang-suggestions/issues/1033), [PR #17772](https://github.com/dotnet/fsharp/pull/17772))
* Support literal attribute on decimals ([PR #17769](https://github.com/dotnet/fsharp/pull/17769))
* Added type conversions cache, only enabled for compiler runs, guarded by language version preview ([PR #17668](https://github.com/dotnet/fsharp/pull/17668))
* Added project property ParallelCompilation which turns on graph based type checking, parallel ILXGen and parallel optimization. By default on for users of langversion=preview ([PR #17948](https://github.com/dotnet/fsharp/pull/17948))
* Adding warning when consuming generic method returning T|null for types not supporting nullness (structs,anons,tuples) ([PR #18057](https://github.com/dotnet/fsharp/pull/18057))
* Sink: report SynPat.ArrayOrList type ([PR #18127](https://github.com/dotnet/fsharp/pull/18127))
* Show the default value of compiler options ([PR #18054](https://github.com/dotnet/fsharp/pull/18054))
* Support ValueOption + Struct attribute as optional parameter for methods ([Language suggestion #1136](https://github.com/fsharp/fslang-suggestions/issues/1136), [PR #18098](https://github.com/dotnet/fsharp/pull/18098))
* Cancellable: add safer APIs to check the token ([PR #18175](https://github.com/dotnet/fsharp/pull/18175))

### Changed

* Make ILTypeDef interface impls calculation lazy. ([PR #17392](https://github.com/dotnet/fsharp/pull/17392))
* Remove non-functional useSyntaxTreeCache option. ([PR #17768](https://github.com/dotnet/fsharp/pull/17768))
* Better ranges for CE `let!` and `use!` error reporting. ([PR #17712](https://github.com/dotnet/fsharp/pull/17712))
* Better ranges for CE `do!` error reporting. ([PR #17779](https://github.com/dotnet/fsharp/pull/17779))
* Better ranges for CE `return, yield, return! and yield!` error reporting. ([PR #17792](https://github.com/dotnet/fsharp/pull/17792))
* Better ranges for CE `match!`. ([PR #17789](https://github.com/dotnet/fsharp/pull/17789))
* Better ranges for CE `use` error reporting. ([PR #17811](https://github.com/dotnet/fsharp/pull/17811))
* Better ranges for `inherit` error reporting. ([PR #17879](https://github.com/dotnet/fsharp/pull/17879))
* Better ranges for `inherit` `struct` error reporting. ([PR #17886](https://github.com/dotnet/fsharp/pull/17886))
* Warn on uppercase identifiers in patterns. ([PR #15816](https://github.com/dotnet/fsharp/pull/15816))
* Better ranges for `inherit` objects error reporting. ([PR #17893](https://github.com/dotnet/fsharp/pull/17893))
* Better ranges for #nowarn error reporting; bring back #nowarn warnings for --langVersion:80; add warnings under feature flag ([PR #17871](https://github.com/dotnet/fsharp/pull/17871))
* Better ranges for #nowarn error reporting; bring back #nowarn warnings for --langVersion:80; add warnings under feature flag ([PR #17871](https://github.com/dotnet/fsharp/pull/17871))
* CheckAndThrow can be invoked only from within Cancellable context ([PR #18037](https://github.com/dotnet/fsharp/pull/18037))
* Make ILTypeDef base type calculation lazy. ([PR #18005](https://github.com/dotnet/fsharp/pull/18005))
* Removed redundant hash directives around nullness syntax ([Issue #18601](https://github.com/dotnet/fsharp/issues/18061), [PR #18203](https://github.com/dotnet/fsharp/pull/18203), [PR #18207](https://github.com/dotnet/fsharp/pull/18207))

### Breaking Changes

* Aliasing `StructAttribute` will now produce a warning (part of [Language suggestion #1136](https://github.com/fsharp/fslang-suggestions/issues/1136), [PR #18098](https://github.com/dotnet/fsharp/pull/18098))
* The `baseType` field in `SynMemberDefn.Inherit` has now type `SynType option`, to fix internal error when analyzing incomplete inherit member ([PR #17905](https://github.com/dotnet/fsharp/pull/17905))

---

### Release Notes .FSharp.Compiler.Service 9.0.202 (`docs/release-notes/.FSharp.Compiler.Service/9.0.202.md`)

### Fixed

* Fix missing TailCall warning in Sequential in use scope ([PR #17927](https://github.com/dotnet/fsharp/pull/17927))
* Fix false negatives for passing null to "obj" arguments. Only "obj | null" can now subsume any type ([PR #17757](https://github.com/dotnet/fsharp/pull/17757))
* Fix internal error when calling 'AddSingleton' and other overloads only differing in generic arity ([PR #17804](https://github.com/dotnet/fsharp/pull/17804))
* Fix extension methods support for non-reference system assemblies ([PR #17799](https://github.com/dotnet/fsharp/pull/17799))
* Ensure `frameworkTcImportsCache` mutations are threadsafe. ([PR #17795](https://github.com/dotnet/fsharp/pull/17795))
* Disallow abstract member with access modifiers in sig file. ([PR #17802](https://github.com/dotnet/fsharp/pull/17802))
* Fix concurrency issue in `ILPreTypeDefImpl`  ([PR #17812](https://github.com/dotnet/fsharp/pull/17812))
* Fix nullness inference for member val and other OO scenarios  ([PR #17845](https://github.com/dotnet/fsharp/pull/17845))
* Fix internal error when analyzing incomplete inherit member ([PR #17905](https://github.com/dotnet/fsharp/pull/17905))
* Add warning when downcasting from nullable type to non-nullable ([PR #17965](https://github.com/dotnet/fsharp/pull/17965))
* Fix missing nullness warning in case of method resolution multiple candidates ([PR #17917](https://github.com/dotnet/fsharp/pull/17918))
* Fix failure to use bound values in `when` clauses of `try-with` in `seq` expressions ([PR #17990](https://github.com/dotnet/fsharp/pull/17990))
* Fix locals allocating for the special `copyOfStruct` defensive copy ([PR #18025](https://github.com/dotnet/fsharp/pull/18025))
* Fix lowering of computed array expressions when the expression consists of a simple mapping from a `uint64` or `unativeint` array. [PR #18081](https://github.com/dotnet/fsharp/pull/18081)
* Add missing nullable-metadata for C# consumers of records,exceptions and DU subtypes generated from F# code. [PR #18079](https://github.com/dotnet/fsharp/pull/18079)
* Reduce excess memory usage in TransparentCompiler. [PR #17543](https://github.com/dotnet/fsharp/pull/17543)
* Fix a race condition in file book keeping in the compiler service ([#18008](https://github.com/dotnet/fsharp/pull/18008))
* Fix trimming '%' characters when lowering interpolated string to a concat call [PR #18123](https://github.com/dotnet/fsharp/pull/18123)
* Completion: fix qualified completion in sequence expressions [PR #18111](https://github.com/dotnet/fsharp/pull/18111)
* Symbols: try to use ValReprInfoForDisplay in Mfv.CurriedParameterGroups ([PR #18124](https://github.com/dotnet/fsharp/pull/18124))
* Shim/file system: fix leaks of the shim [PR #18144](https://github.com/dotnet/fsharp/pull/18144)

### Added

* Let `dotnet fsi --help` print a link to the documentation website. ([PR #18006](https://github.com/dotnet/fsharp/pull/18006))
* Deprecate places where `seq` can be omitted. ([Language suggestion #1033](https://github.com/fsharp/fslang-suggestions/issues/1033), [PR #17772](https://github.com/dotnet/fsharp/pull/17772))
* Support literal attribute on decimals ([PR #17769](https://github.com/dotnet/fsharp/pull/17769))
* Added type conversions cache, only enabled for compiler runs, guarded by language version preview ([PR #17668](https://github.com/dotnet/fsharp/pull/17668))
* Added project property ParallelCompilation which turns on graph based type checking, parallel ILXGen and parallel optimization. By default on for users of langversion=preview ([PR #17948](https://github.com/dotnet/fsharp/pull/17948))
* Adding warning when consuming generic method returning T|null for types not supporting nullness (structs,anons,tuples) ([PR #18057](https://github.com/dotnet/fsharp/pull/18057))
* Sink: report SynPat.ArrayOrList type ([PR #18127](https://github.com/dotnet/fsharp/pull/18127))
* Show the default value of compiler options ([PR #18054](https://github.com/dotnet/fsharp/pull/18054))
* Support ValueOption + Struct attribute as optional parameter for methods ([Language suggestion #1136](https://github.com/fsharp/fslang-suggestions/issues/1136), [PR #18098](https://github.com/dotnet/fsharp/pull/18098))
* Cancellable: add safer APIs to check the token ([PR #18175](https://github.com/dotnet/fsharp/pull/18175))

### Changed

* Make ILTypeDef interface impls calculation lazy. ([PR #17392](https://github.com/dotnet/fsharp/pull/17392))
* Remove non-functional useSyntaxTreeCache option. ([PR #17768](https://github.com/dotnet/fsharp/pull/17768))
* Better ranges for CE `let!` and `use!` error reporting. ([PR #17712](https://github.com/dotnet/fsharp/pull/17712))
* Better ranges for CE `do!` error reporting. ([PR #17779](https://github.com/dotnet/fsharp/pull/17779))
* Better ranges for CE `return, yield, return! and yield!` error reporting. ([PR #17792](https://github.com/dotnet/fsharp/pull/17792))
* Better ranges for CE `match!`. ([PR #17789](https://github.com/dotnet/fsharp/pull/17789))
* Better ranges for CE `use` error reporting. ([PR #17811](https://github.com/dotnet/fsharp/pull/17811))
* Better ranges for `inherit` error reporting. ([PR #17879](https://github.com/dotnet/fsharp/pull/17879))
* Better ranges for `inherit` `struct` error reporting. ([PR #17886](https://github.com/dotnet/fsharp/pull/17886))
* Warn on uppercase identifiers in patterns. ([PR #15816](https://github.com/dotnet/fsharp/pull/15816))
* Better ranges for `inherit` objects error reporting. ([PR #17893](https://github.com/dotnet/fsharp/pull/17893))
* Better ranges for #nowarn error reporting; bring back #nowarn warnings for --langVersion:80; add warnings under feature flag ([PR #17871](https://github.com/dotnet/fsharp/pull/17871))
* Better ranges for #nowarn error reporting; bring back #nowarn warnings for --langVersion:80; add warnings under feature flag ([PR #17871](https://github.com/dotnet/fsharp/pull/17871))
* CheckAndThrow can be invoked only from within Cancellable context ([PR #18037](https://github.com/dotnet/fsharp/pull/18037))
* Make ILTypeDef base type calculation lazy. ([PR #18005](https://github.com/dotnet/fsharp/pull/18005))
* Revert EnforceAttributeTargets Feature. ([PR #18005](https://github.com/dotnet/fsharp/pull/18005))

### Breaking Changes

* Aliasing `StructAttribute` will now produce a warning (part of [Language suggestion #18298](https://github.com/fsharp/fslang-suggestions/issues/18298), [PR #18355](https://github.com/dotnet/fsharp/pull/18355))

---

### Release Notes .FSharp.Compiler.Service 9.0.300 (`docs/release-notes/.FSharp.Compiler.Service/9.0.300.md`)

### Fixed
* Fix missing TailCall warning in TOp.IntegerForLoop ([PR #18399](https://github.com/dotnet/fsharp/pull/18399))
* Fix classification of `nameof` in `nameof<'T>`, `match โ€ฆ with nameof ident -> โ€ฆ`. ([Issue #10026](https://github.com/dotnet/fsharp/issues/10026), [PR #18300](https://github.com/dotnet/fsharp/pull/18300))
* Fix Realsig+ gene