Let F# take part in the Navigate To search that runs while the solution loads - #85213
Conversation
`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>
|
Azure Pipelines: Successfully started running 2 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
🟡 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
IFSharpAdvancedNavigateToSearchServiceto enable an optional cached-per-project search capability during solution load. - Updates
FSharpNavigateToSearchServiceto implementIAdvancedNavigateToSearchServiceand to always complete per-project progress reporting (even when no F# service is composed). - Adds adapter implementations for
SearchCachedDocumentsAsyncandSearchGeneratedDocumentsAsync(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 |
ae89f2a to
b2c57cd
Compare
There was a problem hiding this comment.
🟢 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
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>
b2c57cd to
feeadc1
Compare
|
@T-Gro this is the Roslyn half of a Navigate To change for F#: it adds 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. |
There was a problem hiding this comment.
🟡 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
IFSharpAdvancedNavigateToSearchServiceis added to the relevantInternalAPI.Unshipped.txtbaseline(s) and tosrc/VisualStudio/ExternalAccess/FSharp/TypeForwards.cs, similar toIFSharpNavigateToSearchService, 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
|
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>
|
Addressed in the latest commit.
One thing I could not verify locally: nothing in this repo consumes the |
There was a problem hiding this comment.
🟢 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
Navigate To runs a search of its own while the solution is still loading, and a language whose
INavigateToSearchServiceis not anIAdvancedNavigateToSearchServicecontributes nothing to it:NavigateToSearcher.SearchCachedDocumentsAsyncreports 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, whoseIFSharpNavigateToSearchServicehas no way to say it can answer that search, so Ctrl+T on a largesolution lists nothing F# declares until the user searches again.
This adds the missing half of the contract to the F# external access surface.
IFSharpAdvancedNavigateToSearchServicederives fromIFSharpNavigateToSearchServiceand addsSearchCachedDocumentsAsync, with the signature ofIAdvancedNavigateToSearchService.SearchCachedDocumentsAsync— the batch of projects, the prioritydocuments, 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
IFSharpNavigateToSearchServicewould 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.ExternalAccesstargetsnet472, which has no runtimesupport for them. The same shape is already used next door by
IFSharpEditorFormattingServiceWithOptions.FSharpNavigateToSearchService— the adapter — now implementsIAdvancedNavigateToSearchService:SearchCachedDocumentsAsyncis theservice is not IAdvancedNavigateToSearchServicebranch ofNavigateToSearcheritself: when the imported service also implements the new interface, the callis 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.
SearchGeneratedDocumentsAsyncreports the projects and searches nothing. F# has no sourcegenerators, 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:
SearchProjectsAsyncreturned early when noIFSharpNavigateToSearchServicewas composed, without reporting the projects it was handed. Thesearcher 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/!= nullchecks tois null/is not null, for consistency with the type pattern the new method uses. It is cosmetic and touches nothingelse — 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 activedocument and the priority documents arriving intact, and Ctrl+T lists F# declarations immediately
while the solution is still loading.
🤖 Generated with Claude Code