Skip to content

Add Extract to function and Extract to private member refactorings - #20538

Open
xperiandri wants to merge 9 commits into
dotnet:mainfrom
xperiandri:feature/extract-function-refactoring
Open

xperiandri wants to merge 9 commits into
dotnet:mainfrom
xperiandri:feature/extract-function-refactoring

Conversation

@xperiandri

@xperiandri xperiandri commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Stacked on #20537.

Offers Ctrl+. on a selected expression to move it into a local function, a module function, or, when it uses this or base, a private member. Values the selection reads from the enclosing function become parameters in first-use order.

Parameters are annotated by default because the new function is its own generalization point; the new Parameter types in Extract to function option under Code Fixes can relax that. Not offered when the selection assigns to a captured mutable or reads a byref value.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Sep 14, 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 and others added 8 commits September 14, 2026 16:36
…g or a literal

The selected expression is bound to a new name in front of the statement that uses it: above a statement that starts its line, or, when the body of a binding, match clause, branch or lambda shares the line with its keyword, on new lines under that keyword. A constant inside a module-level declaration can instead become a [<Literal>] in front of that declaration. The shared selection and placement logic lives in Refactor/RefactoringHelpers.fs, and the refactoring test framework gains helpers that run a provider on a selected span.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…tion, a module function or a private member

The values the selection reads from the enclosing member or module-level declaration become parameters in the order they are first used; module values, members and 'this' stay where they are. A member that uses 'this' or 'base' can extract into a private member called on its self identifier. Parameter type annotations follow a new Code Fixes option: always, only for receivers of member and indexer accesses and operator operands, or never. A selection that assigns to a captured mutable local, or captures a byref-like value, is not offered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
C# offers Introduce Constant without a selection. With a caret alone only the
literal is offered; a let binding still needs a selection.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A caret between 'fun' and the end of '->' acts as a selection of the
parenthesized lambda; in the body a caret offers nothing new.

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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the feature/extract-function-refactoring branch from 9689259 to dc118df Compare September 14, 2026 14:36
…e of its section

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@xperiandri
xperiandri marked this pull request as ready for review September 14, 2026 19:40
@xperiandri
xperiandri requested a review from a team as a code owner September 14, 2026 19:40
@github-actions github-actions Bot added the ⚠️ Affects-Design-Time Tooling check: PR touches type providers or dependency manager label Sep 14, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🔍 Tooling Safety Check — Affects-Design-Time
Affects-Design-Time: Modifies Visual Studio code executed while projects are open.

Generated by PR Tooling Safety Check · gpt56 1.8M ·

@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.

🤖🕵️ Please shorten the description using this guidance. Focus on the problem and why the change is needed, in simplified technical English. Leave the implementation inventory to the Files tab and retain necessary caveats.

@github-project-automation github-project-automation Bot moved this from New to In Progress in F# Compiler and Tooling Sep 15, 2026
@xperiandri xperiandri changed the title Refactorings to extract a selected expression into a local function, a module function or a private member Add Extract to function and Extract to private member refactorings Sep 15, 2026
@xperiandri

Copy link
Copy Markdown
Contributor Author

Covered cases

Offered

  • Values the selection uses from the enclosing declaration become parameters of a local function: printfn "%d" (w * h + 1)let extractedFunction (w: int) (h: int) = w * h + 1 + printfn "%d" (extractedFunction w h)
  • The module function is inserted before the declaration that uses it: let private extractedFunction (w: int) (h: int) = w * h + 1 above let area …
  • Selecting (w * h + 1) with its parentheses gives the same result, and the call stays parenthesized
  • A selection that uses no outside values becomes a function of unit: 1 + 2let extractedFunction () = 1 + 2 + extractedFunction ()
  • A selection that uses this becomes a private member after the enclosing member: l * this.Rate in List.sumBy (fun l -> l * this.Rate)this.ExtractedMethod(l) + member private this.ExtractedMethod(l: int) = l * this.Rate
  • The parameter-annotation option: Alwayslet extractedFunction (s: string) (n: int) =, WhenNeeded(s: string) n (only the receiver of s.ToUpper() is annotated), Nevers n
  • Which actions appear: a plain selection (w * h + 1) offers "Extract to local function" and "Extract to module function"; a selection using base (base.ToString() + "!") offers only "Extract to private member"
  • A caret in the header of a parenthesized lambda (x -> in (fun x -> x + n)) offers the same actions, with the same results, as selecting the whole (fun x -> x + n)

Not offered

  • With no selection, a caret in the lambda body: x + n in (fun x -> x + n)
  • The selection assigns to a captured mutable local: for i in 1 .. 3 do n <- n + i
  • The selection reads a byref parameter: x + 1 with x: byref<int>

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

Labels

⚠️ Affects-Design-Time Tooling check: PR touches type providers or dependency manager

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

2 participants