Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
133 commits
Select commit Hold shift + click to select a range
af19aa0
Add Copilot rules reference for other agent engines
xperiandri Aug 30, 2026
8c720e2
Prefer the Visual Studio MCP server over the shell
xperiandri Aug 31, 2026
5899a3c
Add modern F# idiom instructions for Copilot and Claude Code
xperiandri Aug 30, 2026
aa35fa4
Fix NRE on unlinked placeholder entities and add release notes
xperiandri Aug 17, 2026
3aac5d0
Fix accessing `Entity.entity_modul_type` and `Entity.entity_tycon_tcaug`
xperiandri Aug 19, 2026
816acb0
Fix comment #20274#discussion_r3815216161
xperiandri Aug 20, 2026
ad16668
Reduce allocations in Md5Hasher and fix broken ThreadLocal<MD5> reuse
xperiandri Aug 17, 2026
a2f8b91
Add version-stamp cache to FSharpDocumentDiagnosticAnalyzer to avoid …
xperiandri Aug 2, 2026
05deac2
Perf: Refactor diagnostics cache with metadata record
xperiandri Aug 13, 2026
68a8e9a
Perf: Add release notes (#20121)
xperiandri Aug 13, 2026
deee539
evict diagnostics cache on workspace events
xperiandri Aug 20, 2026
5bc89a1
Perf: async isProjectInvalidated + debounce script updateProjectOptions
xperiandri Aug 3, 2026
104818f
Fix: keep updateProjectOptions debounce as plain async to avoid FS340…
xperiandri Aug 3, 2026
8841294
Perf: fix PR comments
xperiandri Aug 14, 2026
2aedc93
Refactor pattern matches and record usage for clarity
xperiandri Aug 16, 2026
59ce1b2
Perf: Add release notes (#20126)
xperiandri Aug 16, 2026
7370e6c
Switch project checker notification async block from `task` to `backg…
xperiandri Aug 20, 2026
70970af
Use ConditionalWeakTable for DocumentDiagnosticAnalyzer caches
xperiandri Aug 20, 2026
2207832
fixup! Fix NRE on unlinked placeholder entities and add release notes
xperiandri Aug 20, 2026
e6f0fdc
Rent the exact UTF8 byte count rather than the upper bound
xperiandri Aug 30, 2026
6cc7cb0
Address review: weak DocumentId cache, complete validity key, race-fr…
xperiandri Aug 30, 2026
257642b
Lock on the cache table itself instead of a separate gate object
xperiandri Aug 30, 2026
5add4c9
Dispose single-file cache subscriptions exactly once
xperiandri Aug 28, 2026
99dbe62
Make text view event subscriptions idempotent to dispose
xperiandri Aug 29, 2026
290d577
Offer F# declarations to the Copilot chat "#" mention picker
xperiandri Aug 31, 2026
d2f14be
Parallelize Copilot symbol lookup and cut allocations on the hot cach…
xperiandri Aug 31, 2026
2f9a008
Link the Copilot mention picker release note to its PR
xperiandri Aug 31, 2026
705aee2
Give FSharpPackage its own VSPackage resource set
xperiandri Aug 31, 2026
a022e05
Add release notes entry for VSPackage.resx fix
xperiandri Aug 31, 2026
51fc9d1
Seed VSPackage translations and document the resource-set split
xperiandri Sep 3, 2026
633f22f
Type `DelayedILModuleReader.result` as nullable instead of boxing
xperiandri Sep 1, 2026
19f161c
Add `Array.tryPickV` and hand back `voption` from `CompilationBinOutp…
xperiandri Sep 1, 2026
d342033
Use struct tuples for the F# project options caches
xperiandri Sep 1, 2026
760d241
Add release notes for the allocation and nullness cleanups
xperiandri Sep 1, 2026
71d507f
Fix build: two more CompilationBinOutputPath sites still returned `op…
xperiandri Sep 1, 2026
ad49a09
Return `voption` from `DelayedILModuleReader`'s stream factory
xperiandri Sep 1, 2026
d3fe914
Add release note for the DelayedILModuleReader API change
xperiandri Sep 1, 2026
20cbe54
Harden Copilot provider registration and widen one-line snippets
xperiandri Sep 3, 2026
3e229f1
Match declaration names over spans instead of building them
xperiandri Sep 3, 2026
6a1a08d
Slice source text instead of copying it line by line for outlining
xperiandri Sep 3, 2026
279875f
Track comment lines by number instead of storing their text
xperiandri Sep 3, 2026
baecfb5
Link the outlining release note to its PR
xperiandri Sep 3, 2026
5676528
Format illib.fsi and add the VisualStudio release note
xperiandri Sep 3, 2026
c6eb626
Scope the System.Memory pin to the net472 test build
xperiandri Sep 3, 2026
496c333
Address review: XML doc structure and shorthand lambda
xperiandri Sep 3, 2026
cf79761
Drop the span IndexOfOrdinal overloads that take a start index
xperiandri Sep 4, 2026
657da41
Report an absolute index from the span IndexOfOrdinal overloads
xperiandri Sep 4, 2026
0834d2e
Wrap the IndexOfOrdinal startIndex overloads' doc text in summary
xperiandri Sep 4, 2026
1fb1ebd
Use a struct tuple for the comment-scan accumulator
xperiandri Sep 4, 2026
cc79d7e
Fix release notes check: add PR link and VS entry
xperiandri Sep 2, 2026
4db8ac7
Drop conditional compilation in WeakMap.cacheConditionally
xperiandri Sep 4, 2026
4d54297
Address review: fail fast when pickling, no writes from getters
xperiandri Sep 4, 2026
190fef6
Drop the per-project PE reference cache
xperiandri Sep 4, 2026
b9ff746
Use and! for parallel async version checks
xperiandri Sep 4, 2026
5e4fde8
Answer with a voption when F# project options are unavailable
xperiandri Sep 4, 2026
ef05c38
Cache the whole-file classification of an open document
xperiandri Sep 4, 2026
6cb4662
Share one whole-file classification between requests for a version
xperiandri Sep 4, 2026
5d4313c
Outline with the quick parsing options when a project has none
xperiandri Sep 4, 2026
43644d4
Soften task{} guidance in FSharp.instructions.md
xperiandri Sep 4, 2026
30bf1d1
Prefer one traversal over a chain of transformations
xperiandri Sep 4, 2026
9fe09e9
Define the orRaise helper the voption options accessors call
xperiandri Sep 4, 2026
cf43dd1
Feed the Copilot snippet lines as memory slices too
xperiandri Sep 4, 2026
9820cdb
Assert instead of substituting contents of unlinked entities
xperiandri Sep 5, 2026
dcb7b3a
Recover per resolution in GetSemanticClassification
xperiandri Sep 5, 2026
eb0ae65
Fail fast when pickling an entity that was never linked
xperiandri Sep 5, 2026
3e10f98
Keep placeholder fields non-nullable; IsLinked is the contract
xperiandri Sep 5, 2026
1fa2899
Record file-watching design review vs Roslyn
xperiandri Aug 17, 2026
234b0c1
Add IVsAsyncFileChangeEx2 file change watcher adapter
xperiandri Aug 17, 2026
8af80f0
Wire reference file watching into FSharpProjectOptionsReactor
xperiandri Aug 17, 2026
f9bac56
Push-based invalidation for script references and snapshot reference …
xperiandri Aug 17, 2026
97dd66b
Add FileChangeWatcher unit tests and VS release notes
xperiandri Aug 17, 2026
4d62aef
Watch script #load sources via the reference change tracker
xperiandri Aug 17, 2026
55f0b2a
Use `voption` for perf-critical option values
xperiandri Aug 18, 2026
c81abfd
Sync the file watcher with #20457 after its review rounds
xperiandri Sep 5, 2026
56e1a85
Keep reference stamps inside the tracker's watch entries
xperiandri Sep 6, 2026
8ce6db9
Watch each project's reference set for its stamps
xperiandri Sep 6, 2026
494422c
Read snapshot reference stamps from the tracker instead of stat'ing
xperiandri Sep 6, 2026
9808777
Document the stamp consumer and add its release note
xperiandri Sep 6, 2026
39bd394
Expose Dispose directly on the tracker and sort the reactor's opens
xperiandri Sep 6, 2026
62326df
Dispose the watcher's agent directly
xperiandri Sep 6, 2026
30c9f66
Stop Debug builds writing StandardOutput.txt/StandardError.txt into t…
xperiandri Sep 6, 2026
25c0004
Link the FxResolver release note to PR #20458
xperiandri Sep 6, 2026
f4389d9
Call String.StartsWith/EndsWith directly instead of the illib helpers
xperiandri Sep 6, 2026
d17ac6d
Let FromOptions take reference stamps from the host
xperiandri Sep 6, 2026
6e4a76f
Make the FromOptions stamp test asynchronous
xperiandri Sep 6, 2026
81329bf
Reuse the in-memory PE reference of a C# project while its semantic v…
xperiandri Sep 6, 2026
5ad59b6
Move the PE reference release note to 18.vNext.md and link PR #20460
xperiandri Sep 6, 2026
515c68d
Wrap the PE reference cache doc comment in <summary> and reference Co…
xperiandri Sep 6, 2026
728049e
Address PR #20460 review: version-first lookup, no Refresh race, dead…
xperiandri Sep 6, 2026
ebfb887
Build multi-project and multi-target Roslyn solutions in FSharp.Edito…
xperiandri Sep 6, 2026
13089bc
Resolve navigation targets through the workspace's current solution
xperiandri Sep 6, 2026
4da550e
Test Go To Definition against a solution snapshot that predates the t…
xperiandri Sep 6, 2026
291ccbe
Add the release note for PR #20462
xperiandri Sep 6, 2026
29bd30a
Move the cross-language symbol navigation service into its own file
xperiandri Sep 6, 2026
0b83c07
Locate C# and VB navigation targets in F# without checking the whole …
xperiandri Sep 6, 2026
11a865a
Test cross-language navigation into F# declarations
xperiandri Sep 6, 2026
80df6aa
Search each file of a multi-targeted project once and throttle across…
xperiandri Sep 6, 2026
bd441a9
Share the find-usages test context between test files
xperiandri Sep 6, 2026
14a1b0f
Test Find All References across target-framework instances
xperiandri Sep 6, 2026
7c6abbb
Host C# projects in the editor test workspace
xperiandri Sep 6, 2026
c15c44d
Report C# and VB uses of an F# symbol in Find All References
xperiandri Sep 6, 2026
66d560b
Test Find All References from an F# declaration into C#
xperiandri Sep 6, 2026
fe6fde6
Add the release note for PR #20463
xperiandri Sep 6, 2026
1efdb9d
Add the release note for PR #20464
xperiandri Sep 6, 2026
8a82896
Add the release note for PR #20465
xperiandri Sep 6, 2026
09fa049
Navigate to union cases and module literals from their compiled members
xperiandri Sep 6, 2026
361ba95
Give a literal the field id Roslyn resolves
xperiandri Sep 6, 2026
9d60b0d
Ignore --pathmap in the IDE's project options
xperiandri Sep 7, 2026
88e2509
Test navigation into a project built with a path map
xperiandri Sep 7, 2026
a42ebf2
Add the release note for PR #20470
xperiandri Sep 7, 2026
090cd93
Build a project's TcGlobals with its own path map when the framework …
xperiandri Sep 7, 2026
1bf0466
Test that a cached framework set does not lend its path map to a sibl…
xperiandri Sep 7, 2026
8f44a34
Add the release note for PR #20476
xperiandri Sep 7, 2026
71cea4d
Make the path-map checker test asynchronous
xperiandri Sep 7, 2026
cc43c2e
Guard the stack when checking long sequence expressions
xperiandri Sep 7, 2026
c10369a
Add the PR link to the release note
xperiandri Sep 7, 2026
a06b67b
Search each file of a multi-targeted project once in NavigateTo and r…
xperiandri Sep 7, 2026
e9f1e4e
Test NavigateTo across the target-framework instances of a project
xperiandri Sep 7, 2026
0cd5a0f
Search the C# and VB consumers alongside the F# projects
xperiandri Sep 7, 2026
20d1c54
Materialize the project list of a multi-project solution once
xperiandri Sep 7, 2026
e7dae62
Take the main thread before registering the Copilot context provider
xperiandri Sep 8, 2026
77ad89c
Trace Copilot context-provider registration through the output pane
xperiandri Sep 8, 2026
21e587f
Wait for Go To Definition through the threaded-wait dialog
xperiandri Sep 7, 2026
84818eb
Do not nest cancellableTask inside task in TryGoToDefinition
xperiandri Sep 7, 2026
7dced5e
Add the release note link for PR #20482
xperiandri Sep 7, 2026
505b9cf
Answer the Copilot mention picker from what is already parsed
xperiandri Sep 8, 2026
24b1e78
Report the references of a document together and share the cores
xperiandri Sep 8, 2026
eb8b48c
Keep the main thread out of the work that navigation does
xperiandri Sep 8, 2026
aa424f2
Name the symbol Go To Definition looks for instead of listing them all
xperiandri Sep 8, 2026
7b9e83f
Report a use once when several instances compile its file
xperiandri Sep 8, 2026
0a6ba94
Take the instance to search in full without ordering the rest
xperiandri Sep 8, 2026
49f4629
Carry navigation results in value options and struct tuples
xperiandri Sep 8, 2026
ddffeff
Answer the Navigate-To search that runs while the solution loads
xperiandri Sep 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .claude/rules/CcrOptout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
paths:
- "**"
---

@../../.github/instructions/CcrOptout.instructions.md
6 changes: 6 additions & 0 deletions .claude/rules/CodeGen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
paths:
- "src/Compiler/CodeGen/**/*.{fs,fsi}"
---

@../../.github/instructions/CodeGen.instructions.md
6 changes: 6 additions & 0 deletions .claude/rules/ComponentTests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
paths:
- "tests/FSharp.Compiler.ComponentTests/**/*.fs"
---

@../../.github/instructions/ComponentTests.instructions.md
6 changes: 6 additions & 0 deletions .claude/rules/DebugEmit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
paths:
- "src/Compiler/AbstractIL/ilwritepdb.{fs,fsi}"
---

@../../.github/instructions/DebugEmit.instructions.md
9 changes: 9 additions & 0 deletions .claude/rules/EngVersioning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
paths:
- "eng/Packages.props"
- "eng/Versions.props"
- "eng/Version.Details.xml"
- "eng/Version.Details.props"
---

@../../.github/instructions/EngVersioning.instructions.md
6 changes: 6 additions & 0 deletions .claude/rules/ExpertReview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
paths:
- "src/Compiler/**/*.{fs,fsi}"
---

@../../.github/instructions/ExpertReview.instructions.md
6 changes: 6 additions & 0 deletions .claude/rules/FSComp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
paths:
- "src/Compiler/FSComp.txt"
---

@../../.github/instructions/FSComp.instructions.md
8 changes: 8 additions & 0 deletions .claude/rules/FSharp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
paths:
- "src/**/*.{fs,fsi,fsx}"
- "vsintegration/src/**/*.{fs,fsi}"
- "tests/**/*.{fs,fsi,fsx}"
---

@../../.github/instructions/FSharp.instructions.md
6 changes: 6 additions & 0 deletions .claude/rules/FSharpCore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
paths:
- "src/FSharp.Core/**/*.{fs,fsi}"
---

@../../.github/instructions/FSharpCore.instructions.md
6 changes: 6 additions & 0 deletions .claude/rules/LSP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
paths:
- "src/FSharp.Compiler.LanguageServer/**/*.{fs,fsi}"
---

@../../.github/instructions/LSP.instructions.md
10 changes: 10 additions & 0 deletions .claude/rules/NoBloat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
paths:
- "src/Compiler/**/*.{fs,fsi}"
- "vsintegration/src/**/*.{fs,fsi}"
- "tests/FSharp.Compiler.ComponentTests/**/*.fs"
- "tests/FSharp.Compiler.Service.Tests/**/*.fs"
- "vsintegration/tests/**/*.fs"
---

@../../.github/instructions/NoBloat.instructions.md
6 changes: 6 additions & 0 deletions .claude/rules/Optimizer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
paths:
- "src/Compiler/Optimize/**/*.{fs,fsi}"
---

@../../.github/instructions/Optimizer.instructions.md
9 changes: 9 additions & 0 deletions .claude/rules/SyntaxTree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
paths:
- "src/Compiler/SyntaxTree/SyntaxTree.{fs,fsi}"
- "src/Compiler/SyntaxTree/SyntaxTreeOps.{fs,fsi}"
- "src/Compiler/SyntaxTree/ParseHelpers.{fs,fsi}"
- "src/Compiler/pars.fsy"
---

@../../.github/instructions/SyntaxTree.instructions.md
8 changes: 8 additions & 0 deletions .claude/rules/TypedTreePickle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
paths:
- "src/Compiler/TypedTree/TypedTreePickle.{fs,fsi}"
- "src/Compiler/TypedTree/TypedTree.{fs,fsi}"
- "src/Compiler/Driver/CompilerImports.{fs,fsi}"
---

@../../.github/instructions/TypedTreePickle.instructions.md
90 changes: 90 additions & 0 deletions .github/instructions/FSharp.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
applyTo:
- "src/**/*.{fs,fsi,fsx}"
- "vsintegration/src/**/*.{fs,fsi}"
- "tests/**/*.{fs,fsi,fsx}"
---

# Writing F#

Which constructs to reach for. Code shape and comments are NoBloat's; naming and the abbreviation glossary belong to `docs/coding-standards.md` and `DEVGUIDE.md`.

These rules govern code you write or touch. Do not sweep the codebase to apply them – `CONTRIBUTING.md`: *"DO NOT submit large code formatting changes without discussing with the team first."*

## Tools

When the IDE's F# semantic tools are unavailable, use the `F#` MCP server (`.mcp.json`, FsLangMCP) for navigation, symbol discovery, diagnostics and cross-project usage search. Prefer its semantic tools over plain text search for F#-specific work.

## Strings

- Prefer interpolated strings over `sprintf` and `String.Format`. Use `$"""…"""` when the text itself contains quotes.
- Format specifiers are valid in interpolated strings and help type inference: `$"count %d{n}"`.
- `nameof` over a string literal that names a value, member or type.
- An explicit `StringComparison` on every `Equals`, `StartsWith`, `EndsWith`, `Contains`, `IndexOf` and `Compare`, and an explicit comparer on every `HashSet<string>` and `Dictionary<string, _>`. `Ordinal` by default, `OrdinalIgnoreCase` for identifiers and paths. Culture-sensitive comparison is a decision, never a default.

## Values and types

- `voption` – `ValueSome`/`ValueNone` – over `option` when the value does not escape; it is this compiler's option type. Exception: when an API hands you `'T option` (`Seq.tryHead`, `List.tryFind`), unwrap with `Option.defaultValue`/`Option.defaultWith` directly – do not insert `ValueOption.ofOption` just to switch modules.
- `struct ('T1 * 'T2)` tuples and `[<Struct>]` types on allocation-sensitive paths.
- Anonymous struct records (`struct {| … |}`) over bare tuples for multi-value returns of internal helpers. Public FCS surface is governed by `.fsi` files and compatibility – do not change it for style.
- The compiler generates `IsCaseName` properties (`IsDefault`, `IsCustom`) for DU cases – use them for a specific-case check instead of a full `match`.
- Deconstruct `KeyValuePair` with the `KeyValue` active pattern: `for KeyValue(k, v) in map do …`.
- `[<InlineIfLambda>]` on `inline` higher-order helpers whose lambda argument must not become a closure.

## Lambdas and collections

- Prefer the `_.Property` shorthand in pipeline position: `tys |> List.map _.Type`. Complex expressions (`fun x -> x.Name = name`, `fun x -> x.A, x.B`) cannot use it. Never add a space – `_.MethodCall ()` breaks parsing. Unrelated to the `member _.Foo` self-identifier.
- Eta-reduce: `Seq.map (fun x -> someFunction x)` must become `Seq.map someFunction`.
- Prefer a single traversal – one `fold`, loop, or comprehension – to a chain of transformations: it allocates nothing per element, where a chain allocates at every stage.
- When the chain reads better than one pass, route it through `Seq` and materialize once at the end – a `List`/`Array` chain allocates a whole intermediate collection per stage, a `Seq` chain only an enumerator.
- Concatenate with `[ yield! xs; yield! ys ]` / `seq { yield! xs; yield! ys }` rather than `@` or `Seq.append` – `@` forces both sides to lists and is O(n).
- Cast sequence items with `Seq.cast<Target>`, not `Seq.map (fun item -> item :> Target)`.

## Async and exceptions

- `src/Compiler` targets `netstandard2.0`: prefer `async { }` and the repo's `cancellable { }` (`src/Compiler/Utilities/Cancellable.fs`). `task { }` appears only in `Service/FSharpProjectSnapshot.fs` and `Service/FSharpWorkspaceQuery.fs` – avoid it elsewhere in new core code.
- `vsintegration` runs on the VS threading model where `task { }` is at home. When an override must return non-generic `Task`, annotate explicitly – `override _.M(…) : Task = task { … }` – never cast through pipelines.
- Thread cancellation through; see `ExpertReview.instructions.md`.
- `reraise ()` does not compile inside a `task`/`async` CE (FS0413). There, rethrow with `ExceptionDispatchInfo.Capture(ex).Throw()`; outside CEs plain `reraise ()` is correct.

## Nullness

Enabled in `src/Compiler`, `src/FSharp.Build`, `src/FSharp.Compiler.LanguageServer`. There:

- Declare non-nullable; check for `null` at entry points. Trust C#/F# annotations – no null checks where the type system says a value cannot be null.
- Prefer `match x with | null -> … | x -> …` over `isNull` – the match narrows the type, `isNull` does not.
- Use `withNull` to hint nullability instead of boxing (`isNull (box f)`).
- Before suppressing a nullness warning (3261, 3262, …), escalate in order: `nonNull value` (runtime assert, fail fast) → `Unchecked.nonNull value` (null provably impossible upstream) → inline `#nowarn`/`#warnon` pair, centralised in one interop helper rather than scattered across call sites.

## Warning suppression

Inline `#nowarn "NN"` / `#warnon "NN"` pairs around the smallest possible scope – they are valid anywhere in an `.fs` file, not only at the top. File-level suppression is a last resort.

## Classes (mostly `vsintegration`)

- Initializer syntax over post-construction property assignment: `MyType(ctorArg, MutableProp1 = v1, MutableProp2 = (5 |> string))` – settable properties by name after positional arguments, computed values in parentheses.
- Extension members consumable from C#: `[<AutoOpen; Extension>]` module, `[<Extension; CompiledName "…">]` on each member.
- Prefer a root module (`module Ns.FeatureExtensions`) over `namespace` + a static holder type for extension files; name it after the single target type plus `Extensions`, or after the feature when there are several targets.
- XML doc comments that use markup (`<see/>`, `<c/>`) need their text wrapped in `<summary>`.

## Opens

Sort into blank-line-separated groups, alphabetically within each: `System.*` → `Microsoft.*` → `Internal.Utilities.*` → `FSharp.Compiler.*` (see the top of `src/Compiler/Service/TransparentCompiler.fs`). `open type` last within its group; type and module aliases at the very end.

## Do not mistake for conventions

This codebase implements every F# feature, so finding one here is no evidence that it is used here. These have no foothold – introducing them is a new pattern, not a continuation:

- `while!` and `and!` – no uses at all; the matches are the parser and the checker implementing them.
- `[<TailCall>]` – a handful of deliberate assertions on specific recursive functions, not a habit.

## The language version is not uniform

`FSharp.Profiles.props` sets `LangVersion=preview`, but not under `Configuration=Proto` and not when `BUILDING_USING_DOTNET=true`. The compiler bootstraps, so a feature this repository is *adding* cannot be used in its own source until it ships in the SDK compiler named by `global.json` – otherwise the Proto stage fails.

- `src/FSharp.Build` is pinned to `LangVersion 9`; it can load in Visual Studio against an older FSharp.Core.
- `src/FSharp.Core` leaves nullness off and is bound by `docs/fsharp-core-notes.md`.

## Tests

The ComponentTests DSL and its pipeline are covered by `ComponentTests.instructions.md`. Name tests with backticked spaces: ``let ``Issue 12345 - brief description`` () = …``.
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Agent Instructions

See [`.github/copilot-instructions.md`](.github/copilot-instructions.md) for build, test, and coding rules for this repository.
18 changes: 18 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@.github/copilot-instructions.md

## Visual Studio first

The `vs` MCP server reaches the Visual Studio instance holding this repo's solution. Where it and the shell both work, use it — it reports what the IDE's compiler and symbol graph know, not what the text files say.

- Build with `build_solution` / `build_project` / `build_clean` rather than `dotnet build` or `msbuild`; the Build section above is the fallback for when no solution is loaded (`ide_get_workspace_folders` is empty).
- Read errors from `ide_get_diagnostics`, not by parsing build output.
- Navigate and rename with `nav_go_to_definition`, `nav_find_references`, `nav_search_workspace_symbols`, `nav_rename_symbol` — the symbol graph, not `grep` plus hand edits.
- Format what you touched with `document_format` / `document_organize_imports` before reaching for `dotnet fantomas`.

Before editing a file on disk, `document_check_dirty` it. An unsaved VS buffer is the real content: read it with `document_read_buffer` and `document_save` first, or the edit lands on stale text and the user's next save reverts it.

`build_*` compiles whichever configuration the IDE has active, and `solution_set_configuration` changes it for the user's next manual build too — read `solution_get_configuration` before assuming `Debug`.

`project_add_file` appends without a position, so it cannot place a new `<Compile Include>` correctly in an order-sensitive F# project: add those to the `.fsproj` by hand.

Tests have no IDE path — the `vs` server exposes no Test Explorer tools (https://github.com/Corsinvest/cv4vs-agents/issues/192), so they run through `dotnet test` as described above.
66 changes: 66 additions & 0 deletions docs/ide/file-watching.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# File watching in FSharp.Editor

`vsintegration/src/FSharp.Editor/LanguageService/FileChangeWatcher.fs` is the F# counterpart of
Roslyn's `FileChangeWatcher` and `ReferenceFileChangeTracker` (both internal to
`Microsoft.VisualStudio.LanguageServices` and not exposed through `ExternalAccess.FSharp`).

## What the workspace already gives us

Not every on-disk change needs this watcher. A `-r:` that the Roslyn workspace holds as a
`MetadataReference` is already watched by Roslyn: `ProjectSystemProjectFactory` advises every
reference path, and when one changes it swaps the reference on the solution, which bumps
`Project.Version`. `FSharpProjectOptionsReactor` sees that version through `isProjectInvalidated`,
recomputes, and calls `InvalidateConfiguration` — measured in VS, that path wins the race against
a watcher subscribed to the same file, because Roslyn batches over 500 ms where this tracker
additionally debounces for 2 s.

So a second subscription to the same reference set buys nothing. What the workspace does *not*
cover is everything it has no document or reference for, and every stat FCS still performs
internally:

- `#load` sources of a script: not documents, not references, invisible to the workspace.
- `IsReferencesInvalidated` on the incremental builder, which stats every reference on every
request.
- `ReferencesOnDisk` on snapshot reuse, which did the same per comparison — the consumer below.

## Shape

- **Service.** `IVsAsyncFileChangeEx2`, obtained asynchronously; nothing ever blocks on the UI
thread. Callbacks arrive through `IVsFreeThreadedFileChangeEvents2` and stay on background
threads.
- **Batching.** Subscribe/unsubscribe operations go through a single-consumer queue with a 500 ms
window (Roslyn's empirical value for solution open/close). Consecutive operations of the same
kind, and for file watches the same sink, are coalesced into one service call.
- **Directory watches.** A context starts with recursive `.dll` watches on the places reference
assemblies live: `DOTNET_ROOT/packs` and the machine-wide `dotnet/packs`, the .NET Framework
reference assemblies, and the NuGet cache (`NUGET_PACKAGES` or `~/.nuget/packages`). A file
under one of them costs no cookie of its own. Roslyn does not watch the NuGet cache; a consumer
that watches every `-r:` uniformly wants it, since package assemblies are the bulk of them and
the alternative is a per-file advise for each.
- **Per-file watches.** Paths outside those directories (project outputs, loose assemblies) get
an individual advise, ref-counted across consumers by `FSharpReferenceChangeTracker`. The
tracker also keeps the last-write stamp of each watched path (see Consumers).
- **Debounce.** A rebuild writes a temp file and renames it, producing several notifications; the
tracker fires one callback per path after 2 s of quiet.

## Consumers

**Snapshot reference stamps.** `FSharpProjectOptionsReactor` watches the `-r:` set of every
project it computes options for, diffed on recompute so an unchanged set touches nothing. The
tracker keeps each path's last-write stamp inside its watch entry and drops it on the raw change
notification, before the debounce. The `ReferencesOnDisk` guard in `createProjectSnapshot` reads
stamps through `IReferenceStamps`, so the comparison that runs for every new `Project` instance is
a dictionary read per reference instead of a stat. A path nobody watches is stat'd directly. A
mismatch against the snapshot's own stamps (FCS stats when it builds a snapshot) drops the
project's stamps, so a missed notification costs one re-stat pass rather than a rebuild per
`Project` instance. The reactor watch exists for these stamps, not to invalidate the FCS build —
Roslyn already does that, as the previous section says.

Still to come:

1. Scripts: watch `#load` sources so an edit outside the editor drops the cached options for
that document.
2. `FSharpProjectSnapshot.FromOptions` stats every `-r:` when a snapshot is built from scratch;
an overload taking host-supplied stamps lets it read the same cache.
3. A reference-change notification for the incremental builder on the FCS side, the analogue of
`useChangeNotifications` for sources, so `IsReferencesInvalidated` stops stat'ing at all.
Loading
Loading