dotnet / dotnet/fsharp

Populate Roslyn `Project.MetadataReferences` for SDK-style F# projects

Open
#20,407 0 comments 0 reactions 0 assignees View on GitHub
Needs-Triage
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.