Skip to content

Let F# take part in the Navigate To search that runs while the solution loads - #85213

Merged
JoeRobich merged 4 commits into
dotnet:mainfrom
xperiandri:fsharp-navigate-to-while-loading
Sep 9, 2026
Merged

Let F# take part in the Navigate To search that runs while the solution loads#85213
JoeRobich merged 4 commits into
dotnet:mainfrom
xperiandri:fsharp-navigate-to-while-loading

Conversation

@xperiandri

@xperiandri xperiandri commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Navigate To runs a search of its own while the solution is still loading, and a language whose
INavigateToSearchService is not an IAdvancedNavigateToSearchService contributes nothing to it:
NavigateToSearcher.SearchCachedDocumentsAsync reports each of its projects complete and moves on.
Nothing follows that search either — the full search is deliberately not started once loading
finishes ("Telemetry shows no meaningful change if we do a full search after this point").

F# reaches Navigate To through Microsoft.CodeAnalysis.ExternalAccess.FSharp, whose
IFSharpNavigateToSearchService has no way to say it can answer that search, so Ctrl+T on a large
solution lists nothing F# declares until the user searches again.

This adds the missing half of the contract to the F# external access surface.

IFSharpAdvancedNavigateToSearchService derives from IFSharpNavigateToSearchService and adds
SearchCachedDocumentsAsync, with the signature of
IAdvancedNavigateToSearchService.SearchCachedDocumentsAsync — the batch of projects, the priority
documents, the active document, and the two callbacks — so the F# side can stream results and report
progress the same way the C# and VB service does, and so a later F# index has the whole contract to
work with.

It is a separate optional interface rather than a member on the existing one because the
implementation lives outside this repository and Roslyn reaches Visual Studio ahead of it: a member
added to IFSharpNavigateToSearchService would fail type load during MEF composition for the F#
language service built against the previous version, taking Navigate To — and plausibly the rest of
its MEF part — down until it catches up. A default interface member is not an option either:
Microsoft.VisualStudio.LanguageServices.ExternalAccess targets net472, which has no runtime
support for them. The same shape is already used next door by
IFSharpEditorFormattingServiceWithOptions.

FSharpNavigateToSearchService — the adapter — now implements IAdvancedNavigateToSearchService:

  • SearchCachedDocumentsAsync is the service is not IAdvancedNavigateToSearchService branch of
    NavigateToSearcher itself: when the imported service also implements the new interface, the call
    is passed through with the results wrapped; when it does not, every project is reported complete
    and nothing is searched, which is byte for byte what the searcher does today for a language service
    that has not been rebuilt.
  • SearchGeneratedDocumentsAsync reports the projects and searches nothing. F# has no source
    generators, and keeping it out of the external access surface means one less contract to get wrong.

The first commit is a separate fix in the same file: SearchProjectsAsync returned early when no
IFSharpNavigateToSearchService was composed, without reporting the projects it was handed. The
searcher adds one progress item per project up front, so those items stayed outstanding until the
whole search ended and drained them.

The last commit converts the file's two remaining == null/!= null checks to is null/is not null, for consistency with the type pattern the new method uses. It is cosmetic and touches nothing
else — happy to drop it if you would rather keep the diff to the behaviour.

The consumer is a matching change in dotnet/fsharp, which cannot merge until this has flowed. It is
written and its tests pass against a local build of this branch: the F# service answers the cached
search for a project the project system has not yet given compilation options, which is the state
every project is in during load, and reports each project exactly once. No test is added here,
matching the other external access contracts.

Both halves were also deployed to the experimental hive and checked against a mixed C#/F# solution of
26 projects. Breaking in the F# implementation gives the stack ProcessOrderedProjectsAsync
NavigateToSearcher.SearchCachedDocumentsAsync → this adapter → the F# service, with the active
document and the priority documents arriving intact, and Ctrl+T lists F# declarations immediately
while the solution is still loading.

🤖 Generated with Claude Code

`SearchProjectsAsync` returns early when no `IFSharpNavigateToSearchService`
was composed, without reporting the projects it was handed. The searcher adds
one progress item per project up front, so those items stay outstanding until
the whole search ends and drains them, and the progress bar sits still in the
meantime.

Report each project instead, and search it only when there is a service to
search it with.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 8, 2026 21:49
@xperiandri
xperiandri requested a review from a team as a code owner September 8, 2026 21:49
@dotnet-policy-service dotnet-policy-service Bot added the Community The pull request was submitted by a contributor who is not a Microsoft employee. label Sep 8, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The new ExternalAccess contract additions need the corresponding TypeForward/API-baseline updates to keep the contract assembly and PublicApiAnalyzer baselines consistent.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR extends the F# ExternalAccess Navigate To contract so the F# language service can participate in Navigate To’s “cached search while solution is loading” flow, and adjusts the Roslyn-side adapter to implement the advanced Navigate To search contract.

Changes:

  • Introduces IFSharpAdvancedNavigateToSearchService to enable an optional cached-per-project search capability during solution load.
  • Updates FSharpNavigateToSearchService to implement IAdvancedNavigateToSearchService and to always complete per-project progress reporting (even when no F# service is composed).
  • Adds adapter implementations for SearchCachedDocumentsAsync and SearchGeneratedDocumentsAsync (the latter completes progress without searching).
File summaries
File Description
src/VisualStudio/ExternalAccess/Core/FSharp/NavigateTo/IFSharpAdvancedNavigateToSearchService.cs Adds a new optional advanced F# Navigate To search interface for cached-per-project searching.
src/VisualStudio/ExternalAccess/Core/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs Updates the adapter to implement IAdvancedNavigateToSearchService, fixes progress completion when the imported service is absent, and wires cached/generated search entry points.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

/// takes part in the search that runs while the solution is still loading; one that does not is skipped until the
/// solution is fully loaded, and so contributes nothing to that search.
/// </summary>
internal interface IFSharpAdvancedNavigateToSearchService : IFSharpNavigateToSearchService
@xperiandri
xperiandri force-pushed the fsharp-navigate-to-while-loading branch from ae89f2a to b2c57cd Compare September 8, 2026 23:39
Copilot AI review requested due to automatic review settings September 8, 2026 23:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The implementation matches existing NavigateToSearcher fallback/forwarding patterns, with only a minor doc-comment wording issue noted.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

xperiandri and others added 2 commits September 9, 2026 01:58
While the solution is loading, Navigate To searches through
`IAdvancedNavigateToSearchService`, and a language service that does not
implement it has each of its projects reported complete and searched not at
all. No full search follows, by design, so nothing that language declares can
be found until the user searches again. F# reaches Navigate To through external
access, which has no way to say it can answer that search.

`IFSharpAdvancedNavigateToSearchService` adds that member with the signature of
`IAdvancedNavigateToSearchService.SearchCachedDocumentsAsync` itself: the batch
of projects, the priority documents, the active document, and the two
callbacks, so the F# side can stream results and report progress the way the
C# and VB service does, and so a later F# index has the whole contract to work
with.

It derives from `IFSharpNavigateToSearchService` rather than extending it,
because the implementation ships separately and this repository reaches Visual
Studio first: a member added to the existing interface fails type load during
composition for an implementation built against the previous version, and
`Microsoft.VisualStudio.LanguageServices.ExternalAccess` targets net472, where
default interface members have no runtime. `IFSharpEditorFormattingServiceWithOptions`
is the same shape.

The adapter implements `IAdvancedNavigateToSearchService`. Its cached search is
the branch `NavigateToSearcher` has for a service without the advanced
interface: when the imported service has it, the call is passed through with
the results wrapped; when it does not, every project is reported and nothing is
searched, which is what the searcher does today. Generated documents are
reported and not searched: F# has no Roslyn source generators, and type
providers give types during checking rather than documents.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The file now tests the imported service for the advanced interface with a type
pattern, and reads oddly with the remaining two checks in the older form. Both
say the same thing to the compiler; this is the one the rest of the file is
written in.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 8, 2026 23:58
@xperiandri
xperiandri force-pushed the fsharp-navigate-to-while-loading branch from b2c57cd to feeadc1 Compare September 8, 2026 23:58
@xperiandri

Copy link
Copy Markdown
Contributor Author

@T-Gro this is the Roslyn half of a Navigate To change for F#: it adds IFSharpAdvancedNavigateToSearchService so the F# language service can answer the search that runs while a solution is still loading, which today skips F# entirely and is never followed by a full search.

The dotnet/fsharp side is written and verified against a local build of this branch — deployed to the experimental hive, Ctrl+T lists F# declarations immediately during load on a 26-project mixed C#/F# solution. It cannot be opened for merge until this contract flows.

Flagging it to you since it is the F# tooling surface.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The new ExternalAccess surface likely needs API baseline/type-forward updates, and there are a couple of small correctness/maintainability nits to address.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (3)

src/VisualStudio/ExternalAccess/Core/FSharp/NavigateTo/IFSharpAdvancedNavigateToSearchService.cs:22

  • The XML doc has a couple grammatical issues ("symbols that matches" and "previous computed cache"). Please fix these in this new external-access contract to avoid propagating the typo to downstream docs.
    /// Searches the documents inside <paramref name="projects"/> for symbols that matches <paramref
    /// name="searchPattern"/>. Results should be reported from a previous computed cache (even if that cache is out of
    /// date) to produce results as quickly as possible.  This is called for every project of the solution while it

src/VisualStudio/ExternalAccess/Core/FSharp/NavigateTo/IFSharpAdvancedNavigateToSearchService.cs:17

  • Adding a new ExternalAccess surface type usually requires updating the API baselines and type-forwarding list. Please ensure IFSharpAdvancedNavigateToSearchService is added to the relevant InternalAPI.Unshipped.txt baseline(s) and to src/VisualStudio/ExternalAccess/FSharp/TypeForwards.cs, similar to IFSharpNavigateToSearchService, so downstream consumers can reference it consistently.
internal interface IFSharpAdvancedNavigateToSearchService : IFSharpNavigateToSearchService

src/VisualStudio/ExternalAccess/Core/FSharp/Internal/NavigateTo/FSharpNavigateToSearchService.cs:115

  • The loop variable is unused; prefer _ for clarity and consistency with nearby code (e.g. NavigateToSearcher.NoOpNavigateToSearchService).
        foreach (var project in projects)
            await onProjectCompleted().ConfigureAwait(false);
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

@xperiandri

Copy link
Copy Markdown
Contributor Author

The F# side is now open as a draft: dotnet/fsharp#20492. It stays draft until this contract flows.

The type forward and the API baselines are how a type added under
`ExternalAccess` becomes visible to the consumers that reference the F# facade
rather than the core assembly, and `IFSharpNavigateToSearchService` next door
has entries in all three. Give the new interface and the two adapter methods
the same.

Also: the loop that reports the projects of a search it does not run keeps its
variable unused, so name it `_`, as `NoOpNavigateToSearchService` does; and the
doc comment, copied from `IAdvancedNavigateToSearchService`, carried two
grammar slips from there.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 9, 2026 14:40
@xperiandri

Copy link
Copy Markdown
Contributor Author

Addressed in the latest commit.

  • Added the type forward and the baseline entries: IFSharpAdvancedNavigateToSearchService and its member in ExternalAccess/FSharp/InternalAPI.Unshipped.txt, and both there and in ExternalAccess/Core/InternalAPI.Unshipped.txt for the two namespace families that file carries, plus the adapter's SearchCachedDocumentsAsync/SearchGeneratedDocumentsAsync.
  • Renamed the unused loop variables to _, matching NoOpNavigateToSearchService. Worth noting Roslyn has it both ways: the is not IAdvancedNavigateToSearchService branch in NavigateToSearcher — which the adapter mirrors — writes foreach (var project in projects).
  • Fixed the two grammar slips. They came from IAdvancedNavigateToSearchService, whose doc this one mirrors, so the same wording is still there.

One thing I could not verify locally: nothing in this repo consumes the PublicAPI item group these projects declare, and PublicApiAnalyzers is only referenced under src/Razor, so building with -p:RunAnalyzersDuringBuild=true reports no RS00xx either before or after the change. I wrote the entries by matching the shape of the existing IFSharpNavigateToSearchService ones rather than from analyzer output — please check them against what your API leg actually expects.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The changes are narrowly scoped, follow the existing Navigate To advanced-search pattern, and correctly preserve progress completion behavior across all adapter paths.

Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@JoeRobich
JoeRobich merged commit 20d2949 into dotnet:main Sep 9, 2026
23 checks passed
@dotnet-policy-service dotnet-policy-service Bot added this to the Next milestone Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-IDE Community The pull request was submitted by a contributor who is not a Microsoft employee.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants