Skip to content

Reject _ as a rename target for a binding that is used - #20599

Open
xperiandri wants to merge 8 commits into
dotnet:mainfrom
xperiandri:fix/rename-underscore-with-uses
Open

xperiandri wants to merge 8 commits into
dotnet:mainfrom
xperiandri:fix/rename-underscore-with-uses

Conversation

@xperiandri

Copy link
Copy Markdown
Contributor

Fixes #20597

Stacked on #20598

_ is not a referenceable name in F# — in expression position _.M() is the shorthand lambda — so renaming a self identifier (or any local) that its body uses produced a file that no longer compiles. Rename now accepts _ only when nothing but the declaration would be rewritten.

Rename also refuses outright when the symbol's declaration sits in the file being renamed but is missing from the locations to rewrite, rather than rewriting the uses and leaving the declaration behind.

🤖 Generated with Claude Code

xperiandri and others added 5 commits September 20, 2026 21:49
`MakeAndPublishVal` skipped the sink for any `MemberThisVal` literally named
`__`. The target was the self identifier that the auto-property desugaring
synthesizes, which sits on the *property name's* range and would therefore
shadow the property's own symbol -- a range collision, not a name problem.

Filtering by name also hid every `member __.M()` a user writes. Declarations go
through `MakeAndPublishVal` and were suppressed, while uses in the body resolve
normally and were not, so FCS reported the uses of `__` without its declaration.
Editor rename then rewrote the body and left `member __.` behind, silently
producing code that no longer compiles.

Mark the synthesized ident's range synthetic instead, as the adjacent backing
field already does. `TcResultsSinkImpl.allowedRange` drops synthetic ranges for
every sink notification, so the auto-property self identifier stays invisible
and the name check becomes dead code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`_` is not a referenceable name in F#: in expression position `_.M()` is the
shorthand lambda, not a reference. Renaming a self identifier (or any local)
that the body actually uses therefore produced a file that no longer compiles.

Accept `_` only when the rename rewrites nothing but the declaration.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FCS reports both the declaration and the uses of a healthy symbol, so a
declaration that sits in the file being renamed yet is absent from that
file's uses means the rename would rewrite the uses and leave the
declaration behind -- the failure mode `member __.M()` just exhibited.

Refuse to start the rename instead of corrupting the file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nothing covered InlineRenameService before.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

✅ Release notes checked


✅ Found changes and release notes in following paths:

Warning

No PR link found in some release notes, please consider adding it.

Change path Release notes path Description
`src/Compiler` docs/release-notes/.FSharp.Compiler.Service/11.0.100.md No current pull request URL (#20599) found, please consider adding it
`vsintegration/src` docs/release-notes/.VisualStudio/18.vNext.md

@github-actions github-actions Bot added the ⚠️ Affects-Design-Time Tooling check: PR touches type providers or dependency manager label Sep 20, 2026
@github-actions

This comment has been minimized.

xperiandri and others added 2 commits September 21, 2026 00:26
FSharp.Editor consumes Microsoft.CodeAnalysis.EditorFeatures with
ExcludeAssets="runtime" because Visual Studio supplies it at run time.
Nothing put it in the test output, so touching a rename type threw
FileNotFoundException before any assertion ran.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`__.TestMethod()` also matched the declaration one line above, whose own
self identifier has no uses, so the test placed the caret there and found
one location instead of two.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Tooling Safety Check — Affects-Build-Infra, Affects-Design-Time, Affects-Restore
Affects-Build-Infra: Test project adds a package reference.
Affects-Design-Time: Changes Visual Studio rename and symbol-reporting behavior.
Affects-Restore: New package reference changes dependency resolution.

Generated by PR Tooling Safety Check · gpt56 462.4K ·

@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 20, 2026

@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 symbolUse.Symbol.DeclarationLocation with
| Some declRange when String.Equals(declRange.FileName, symbolUse.Range.FileName, StringComparison.Ordinal) ->
checkFileResults.GetUsesOfSymbolInFile(symbolUse.Symbol, cancellationToken = ct)
|> Array.exists (fun su -> Range.equals su.Range declRange)

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.

🤖 🕵️ Rename refuses active-pattern cases in their declaring file — renaming Even to DivisibleByTwo worked before, but the whole-pattern declaration range never matches an individual case range.

module M
let (|Even|Odd|) n =
    if n % 2 = 0 then Even else Odd
let f n =
    match n with
    | Even -> true
    | Odd -> false

// `_` names a binding nothing refers to: in expression position `_.M()` is the shorthand lambda
let replacementTextValid =
Tokenizer.isValidNameForSymbol (symbolKind, symbol, replacementText)
&& not (replacementText = "_" && locations.Length > 1)

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.

🤖 🕵️ Rename rejects an explicitly backticked underscore after normalizing it to bare _ — the escaped identifier remains referenceable, and this replacement compiled and was accepted before.

// Rename value to ``_``:
module M
let value = 1
let answer = value + 1

// Valid replacement, now marked invalid:
// let ``_`` = 1
// let answer = ``_`` + 1

@T-Gro T-Gro added the AI-reviewed PR reviewed by AI review council label Sep 21, 2026
@T-Gro
T-Gro self-requested a review September 21, 2026 09:31
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.

Rename accepts _ as a new name for a value that is used

2 participants