F# Repository

repository·main·Indexed 26 days ago

https://github.com/dotnet/fsharp

The central hub for the development of the F# language, containing the F# compiler, F# core library, and editor tools. This documentation covers building the codebase on Windows, Linux, and macOS, configuring NuGet feeds for FSharp.Compiler.Service pre-releases, and using F# Interactive dependency manager plugins for NuGet and Paket. It also includes details on Visual F# IDE tooling (FSharp.Editor, FSharp.VS.FSI), deterministic compilation via the --deterministic flag, and contributor guides for FsLex/FsYacc and test suite execution.

Tokens
32.4K
Snippets
41
Records
147
Agent score
88%

What's inside dotnet-fsharp

  1. Overview of F# LSP Architecture

    main

    The F# Language Server Protocol (LSP) design aims to provide a fully featured LSP server implementation usable by any editor (VS Code, Vim, Visual Studio, etc.). The architecture is composed of several key components:

    • FsLSP: The core F# LSP library used to create an LSP server. It includes workspace state management, a project model, and an LSP library integration.
    • FsLSPServer: A thin wrapper around FsLSP provided as an executable. It uses STD IN/OUT for transport and is intended to be available as a dotnet tool.
    • FCS (F# Compiler Service): The underlying service that performs parsing and type checking. The LSP server interacts with FCS to provide language features.
    • NewVsix: A Visual Studio extension powered by the LSP server, built using the VisualStudio.Extensibility model to run out-of-process.
  2. Understand Multi-project Builds and Cross-project References

    main

    In F# Core Libraries (FCS), there is no single abstraction for a "solution build." Instead, multiple independent project builds are used. Each project build is logically an independent invocation of the F# compiler.

    Key Invariants

    • No Shared Nodes: Typed Tree (TAST) nodes and TcState nodes are not shared between different project compilations.
    • Data Flow:
      1. Each project produces a RawFSharpAssemblyData blob (the "output").
      2. This blob is used as the "input" for the assembly reference of consuming projects (instead of an on-disk DLL).
      3. The blob is resurrected into Typed Tree nodes in TypedTreePickle.fs within the consuming project.
    • Correctness: You cannot directly share Typed Tree nodes across projects because the process of generating and resurrecting the data performs transformations necessary for correctness. Typed Tree nodes (including CcuData nodes) are tied to a specific compilation context and its TcImports.
  3. Understand F# debug information and limitations

    main

    The F# compiler emits debug information and attributes that impact several developer experiences.

    Supported Debugging Experiences

    • Call stacks
    • Breakpoint placement
    • Locals
    • Just my code (filtering out library code)
    • Exception debugging (e.g., "first chance" exceptions)
    • Stepping
    • Watch window
    • Profiling
    • Code coverage

    Unimplemented Features

    Note that the following are currently not implemented in F#:

    • Autos window during debugging
    • Edit and Continue
    • Hot reload
  4. Understand the F# core components

    main

    The F# ecosystem consists of four primary artifacts:

    • FSharp.Compiler.Service: The core logic for F# compilation (parsing, typechecking, optimizations, IL generation) and the primary API surface for tooling.
    • fsc (F# compiler executable): A console application that invokes FSharp.Compiler.Service using command-line arguments.
    • FSharp.Core Library: The standard library containing primitive F# types, core data structures, units of measure, quotations, and asynchronous programming types.
    • fsi (F# Interactive tool): A REPL for executing F# code, loading script files, and referencing assemblies or NuGet packages.
  5. Understand equality operation semantics and performance

    main

    In F#, equality operations (a = b and a <> b) and certain FSharp.Core functions have performance and semantics determined by the types involved after inlining and optimization.

    Key constructs that involve implied equality checks include:

    • 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
  6. Explore the F# compiler source code structure

    main

    The F# compiler repository is organized into several key functional areas. Use these paths to locate specific logic within the source tree:

    • Core Library: src/FSharp.Core/
    • Syntax & Parsing: src/Compiler/SyntaxTree/ (Lexing and parsing)
    • Type Checking: src/Compiler/TypedTree/ (TypedTree and utilities) and src/Compiler/Checking/ (Checking logic)
    • Optimization: src/Compiler/Optimize/ (Optimization and lowering logic)
    • Code Generation: src/Compiler/CodeGen/ (IL code generation) and src/Compiler/AbstractIL/ (Abstract IL library)
    • Compiler Driver: src/Compiler/Driver/ (Compiler options, diagnostics, and coordination)
    • Incremental Compilation & Editor Services: src/Compiler/Service/ (Incremental build logic and public editor services API)
    • Interactive REPL: src/Compiler/Interactive/ (REPL components and notebook engine core)
    • Public API Symbols: src/Compiler/Symbols/ (Symbols available in the public compiler API)
    • Utilities: src/Compiler/Utilities/ (General utilities independent of the compiler)
    • Facilities: src/Compiler/Facilities/ (Compiler-specific functionality)
    • Visual Studio Integration: vsintegration/
    • Tests: tests/
  7. Understand F# Language Service capabilities

    main

    The F# Language Service (implemented via FSharp.Editor using FSharp.Compiler.Service) provides the underlying intelligence for IDEs like Visual Studio. It processes two main types of data to support tooling:

    1. Syntactic Data: Operations based on the source text or syntax tree (e.g., indentation, brace matching, syntax diagnostics, and formatting). These are generally computationally inexpensive (S to L cost) and run asynchronously.
    2. Semantic Data: Operations based on typecheck data and symbol resolution (e.g., Go to Definition, Rename, Find All References, and Code Completion). These are more computationally intensive (S to XL cost) as they often require inspecting project graphs or re-running typechecking.
  8. Understand F# Compiler naming concepts

    main

    The F# tooling distinguishes between four types of names for values, union cases, class/record fields, and entities. Understanding these is critical when working with compiler internals or generating diagnostics:

    • Display names (as in code): How names appear in source code. Most identifiers use double backticks (e.g., `Module name with spaces`). Operators are parenthesized (e.g., (+)), and active patterns are parenthesized (e.g., (|A|_|)). Used in signature files and diagnostics.
    • Display names (for navigation/declaration lists): Similar to display names in code, but without double backticks or parentheses (e.g., SomeType instead of `SomeType`).
    • Logical names: The canonical names used within the TypedTree. These are often used for internal representation and may require extra flags to qualify meaning.
    • Compiled names: The names that actually appear in the resulting .NET IL.
  9. Understand factors affecting F# compiler startup performance

    main

    F# compiler startup performance is influenced by several platform-independent and platform-specific factors. Understanding these can help diagnose why compilation might feel slow in certain environments:

    Platform-Independent Factors

    • Binary Loading Time: The size of the compiler binaries and whether they are pre-compiled (e.g., using NGEN or CrossGen).
    • Assembly Analysis: The time taken to open referenced assemblies (like mscorlib.dll or FSharp.Core.dll) and analyze their types and namespaces. Performance depends on whether this analysis is performed on-demand.
    • Open Declarations: The time required to process open declarations at the top of each F# file.
    • File-Specific Factors: Characteristics of the specific source files being compiled.

    Windows-Specific Behavior (Visual Studio)

    On Windows, the compiler uses NGEN to pre-compile fsc, fsi, and certain Visual Studio tooling assemblies. Visual Studio employs delayed NGEN, which means binaries are not pre-compiled immediately. Consequently, F# compilation through Visual Studio may experience slower startup times for the first few executions before the binaries are NGENed.

  10. Understand F# compiler code optimizations

    main

    The F# compiler performs several automatic optimizations to improve runtime performance. Key optimizations include:

    • Value Propagation: Propagation of known constants, known equalities (x = y), lambdas, and known values within tuples, records, or union cases.
    • Inlining: Inlining of known lambda values and tuple argument detupling.
    • Elimination:
      • Unused bindings.
      • Sequential code that has no side-effects.
      • Switches where pattern matching success/failure is definite.
      • Field access for immutable records/tuples/union-cases of known values.
    • Allocation Reduction:
      • Expanding tuple bindings (let v = (x1,...x3)) to avoid allocations if the tuple is not used as a first-class value.
      • Rewriting inner lambdas and functions into separate static methods to avoid FSharpFunc allocations.
      • Translating sequence expressions into state machines to reduce closure overhead.
    • Code Restructuring:
      • Splitting large functions into multiple methods (especially at match cases) to assist the JIT compiler.
      • Removing tailcalls when no code in the transitive closure performs a tailcall or recurses.
      • Performing eta-expansion and beta-reduction in LowerCalls.
  11. Understand the roadmap for reusing typechecking results

    main

    The F# compiler is implementing a multi-stage plan to improve compilation speed by caching and reusing typechecking results. The roadmap includes:

    1. Stage 1: Graph Generation and Comparison: Forcing the generation of typechecking (TC) graphs and comparing them to detect if retypechecking is necessary. This involves tracking compilation arguments and file update times.
    2. Stage 2: File-level Typechecking Skipping: Implementing full pickling (serialization) and unpickling (deserialization) of typechecked files (e.g., CheckedImplFile, topAttrs, and CcuThunk). This allows skipping the typechecking phase for files whose graphs haven't changed.
    3. Stage 3: Signature Data Optimization: Reducing signature data generation for projects that don't require cross-project compilation (e.g., web apps, console apps, and test projects).
    4. Stage 4: Import Data Reuse: Serializing and caching imported IL (e.g., from System.*.dll) to avoid reimporting the same assemblies across different projects in a large solution.