Skip to content

Answer Peek Definition from C# and Visual Basic with the F# source - #20520

Draft
xperiandri wants to merge 7 commits into
dotnet:mainfrom
xperiandri:feature/peek-cross-language
Draft

Answer Peek Definition from C# and Visual Basic with the F# source#20520
xperiandri wants to merge 7 commits into
dotnet:mainfrom
xperiandri:feature/peek-cross-language

Conversation

@xperiandri

Copy link
Copy Markdown
Contributor

Peek Definition from C# or Visual Basic on an F# symbol shows the symbol decompiled into C#, while Go
To Definition on the same symbol opens the F# file.

Roslyn's Peek never asked F#: PeekableItemFactory went from the RQName lookup straight to
metadata-as-source, and the cross-language service F# implements was consulted only by Go To
Definition. dotnet/roslyn#85257 has Peek ask the other language first, through a new
IFSharpCrossLanguageSymbolNavigationService2.TryGetNavigableFileLocationAsync that returns a file and
a position — Peek shows the file in place, so the navigable location Go To Definition gets is no use
to it.

This implements that member.

  • CrossLanguageSymbolNavigation.tryFindFileLocation finds the declaration the way cross-language
    Go To Definition already does, through tryFindDeclaration from Go To Definition from C# and VB into F# without checking the whole project #20465, and returns the document's
    path with a 0-based position.
  • The document's path, not the range's: Peek opens the path it is handed, and in an assembly built with
    a path map the range names its file relative to a root the assembly never records.
  • The location is a FileLocation record until toContract produces the tuple the Roslyn contract
    carries, so the tuple appears in one place.

The test checks, for a type, a function and a union case, that the location is the declaring
document's path and the 0-based line of the declaration.

Draft

This cannot build until dotnet/roslyn#85257 has flowed. Against a local build of that branch,
CrossLanguageSymbolNavigationTests pass (31), and with both halves deployed to the experimental hive,
Peek Definition from C# on an F# symbol shows the F# declaration in place while Go To Definition is
unchanged.

Base

Stacked on #20465, which adds CrossLanguageSymbolNavigation; the diff shrinks to its own commit once
that merges.

🤖 Generated with Claude Code

xperiandri and others added 6 commits September 6, 2026 11:17
Pure move of the doc-comment-id types, FSharpNavigableLocation and
FSharpCrossLanguageSymbolNavigationService out of GoToDefinition.fs, compiled after
NavigateToSearchService.fs so the service can use the parsed navigable items cache.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…project

Go To Definition from C# or Visual Basic into an F# project ran a full ParseAndCheckProject for
every target-framework instance of the project whose assembly name matched, on every keystroke
of F12 and without any cache. Cold, that exceeded the time Roslyn waits for a cross-language
location and it fell back to its own decompiled view.

The parsed navigable items of a document name every type and module it declares, so the files
that can hold the declaration are known from parse results alone, without a type check. Only
those files are checked, one at a time in compile order, and the member is matched by its exact
compiled id first, with the name-and-shape heuristics reserved for the last candidate whose
partial signature holds every member of the entity. The whole-project check remains the
fallback. One instance per project file goes first; the service gets its dependencies through
the MEF constructor so the lookup runs against a plain Solution in tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Go To Definition from C# on `Shape.NewCircle(…)`, `shape.IsCircle` or a nullary
case property found no F# declaration: the compiled members of a union case
are not among the entity's members, so both the exact and the shape lookup
came back empty and Roslyn decompiled instead. A module literal has the same
fate: C# sees a const field, and the F# side only searched the entity's
fields. Both now map back to their declaration.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Peek Definition on an F# symbol from C# or Visual Basic generated
metadata-as-source, so it showed the symbol decompiled into C# while Go To
Definition on the same symbol opened the F# file. With the Roslyn change that
has Peek ask the other language first, implement
IFSharpCrossLanguageSymbolNavigationService2: find the declaration the way
navigation already does and hand back the document's path and a 0-based
position.

The document's path rather than the range's: Peek opens the path it is handed,
and under a path map the range names the file relative to a root the assembly
never records. The file location stays a record until the one place that has
to produce the contract's tuple.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

❗ Release notes required

You can open this PR in browser to add release notes: open in github.dev


✅ Found changes and release notes in following paths:

Change path Release notes path Description
`vsintegration/src` docs/release-notes/.VisualStudio/18.vNext.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

1 participant