Resolve Go To Definition targets through the workspace's current solution - #20462
Open
xperiandri wants to merge 4 commits into
Open
Resolve Go To Definition targets through the workspace's current solution#20462xperiandri wants to merge 4 commits into
xperiandri wants to merge 4 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 document Roslyn hands to Go To Definition can come from a snapshot taken before every project of the solution finished loading. Deciding that a symbol is external because that snapshot has no document for its file sent F# to F# navigation into a generated signature, and the same lookup silently narrowed the scope of Find All References and Rename. Look the target up in the document's own solution first and in Workspace.CurrentSolution when it is missing, normalising the path and preferring the target-framework instance the origin project depends on. The branch of Go To Definition that already sits on the declaration moves into its own member. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…arget document 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
|
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Sep 6, 2026
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
Go To Definition in an F# file, on a symbol declared in another F# project of the same solution, opened a generated signature (the "F# Metadata" document) instead of the source, and Go To Implementation reported nothing. Reproduced with the in-box tools on Visual Studio 18 Insiders while a large solution (135 project instances) was still loading.
Root cause, measured in the debugger:
GoToDefinition.FindDefinitionAtPositiondecides that a symbol is external whenoriginDocument.Project.Solutionhas no document for the file FCS reports. TheDocumentRoslyn hands to the navigation service can come from a snapshot taken before every project finished loading: for the same path,originDocument.Project.Solution.GetDocumentIdsWithFilePathreturned 0 documents whileWorkspace.CurrentSolution.GetDocumentIdsWithFilePathreturned 2. Every later lookup in that method (TryGetDocumentFromPathfor the signature/implementation counterparts) used the same snapshot, so even a symbol that passed the first check could end in a silent no-op. The same lookup inGetSymbolScopenarrowed the scope of Find All References and Rename, andrangeToDocumentSpanscould miss declaration spans.Fix:
DocumentgainsTryFindInSolutions,GetSolutionDocumentsWithFilePath,TryGetSolutionDocumentFromPathandTryGetSolutionDocumentFromFSharpRange(CodeAnalysisExtensions.fs). They look in the document's own solution first, which keeps forks carrying unsaved buffer text intact, and inWorkspace.CurrentSolutionwhen that has nothing; they normalise the path (the oldContainsDocumentWithFilePathcheck did not) and prefer the target-framework instance the origin project depends on. All lookups inGoToDefinition.fs, the scope computation inSymbols.fs, the signature/implementation counterpart lookup inSymbolHelpers.fsandrangeToDocumentSpansinFindUsagesService.fsuse them. The "caret is already on the declaration" branch ofFindDefinitionAtPositionmoves intoFindCounterpartOfDeclarationAtCaret, and the repeated "declaration in this implementation document → navigable item" sequence intoFindNavigableDeclarationIn.Tests:
RoslynTestHelperscan now build a Roslyn solution with several F# projects wired by project references (CreateMultiProjectSolution) and one with several target-framework instances of a single project (CreateMultiTargetSolution); the F# options come from the sameSyntheticProject, so FCS gets in-memory project references the way it does in VS.GoToDefinitionServiceTestsreproduces the measured condition — the origin document taken from a solution fork that lacks the library document while the workspace has it — and checks that the lookup,FindDefinitionAtPositionandGetSymbolScopeall resolve the library, plus a control that a symbol whose file is in no solution is still reported as external.Checklist
Test cases added
Performance benchmarks added in case of performance changes
Release notes entry updated:
🤖 Generated with Claude Code