Search each file of a multi-targeted F# project once in Find All References and Rename - #20464
Open
xperiandri wants to merge 5 commits into
Open
Search each file of a multi-targeted F# project once in Find All References and Rename#20464xperiandri wants to merge 5 commits into
xperiandri wants to merge 5 commits into
Conversation
…r tests Test helpers so far put every synthetic file into one Roslyn project. CreateMultiProjectSolution creates one project per synthetic project with project references, the way VS wires project-to-project references; CreateMultiTargetSolution creates one project per target instance sharing the project path and the document paths, the way VS loads a multi-targeted project. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The IFSharpFindUsagesContext stub of FindReferencesTests moves to RoslynTestHelpers.CreateFindUsagesContext so other test files can collect the definitions and references a search reports. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the solution Find All References and Rename searched every target-framework instance of a multi-targeted F# project in full, and built the snapshots of all projects in scope before the first search started. Instances of one project file are now grouped: the instance of the current document (or one in its dependency closure) is searched in full, the others only for files compiled solely there and for files with conditional compilation directives, whose sources can differ between instances. Searches start as soon as a project's snapshot is ready, snapshots are built only for the transparent compiler, and one SemaphoreSlim bounds the concurrent file checks across the whole search instead of per project. With EnableFastFindReferencesAndRename off every instance is still searched in full; the concurrency bound then replaces the previous per-project sequential loop. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
One project loaded as two instances (one without FOO and without the fourth file, one with both): every file is reported once, and Rename gets one document per file, owned by an instance that compiles it. The fixture lives in its own file: the modules of one file share a static initializer, so a second fixture module makes the first module's values observable before they are assigned when xunit runs the classes in parallel. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
❗ Release notes requiredYou can open this PR in browser to add release notes: open in github.dev
|
3 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Find All References and Rename on a symbol of a multi-targeted F# project did every piece of work once per target framework. Measured in the debugger on a solution with 135 project instances: a scope of two projects (the two instances of one
.fsproj) grew to 86 instances of 26 project files after adding dependents, 1246 documents in all; the snapshots of all 86 were built one after another before the first search started; every file was type checked once per instance, and FCS never shares those checks between instances; Roslyn's window hides the duplicate results, so the user only sees the time. On top of that the "skip the files before the declaration" optimisation only applied in the instance owning the firstDocumentIdof the declaration, becauseProject.FindFSharpReferencesAsynccompareddocument.Project = thisby reference.Change (
SymbolHelpers.fs,WorkspaceExtensions.fs):getSymbolUsesInProjectskeeps F# projects only (which also stopsgetFSharpOptionsForProjectfrom being asked about a C# project in the external-symbol branch) and, withEnableFastFindReferencesAndRenameon (the default), groups the instances of one project file. The instance searched in full is the one of the current document, else one in its dependency closure, else the first; the other instances only search files compiled solely there and files whose parse tree has conditional compilation directives (ParsedInput.Trivia.ConditionalDirectives), since those are the files whose sources can differ between instances.Document.FindFSharpReferencesAsynctakes anFSharpProjectSnapshot voptionand picks the FCS overload from it); oneSemaphoreSlim(ProcessorCount)bounds the concurrent file checks across the whole search instead of per project.Project.FindFSharpReferencesAsyncresolves the declaration document inside its own instance, so the compile-order skip works in every instance.findSymbolUses(CurrentDocument/SignatureAndImplementationinside the| scope ->fallback) are gone; the outer match is exhaustive.Behaviour change with the option off: every instance is still searched in full, but the concurrency bound now applies across the solution where the code used to run one project's documents sequentially and projects without a limit. Residual risk with the option on: an instance can resolve an overload differently without any
#if(an extension member shadowed by a newer BCL intrinsic); the ranking covers the polyfill case within the current project's world, and turning the option off restores the full search.Rename needs no change:
getSymbolUsesInSolutionnow yields oneDocumentIdper file plus the instance-specific ones, and Roslyn merges the edits of linked documents.Tests:
MultiTargetFindReferencesTestsloads one project as two instances (one withoutFOOand without the fourth file, one with both) through the newRoslynTestHelpers.CreateMultiTargetSolutionand, starting from either instance, checks that Find All References reports the signature, the plain use, the use under#if FOOand the use in the instance-only file exactly once each, and that Rename gets one document per file, owned by an instance that compiles it. The first commit (test helpers) is shared with #20462; the second moves theIFSharpFindUsagesContextstub intoRoslynTestHelpers.No timings are claimed: on the solution above the search scope goes from 86 instances to 26, plus the conditional and instance-only files of the other 60.
Checklist
Test cases added
Performance benchmarks added in case of performance changes
Release notes entry updated:
🤖 Generated with Claude Code