Populate Roslyn `Project.MetadataReferences` for SDK-style F# projects
- Dominant language
- F#
- Stars
- 4.3k
- Forks
- 876
- Avg merge
- 4d 22h
- Merged PRs (30d)
- 144
Description
## Symptom
For SDK-style (CPS) F# projects, `Project.MetadataReferences` in the Roslyn workspace is **empty** (verified in the debugger: `Count = 0`). References exist only as `-r:` strings inside `FSharpProjectOptions.OtherOptions`, and only after the project has been "activated" (a file opened). Only the legacy project path (`LegacyProjectWorkspaceMap`) populates real metadata references.
This breaks or degrades:
- Object Browser / Class View referenced-assembly nodes — known gap in #20406
- "Remove unused references" (#15961)
- Any Roslyn-side or cross-language feature that inspects the F# project's reference set
- Related architectural issue: #20131 (references are opaque `-r:` strings rather than addressable `MetadataReference`s)
## Root cause chain
1. **F# receives the references and drops them.** CPS delivers the design-time command line via the MEF export `HandleCommandLineChanges` (`vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs:570-637`). Its signature includes `references: ImmutableArray`, but only `sources` and `options` are forwarded to `FSharpProjectOptionsManager.SetCommandLineOptions`; the `references` parameter is discarded.
2. **Roslyn's command-line path is a silent no-op for F#.** `CPSProject` only creates a `ProjectSystemProjectOptionsProcessor` when an `ICommandLineParserService` is registered for the language (`CPSProject_IWorkspaceProjectContext.cs:91-98`); only C# and VB register one, so both `SetOptions` overloads no-op for F#. `CPSProjectFactory.cs:79-105` also explicitly excludes non-C#/VB languages from `CommandLineArgsForDesignTimeEvaluation`.
3. **Even C# doesn't get references from `SetOptions`.** `ProjectSystemProjectOptionsProcessor` never applies parsed `CommandLineArguments.MetadataReferences`; C#/VB references arrive via explicit `IWorkspaceProjectContext.AddMetadataReference` calls from dotnet/project-system's `MetadataReferenceItemHandler` — a path the F# project system never wired.
4. **The F# options manager already expects the fix.** `tryComputeOptions` (`FSharpProjectOptionsManager.fs:361-380`) strips `-r:` from the CPS command line and re-derives references from `project.MetadataReferences` / `project.ProjectReferences` — by design the workspace is meant to be the source of truth; the collection is just never populated on the CPS path.
## Proposed fix (two small PRs, no dotnet/project-system change needed)
Because F# already receives the reference list via MEF, the fix does not need project-system involvement:
**PR 1 — dotnet/roslyn (ExternalAccess.FSharp).** Add a `ProjectId`-keyed metadata-reference API — e.g. `FSharpVisualStudioWorkspaceExtensions.SetMetadataReferences(workspace, projectId, paths)` or a `TryGetProjectContext(ProjectId)` returning the existing `IFSharpWorkspaceProjectContext` — routed internally to the CPS `ProjectSystemProject` batch API (`AddMetadataReference`/`RemoveMetadataReference`). The shape already exists in `FSharpWorkspaceProjectContext.SetMetadataReferences` (`src/VisualStudio/ExternalAccess/Core/FSharp/VS/IFSharpWorkspaceProjectContextFactory.cs:168-184`); what's missing is reaching a CPS-created context by `ProjectId`.
**PR 2 — dotnet/fsharp (after the Roslyn dependency flows).** In `HandleCommandLineChanges`, after the `ProjectId` is resolved (already done at `LanguageService.fs:596-609`), push the received `references` into the workspace via the new API, diffing against the current set so unchanged reference lists are a no-op (precedent: `LegacyProjectWorkspaceMap.fs:91-97`, `SingleFileWorkspaceMap.fs:96`). `tryComputeOptions` then picks the references up with no further change.
### Rejected alternative
Registering an `ICommandLineParserService` for F# in Roslyn: insufficient on its own (see point 3 — the options processor ignores parsed references) and would additionally require project-system changes to pass `CommandLineArgsForDesignTimeEvaluation` for F#.
## Related
#3512, #15961, #20131, #20406
/cc @vzarytovskii @T-Gro @psfinaki @0101
Contributor guide
Research direction
Start with HandleCommandLineChanges in vsintegration/src/FSharp.Editor/LanguageService/LanguageService.fs and the existing SetMetadataReferences shape in src/VisualStudio/ExternalAccess/Core/FSharp/VS/IFSharpWorkspaceProjectContextFactory.cs. Read CPSProject_IWorkspaceProjectContext.cs and FSharpProjectOptionsManager.fs to trace how a ProjectId-keyed API can update CPS metadata references. Done means the Roslyn API and F# command-line path propagate references so Project.MetadataReferences is populated and tryComputeOptions can use it.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- fsharp
- Domain
- developer-experience, tooling
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100