Skip to content

Find All References and Rename Symbol Bug Fixes#19252

Merged
T-Gro merged 45 commits intomainfrom
rename-fixes
Feb 12, 2026
Merged

Find All References and Rename Symbol Bug Fixes#19252
T-Gro merged 45 commits intomainfrom
rename-fixes

Conversation

@T-Gro
Copy link
Member

@T-Gro T-Gro commented Feb 3, 2026

Find All References and Rename Symbol Bug Fixes

This PR addresses multiple longstanding bugs in the Find All References (FAR) and Rename Symbol features.

F# Compiler Service Layer

Visual Studio Editor Layer

Notes

T-Gro added 30 commits January 27, 2026 13:11
This fixes issues #14969 and #19173 where active pattern cases were not
being found in signature files by Find All References.

Root cause: When a value is published via MakeAndPublishVal, only
Item.Value was registered with CallNameResolutionSink. For active
patterns in implementation files, the individual cases are registered
during TcLetBinding. However, signature files don't go through that
path, so active pattern cases were missing.

Fix: In MakeAndPublishVal, when processing a signature file (inSig=true),
also check if the value is an active pattern using TryGetActivePatternInfo.
If so, register each case with Item.ActivePatternResult at its range.

Changes:
- src/Compiler/Checking/Expressions/CheckExpressions.fs: Add active
  pattern case registration for signature files
- tests/FSharp.Compiler.ComponentTests/FSharpChecker/FindReferences.fs:
  Update tests to expect correct behavior and add new test for case
  distinction in signature files
When renaming a property with explicit get/set accessors, the rename
operation now correctly preserves the get and set keywords instead
of replacing them with the new property name.

Changes:
- Added Tokenizer.tryFixupSpan to detect and filter out property accessor
  keywords (get/set) from rename operations
- Updated InlineRenameService.FindRenameLocationsAsync to use tryFixupSpan
  and skip locations where it returns ValueNone
- Added test documenting compiler service behavior for property references
- Added VS layer test for property rename with get/set accessors

The fix works by detecting when the span text after fixup is exactly
'get' or 'set' and excluding such spans from the rename locations.
The previous fix only applied tryFixupSpan filtering to InlineRenameService.
This commit extends the fix to FindUsagesService.onSymbolFound so that
'Find All References' also filters out property accessor keywords (get/set).

Changes:
- FindUsagesService.onSymbolFound: Use Tokenizer.tryFixupSpan instead of fixupSpan
  to filter out property accessor keywords from references
- Update test comment to accurately describe where filtering occurs
- Fix fixupSpan to not split operators on '.' character
- Fix operator-to-operator rename by skipping backtick normalization for operators
- Add test for operators with '.' (like '-.-')

Fixes #14057
Fixes #17221
- Add 2 tests in SingleLineInterfaceSyntax module to verify Find All References
  works correctly for single-line interface syntax
- Test interface member references (definition, implementation, usage)
- Test interface type references (definition, implementation, cast)
- All 42 FindReferences tests pass

Note: The VS crash mentioned in #15399 was identified as a VS-side bug that also
affects C# when renaming the last symbol in a file (not an F# issue).
When using #line directives (common in generated code like parser/lexer files),
Find All References now correctly returns ranges at the remapped file/line
positions from the directive, matching C# behavior.

The fix applies range.ApplyLineDirectives() when returning ranges from
ItemKeyStore.FindAll, converting original file positions to the positions
specified by #line directives.

Added test to verify references are found at correct remapped locations.
Fix SynPat.Or pattern binding classification (#5546):
- In disjunctive 'or' patterns like '| x | x', the second occurrence of 'x'
  was incorrectly marked as ItemOccurrence.Binding. It should be marked as
  ItemOccurrence.Use since only the left-most path introduces the binding.
- Changed CheckPatterns.fs to emit ItemOccurrence.Use for non-left-most paths.

Filter synthetic event handler symbols (#4136):
- Events with [<CLIEvent>] generate synthetic 'handler' values that were
  incorrectly appearing in GetAllUsesOfAllSymbolsInFile results.
- Added filtering in IncrementalBuild.fs and TransparentCompiler.fs to skip
  items with synthetic ranges when building the ItemKeyStore.

Tests:
- Added test for SynPat.Or pattern to verify second binding is classified as Use
- Added test for event handler to verify synthetic 'handler' symbols are filtered
- All 45 FindReferences tests pass
- Add isFSharpSourceFile function to Pervasive.fs to check for F# file extensions
- Filter Project.FindFSharpReferencesAsync to only process F# source files
- Fixes crash when F# projects contain non-F# files (e.g., .cshtml)

Issue #15721 (disposable type rename timing) was investigated and determined
to be a VS IDE layer caching issue, not a compiler service bug.
…fix (#5546)

The fix for #5546 changes Or pattern secondary bindings from Binding to Use.
This updates the ProjectAnalysisTests baseline to match the new behavior.
…ers (#15290, #16621)

## Record copy-and-update (#15290)
When a record copy-and-update expression like `{ m with V = "m" }` is processed,
the record type is now registered as a reference. This ensures "Find All References"
on the record type includes copy-and-update usages.

Implementation: Added CallNameResolutionSink call in CheckExpressions.fs after
BuildFieldMap determines the record type for copy-and-update expressions.

## DU case testers (#16621)
When accessing a union case tester property like `.IsB` on a union type,
a reference to the underlying union case (`B`) is now registered.
This ensures "Find All References" on a union case includes usages via its tester.

Implementation: Modified CallNameResolutionSink, CallMethodGroupNameResolutionSink,
and CallNameResolutionSinkReplacing in NameResolution.fs to detect union case testers
and register the corresponding union case reference with a slightly shifted range
to avoid duplicate filtering.

## Tests
Added 4 new tests in FindReferences.fs:
- Record copy-and-update detection (2 tests)
- DU case tester detection (2 tests)

All 49 FindReferences tests pass.
Additional constructors can now be found via Find All References.
When a constructor usage is resolved (e.g., MyClass()), we now also
register it as Item.Value(vref) in addition to Item.CtorGroup.
This allows symbol lookup from constructor definitions to find usages.

Changes:
- src/Compiler/Checking/Expressions/CheckExpressions.fs: Register
  Item.Value for F# constructor usages in ForNewConstructors
- tests: Added tests for additional constructor references
- tests: Updated symbol count baseline (79→80)
When searching for references to symbols from external DLLs (e.g., System.String),
the code previously searched ALL projects in the solution. This commit optimizes
the search by filtering to only projects that actually reference the specific DLL.

Changes:
- Added getProjectsReferencingAssembly helper function in SymbolHelpers.fs
- Updated findSymbolUses to use assembly file path for filtering when scope is None
- Added 2 new tests in ExternalDllOptimization module to verify the optimization

The optimization checks the assembly file path of external symbols and matches it
against project metadata references, significantly reducing unnecessary searches
in large solutions with many projects that don't reference the specific DLL.
For C# extension methods, ItemKeyStore was using ApparentEnclosingType
(the type being extended, e.g., Array) instead of DeclaringTyconRef
(the static class declaring the extension, e.g., Enumerable). This caused
inconsistent keys between usages of the same extension method, preventing
Find All References from finding all uses.

The fix uses IsILExtensionMethod to detect C# extension methods and writes
the DeclaringTyconRef to the key instead of ApparentEnclosingType.

Added 2 tests in CSharpExtensionMethods module:
- Find references for same overload finds all usages
- Extension method has correct symbol information
Added issue references (#NNNNN) to source code for traceability:
- #19173, #14969: Active patterns in signature files (CheckExpressions.fs)
- #18270: Property rename get/set keyword filtering (Tokenizer.fs)
- #17221: Operator handling in fixupSpan (Tokenizer.fs)
- #16394: Non-F# file filtering (Pervasive.fs)
- #16621: Union case tester references (NameResolution.fs)
- #10227: DLL optimization for Find All References (SymbolHelpers.fs)

#15399 already has documentation in test file (FindReferences.fs).

All 55 FindReferences tests pass.
- Add #15290 comment to CheckExpressions.fs for record copy-and-update fix
- Add #14057 to Tokenizer.fs comment (alongside #17221 for operator fixes)
- Update docs/TASKLIST.md to reflect 14 fixed issues

All 14 fixed issues now have searchable source code comments:
#19173, #14969, #18270, #17221, #14057, #16394, #9928, #5546,
#4136, #16993, #15290, #16621, #14902, #10227

Open issues (4):
- #15399: VS layer interface rename crash (test coverage added)
- #15721: VS layer disposable timing issue
- #5545: Not investigated (2018 SAFE bookstore bug, low impact)
- #4760: Feature request (rename in strings, deferred)
…ixed

The 2018 issue #5545 reported that DU types like Wishlist/Msg and
Database/DatabaseType were not always found by Find All References.

Investigation shows the issue has been fixed by other changes in this PR.
Added 2 tests in SAFEBookstoreSymbols module to verify:
- DU type definitions are found
- Type annotations in parameters are found
- Qualified usages (TypeName.Case) are found

All 57 FindReferences tests pass (55 existing + 2 new).
Extract the union case tester registration logic that was duplicated
3 times in CallNameResolutionSink, CallMethodGroupNameResolutionSink,
and CallNameResolutionSinkReplacing into a single private helper function.

This reduces ~62 lines of duplicate code to ~39 lines of DRY code.
Updates all Find All References and Rename code paths to use tryFixupSpan
instead of fixupSpan. The tryFixupSpan function filters out property
accessor keywords (get/set) which should not be included in rename
operations.

Changes:
- InlineRenameService.fs: Use tryFixupSpan in GetReferenceEditSpan and
  trigger span initialization
- FindUsagesService.fs: Use tryFixupSpan in rangeToDocumentSpans
- RenameParamToMatchSignature.fs: Use tryFixupSpan to filter keywords
- ClassificationService.fs: Added comment documenting intentional use
  of fixupSpan for syntax coloring
- Tokenizer.fs: Enhanced tryFixupSpan documentation with usage pattern

Usage pattern:
- tryFixupSpan: For Find All References and Rename (filters get/set)
- fixupSpan: Only for semantic classification/syntax coloring
- Add 6 helper functions: findRefsInSource, testFindRefsInSource, expectRanges, expectLines, expectLinesInclude, expectMinRefs
- Refactor 12+ tests to use new helpers
- Reduce file from 1417 to 1168 lines (17.5% reduction, 249 lines saved)
- Remove duplicate sortBy/map/Array.ofSeq assertion patterns
- All 57 FindReferences tests pass
Documents 15 fixed issues:
- #19173, #14969: Active patterns in signature files
- #17221, #14057: Operators with . and operator-to-operator rename
- #18270: Property get/set keyword rename
- #16394: cshtml file crash
- #9928: #line directive remapping
- #5546, #5545: SynPat.Or and DU types in modules
- #4136: Synthetic event handlers filtering
- #16993: C# extension methods
- #16621: DU case tester properties
- #15290: Record copy-and-update expressions
- #14902: Constructor references
- #10227: DLL optimization
Updated test baselines in ProjectAnalysisTests and EditorTests to include
the new constructor reference symbols. The #14902 fix registers F# constructor
usages as Item.Value references, which adds additional symbol entries to
GetAllUsesOfAllSymbols results.
Previously, registerUnionCaseTesterIfApplicable was called from 3 generic
sink functions (CallNameResolutionSink, CallMethodGroupNameResolutionSink,
CallNameResolutionSinkReplacing), pattern-matching Item.Property on EVERY
name resolution (variables, methods, types, everything).

Now:
- Renamed to RegisterUnionCaseTesterForProperty (public, takes PropInfo list)
- Called directly from 2 property-specific handlers in CheckExpressions.fs:
  1. TcItemThen for static properties
  2. TcLookupItemThen for instance properties
- Removed from 3 generic sink functions

Benefits:
- Zero overhead for non-property name resolutions
- Logic colocated with property-specific handling
- Clearer architectural layering (property logic in property handlers)
- No redundant pattern matching on every sink call

This addresses the architectural inefficiency identified in code review
where union case tester logic was scattered across generic sinks instead
of being targeted at property-specific code paths.
@T-Gro T-Gro marked this pull request as ready for review February 7, 2026 19:22
@T-Gro T-Gro requested a review from a team as a code owner February 7, 2026 19:22
@github-project-automation github-project-automation bot moved this from New to In Progress in F# Compiler and Tooling Feb 12, 2026
@T-Gro T-Gro enabled auto-merge (squash) February 12, 2026 17:00
@T-Gro
Copy link
Member Author

T-Gro commented Feb 12, 2026

/run fantomas

@github-actions
Copy link
Contributor

🔧 CLI Command Report

  • Command: /run fantomas
  • Outcome: success

✅ Patch applied:
- Files changed: 1
- Lines changed: 15

@T-Gro T-Gro merged commit 01bad01 into main Feb 12, 2026
46 checks passed
@github-project-automation github-project-automation bot moved this from In Progress to Done in F# Compiler and Tooling Feb 12, 2026
@TheAngryByrd
Copy link
Contributor

🥳

Comment on lines +786 to +787
let shiftedRange = Range.mkRange mObjTy.FileName (Position.mkPos mObjTy.StartLine (mObjTy.StartColumn + 1)) mObjTy.End
CallNameResolutionSink tcSink (shiftedRange, env.NameEnv, Item.Value vref, minst, ItemOccurrence.Use, env.AccessRights)
Copy link
Member

@auduchinok auduchinok Feb 23, 2026

Choose a reason for hiding this comment

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

@T-Gro Why do we need an additional symbol and the range arithmetic?

A proper symbol was already reported there and had a correct range. Was the intention here to fix some specific case?

Copy link
Member Author

Choose a reason for hiding this comment

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

This was to match secondary constructors, but it is also what caused the duplicate .ctor symbol regressions for FAR (find all references) you noticed.

#19358 attempts to solve this differently.

Comment on lines +2269 to +2271
let shiftedStart = Position.mkPos m.StartLine (m.StartColumn + 1)
let shiftedRange = Range.withStart shiftedStart m
currentSink.NotifyNameResolution(shiftedRange.End, ucItem, emptyTyparInst, occurrenceType, nenv, ad, shiftedRange, false)
Copy link
Member

@auduchinok auduchinok Feb 23, 2026

Choose a reason for hiding this comment

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

@T-Gro I believe this is a wrong approach. The symbol at this range is the tester, but its union case is reported here instead. I understand it helps to implement finding usages of related symbols, but it should not come at a cost of reporting wrong data.

Let's find another approach? You can define an additional sink, like it's done for method groups and do something like sink.NotifyRelatedSymbol and then get symbols from this sink via a separate FCS API or with a custom flag.

Copy link
Member Author

Choose a reason for hiding this comment

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

But wouldnt we rather want the results be included in existing API calls instead of coming up with a new API (where we would have to make sure to call it alongside existing calls) ?

The sink.NotifyRelatedSymbol is a good idea and semantically A LOT better then NameResolution misuse, I just want to find the right spot to integrate related symbols into existing results, to provide a transparent fix for the renaming/find all references scenarios.

Copy link
Member

Choose a reason for hiding this comment

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

But wouldnt we rather want the results be included in existing API calls instead of coming up with a new API (where we would have to make sure to call it alongside existing calls) ?

In Rider we remove all the extra symbols, and usually it was relatively easy to check because the symbols API wasn't misused on purpose like this. I think it'd be better to keep the related symbols separately (another sink? synthetic ranges?) and have a flag in FCS to allow returning/filtering these symbols.

Copy link
Member Author

Choose a reason for hiding this comment

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

Flag for existing APIs (or maybe a bitwise flagging mechanism, as there might be more dimensions of what to ask for) sounds good, let me try it 👍

// so that "Find All References" on the record type includes copy-and-update usages
if hasOrigExpr then
let item = Item.Types(tcref.DisplayName, [gtyp])
CallNameResolutionSink cenv.tcSink (mWholeExpr, env.NameEnv, item, emptyTyparInst, ItemOccurrence.Use, env.eAccessRights)
Copy link
Member

Choose a reason for hiding this comment

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

This is also not the result of name resolution. Please add a separate reporting path, as suggested before.

auduchinok added a commit to JetBrains/fsharp that referenced this pull request Feb 25, 2026
commit cd1f270
Author: Eugene Auduchinok <eugene.auduchinok@jetbrains.com>
Date:   Mon Feb 16 17:44:59 2026 +0100

    Fantomas

commit 4edb3dc
Author: Eugene Auduchinok <eugene.auduchinok@gmail.com>
Date:   Mon Feb 16 17:12:08 2026 +0100

    Release notes

commit b737ebc
Author: Eugene Auduchinok <eugene.auduchinok@jetbrains.com>
Date:   Mon Feb 16 16:54:21 2026 +0100

    FCS: capture additional types during analysis

commit e82d6f6
Author: Tomas Grosup <Tomas.Grosup@gmail.com>
Date:   Mon Feb 16 12:56:35 2026 +0100

    Create SKILL.md (dotnet#19282)

commit 01bad01
Author: Tomas Grosup <Tomas.Grosup@gmail.com>
Date:   Thu Feb 12 21:49:32 2026 +0100

    Find All References and Rename Symbol Bug Fixes (dotnet#19252)
FCS: add more predefined types checks
T-Gro added a commit that referenced this pull request Feb 25, 2026
…bols

Address auduchinok's review comments on PR #19252: related symbols (union case
testers, copy-and-update record types) are now reported via a separate
NotifyRelatedSymbolUse sink instead of abusing NotifyNameResolution.

- Add [<Flags>] RelatedSymbolUseKind enum (None/UnionCaseTester/CopyAndUpdateRecord/All)
- Add NotifyRelatedSymbolUse to ITypecheckResultsSink interface
- Refactor RegisterUnionCaseTesterForProperty to use the new sink
- Refactor copy-and-update in TcRecdExpr to use CallRelatedSymbolSink
- Add ?relatedSymbolKinds parameter to GetUsesOfSymbolInFile (default: None)
- Wire up VS FAR to pass relatedSymbolKinds=All
- Write related symbols to ItemKeyStore for background FAR
- Update semantic classification tests: tester properties now classified as
  Property (not UnionCase) since they're no longer in capturedNameResolutions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment