Skip to content

Publish NavigableContainer's cases - #20529

Open
xperiandri wants to merge 2 commits into
dotnet:mainfrom
xperiandri:feature/navigable-container-cases
Open

xperiandri wants to merge 2 commits into
dotnet:mainfrom
xperiandri:feature/navigable-container-cases

Conversation

@xperiandri

Copy link
Copy Markdown
Contributor

NavigateTo.GetNavigableItems returns items a consumer can read but not rebuild: the signature seals
NavigableContainer and hides its File and Container cases. That is enough to display a result, and not
enough to carry one out of the process that produced it. Visual Studio's Navigate To is to keep each file's
navigable items on disk between sessions, as Roslyn keeps its syntax index, and reading them back means
constructing containers.

The change

  • NavigableContainer publishes File of fileName: string and Container of info: NavigableContainerInfo. Its
    members Type, FullName and Name are unchanged.

  • Container carried a three-element tuple, containerType * nameParts * parent. Published as it was, the tuple
    would be fixed into the API, so it becomes a record with named fields first:
    NavigableContainerInfo { ContainerType; NameParts; Parent }.

  • The record is a [<Struct>]. A struct record lies inside the case exactly as the tuple's fields did — one
    allocation per container — where a reference record adds an object per container and an indirection on every
    access. Building a file and three nested containers a million times (x64, .NET 11, fsi --optimize+):

    Container payload bytes per chain
    tuple (before) 144
    [<Struct>] record 144
    reference record 216

    A struct union is not an option: Parent is recursive.

The change is additive: the surface-area baseline only gains lines. NavigableContainer keeps its structural
equality and comparison, which the struct record derives as well.

SurfaceAreaTest passes against the updated FSharp.Compiler.Service.SurfaceArea.netstandard20.bsl. The
consumer, a persistent cache for Navigate To in FSharp.Editor, is a separate PR that builds on this one.

🤖 Generated with Claude Code

`NavigateTo.GetNavigableItems` hands back items whose `Container` can be read
but not constructed: the signature seals the type and hides the `File` and
`Container` cases. That is enough to display a result and not enough to carry
one across a process boundary, which is what caching navigable items on disk
needs — the reader has to rebuild the container it deserialized.

Publish both cases. While the shape is still private, replace `Container`'s
three-element tuple with a named record so the parts have names at the point of
use, and make it a struct: a struct record is laid out inside the case exactly
as the tuple was, so this costs nothing. Building a file plus three nested
containers a million times allocates 144 bytes per chain either way, where a
reference record would take 216.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

❗ Release notes required

You can open this PR in browser to add release notes: open in github.dev


✅ Found changes and release notes in following paths:

Change path Release notes path Description
`src/Compiler` docs/release-notes/.FSharp.Compiler.Service/11.0.100.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the ⚠️ Affects-Design-Time Tooling check: PR touches type providers or dependency manager label Sep 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Tooling Safety Check — Affects-Design-Time
Affects-Design-Time: Changes navigable compiler-service data exposed to IDE features.

Generated by PR Tooling Safety Check · gpt56 3.1M ·

@T-Gro T-Gro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 🕵️ AI review — verify independently.

and NavigableContainer =
| File of fileName: string
| Container of containerType: NavigableContainerType * nameParts: string list * parent: NavigableContainer
| Container of info: NavigableContainerInfo

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 🕵️ [P2] Ordered-container comparisons now box each NavigableContainerInfo — 120 bytes per comparison at depth three, versus 0 with the previous representation.

open System
open System.Collections.Generic
open FSharp.Compiler.EditorServices

let chain file =
    [1..3] |> List.fold (fun parent _ ->
        NavigableContainer.Container {
            ContainerType = NavigableContainerType.Module
            NameParts = ["M"]
            Parent = parent
        }) (NavigableContainer.File file)

let a, b = chain "a.fs", chain "b.fs"
let comparer = Comparer<NavigableContainer>.Default
for _ in 1..10000 do comparer.Compare(a, b) |> ignore

let allocated =
    let before = GC.GetAllocatedBytesForCurrentThread()
    for _ in 1..100000 do comparer.Compare(a, b) |> ignore
    GC.GetAllocatedBytesForCurrentThread() - before
printfn "%d bytes" allocated // 12000000 bytes

@T-Gro
T-Gro self-requested a review September 14, 2026 14:20
@T-Gro T-Gro added the AI-reviewed PR reviewed by AI review council label Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚠️ Affects-Design-Time Tooling check: PR touches type providers or dependency manager AI-reviewed PR reviewed by AI review council

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

2 participants