Skip to content

Show C# and VB uses of an F# symbol in Find All References - #20463

Open
xperiandri wants to merge 8 commits into
dotnet:mainfrom
xperiandri:feature/find-references-csharp
Open

Show C# and VB uses of an F# symbol in Find All References#20463
xperiandri wants to merge 8 commits into
dotnet:mainfrom
xperiandri:feature/find-references-csharp

Conversation

@xperiandri

@xperiandri xperiandri commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Description

Find All References on an F# symbol never listed the call sites in C# or VB projects. Roslyn hands F# entirely to IFSharpFindUsagesService, and the F# search only ever visited F# documents: no C# project was in scope and Project.FindFSharpReferencesAsync filters by isFSharpSourceFile. This is not related to [<CompiledName>] — the compiled name is what the cross-language lookup already uses.

Change (Symbols.fs, FindUsagesService.fs): after the F# uses have been reported, FSharpSymbol.DocumentationCommentId — the symbol's XmlDocSig, the compiled form Roslyn resolves — is looked up with DocumentationCommentId.GetFirstSymbolForDeclarationId in the compilation of every C# or VB project whose metadata references include the built assembly of a project declaring the symbol (ProjectFiltering.getProjectsReferencingAssembly on OutputFilePath; Visual Studio keeps a C# → F# project reference as a PortableExecutableReference, see ProjectSystemProjectFactory.CanConvertMetadataReferenceToProjectReference). SymbolFinder.FindReferencesAsync is limited to that project's documents, the locations are reported as FSharpSourceReferenceItems under the F# definition item, and each file span is reported once across the target-framework instances of a consumer. The consumers are searched a few at a time (each search may build a compilation) and concurrently with the F# projects, since the id and the definition items are known before either search starts; the C# locations are reported after the F# uses, so the window keeps its order. Only Find All References does this; Find Implementations and Rename keep their F#-only scope, and symbols internal to their project or declared in external assemblies are skipped. Expected failures are null results (no compilation, unresolved id, unbuilt or stale assembly) and simply yield no C# results.

Known limits: a union case resolves to its nested type, so U.NewCase(…) calls are not found; conversion operators need ~ret in the id; active patterns have no compiled name Roslyn can parse.

Test infrastructure: the test host now serves language services for any language from the same export provider and imports Microsoft.CodeAnalysis.CSharp.Workspaces (added to eng/Packages.props at the Roslyn version the tests already use). RoslynTestHelpers.CompileToAssembly builds a SyntheticProject into its OutputFilename with the checker's options; AddCSharpProject adds a C# library referencing the framework of those options and the given assemblies — added after AdhocWorkspace.AddProject, which otherwise rewrites a reference to a project's output into a project reference, unlike Visual Studio. The first two commits are shared with #20462 and #20464.

Tests (FindReferencesFromCSharpTests): the C# compilation resolves the doc comment id of the F# function and SymbolFinder finds its call site; ProjectFiltering sees the C# project as a consumer of the F# assembly; DocumentationCommentId gives T:/M: ids for a module and a function and nothing for a local; Find All References on the F# declaration reports exactly the C# twice span; Find Implementations reports nothing.

Checklist

  • Test cases added

  • Performance benchmarks added in case of performance changes

  • Release notes entry updated:

    Please make sure to add an entry with short succinct description of the change as well as link to this pull request to the respective release notes file, if applicable.

    Release notes files:

    • If anything under src/Compiler has been changed, please make sure to make an entry in docs/release-notes/.FSharp.Compiler.Service/<version>.md, where <version> is usually "highest" one, e.g. 42.8.200
    • If language feature was added (i.e. LanguageFeatures.fsi was changed), please add it to docs/release-notes/.Language/preview.md
    • If a change to FSharp.Core was made, please make sure to edit docs/release-notes/.FSharp.Core/<version>.md where version is "highest" one, e.g. 8.0.200.

    Information about the release notes entries format can be found in the documentation.
    Example:

    If you believe that release notes are not necessary for this PR, please add NO_RELEASE_NOTES label to the pull request.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Sep 6, 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

xperiandri added a commit to xperiandri/fsharp that referenced this pull request Sep 6, 2026
@github-actions github-actions Bot added ⚠️ Affects-Build-Infra Tooling check: PR touches build infrastructure ⚠️ Affects-Restore Tooling check: PR touches NuGet packages or feeds labels Sep 6, 2026
@github-actions

This comment has been minimized.

@T-Gro T-Gro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 🕵️ AI review — verify independently.

match! project.GetCompilationAsync cancellationToken with
| null -> return Seq.empty
| compilation ->
match DocumentationCommentId.GetFirstSymbolForDeclarationId(docId, compilation) with

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖 🕵️ [P2] Find All References reports the unrelated C# method and misses the F# call. With the F# reference aliased as FSLib, the editor search returns Other()'s Value span instead of Real()'s. Resolve the ID within the declaring assembly rather than taking the compilation-wide first match.

// F# library
namespace Collision
type Widget() =
    static member Value() = 1
// Reference the F# library with alias FSLib.
extern alias FSLib;
namespace Collision {
    public class Widget { public static int Value() => 2; }
}
class Consumer {
    int Real() => FSLib::Collision.Widget.Value(); // missed
    int Other() => Collision.Widget.Value();      // incorrectly reported
}

@T-Gro
T-Gro self-requested a review September 9, 2026 08:58
@T-Gro T-Gro added the AI-reviewed PR reviewed by AI review council label Sep 9, 2026
| docSig when value.LiteralValue.IsSome && docSig.StartsWith("P:", StringComparison.Ordinal) -> $"F:{docSig.Substring 2}"
| docSig -> docSig
| :? FSharpEntity as entity -> entity.XmlDocSig
| :? FSharpField as field -> field.XmlDocSig

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🤖🕵️ Enum IDs do not resolve: P:N.Color.Red returns null, but F:N.Color.Red resolves the field.

namespace N
type Color = Red = 0 | Blue = 1

Map enum-field IDs to F: and cover C#/VB callers.

xperiandri and others added 8 commits September 11, 2026 17:54
…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 test host served language services for F# only. It now creates them for
any language from the same export provider, imports the C# workspace parts,
accepts .cs documents, and gains two helpers: CompileToAssembly builds a
synthetic project into its OutputFilename with the checker's options, and
AddCSharpProject adds a C# library referencing the framework of the F# options
and given assemblies. The assemblies are added after AdhocWorkspace.AddProject,
which would otherwise rewrite a reference to a project's output into a project
reference; VS keeps a C# → F# reference as metadata.

The smoke tests check that the C# compilation resolves the documentation
comment id of an F# function, that SymbolFinder finds its call site, and that
ProjectFiltering sees the C# project as a consumer of the F# assembly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Find All References searched F# documents only: C# and VB projects were never
in scope, so call sites of F# functions from C# were missing. After the F#
uses are reported, the symbol's documentation comment id (its XmlDocSig, the
compiled form Roslyn resolves) is looked up in the compilation of every C# or
VB project whose metadata references include the assembly of a project
declaring the symbol, and SymbolFinder.FindReferencesAsync reports the
locations under the F# definition item. Multi-targeted consumers report each
file span once.

Only Find All References does this: Find Implementations and Rename keep
their F#-only scope, and symbols internal to their project or declared in
external assemblies are skipped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An F# library compiled to its assembly and a C# consumer calling its function:
Find All References on the F# declaration reports the C# call site, Find
Implementations does not, and DocumentationCommentId gives the compiled form
for a module, a function and nothing for a local.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
FCS names a module literal `P:` like any module value, but it compiles to a
const field, so `DocumentationCommentId.GetFirstSymbolForDeclarationId` found
nothing and Find All References showed no C# uses of it. The id now starts
with `F:` for a literal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The cross-language search ran after the F# one and visited the consumers one
by one, so on a solution where the F# search takes minutes the C# call sites
were the last thing to appear. The search now starts before the F# one and
runs a few consumers at a time, each search building a compilation; the
results are still reported after the F# uses, each file span once, so the
order in the window is unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the feature/find-references-csharp branch from 8b67667 to 450d60b Compare September 11, 2026 16:18
@github-actions github-actions Bot added the ⚠️ Affects-Design-Time Tooling check: PR touches type providers or dependency manager label Sep 11, 2026
@github-actions

This comment has been minimized.

@github-actions

Copy link
Copy Markdown
Contributor

🔍 Tooling Safety Check — Affects-Build-Infra, Affects-Design-Time, Affects-Restore
Affects-Build-Infra: Changes production editor project structure and dependencies.
Affects-Design-Time: Adds cross-language Find All References integration.
Affects-Restore: Changes Roslyn package references.

Generated by PR Tooling Safety Check · gpt56 3.1M ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚠️ Affects-Build-Infra Tooling check: PR touches build infrastructure ⚠️ Affects-Design-Time Tooling check: PR touches type providers or dependency manager ⚠️ Affects-Restore Tooling check: PR touches NuGet packages or feeds AI-reviewed PR reviewed by AI review council

Projects

Status: New

Development

Successfully merging this pull request may close these issues.

2 participants