Skip to content

Add a refactoring between ?x and [<Struct>] ?x optional parameters - #20546

Open
xperiandri wants to merge 4 commits into
dotnet:mainfrom
xperiandri:feature/optional-parameter-struct-refactoring
Open

xperiandri wants to merge 4 commits into
dotnet:mainfrom
xperiandri:feature/optional-parameter-struct-refactoring

Conversation

@xperiandri

@xperiandri xperiandri commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Offers Ctrl+. on a member's optional parameter to switch it between option and voption (F# 10). defaultArg, Option functions and match cases on it in the body, and ?x = value arguments at call sites across the solution, are rewritten to match.

Not offered when a use of the parameter would change type (passed on, Option.map), for overrides, abstract slots and constructors, or when the file has a paired .fsi.

🤖 Generated with Claude Code

?x and [<Struct>] ?x give the member body an option or a value option. The
refactoring toggles the attribute, renames what keeps its type in the body
(defaultArg, Option functions that do not return an option, match cases on the
parameter) and rewrites ?x = value arguments at the call sites in the solution.
Bodies that pass the parameter on or map it are refused.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@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

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…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 Refactoring between option and struct optional member parameters Add a refactoring between ?x and [<Struct>] ?x optional parameters Sep 15, 2026
@xperiandri

Copy link
Copy Markdown
Contributor Author

Covered cases

Converts

  • Parameter and body: member _.Greet(name: string, ?greeting: string) with defaultArg greeting "Hello"[<Struct>] ?greeting: string with defaultValueArg greeting "Hello"
  • Call sites in the same file: ?greeting = Some "Hey"?greeting = ValueSome "Hey", and ?greeting = g (with g: string option) → ?greeting = ValueOption.ofOption g; Greet("Ada") and greeting = "Hi" stay as they are
  • Converting that whole file back gives back the original exactly (ValueSome "Hey"Some "Hey", ValueOption.ofOption gg)
  • match on the parameter: | Some s -> s | None -> 1| ValueSome s -> s | ValueNone -> 1, both ways
  • Option module functions, called directly or through a pipe, in a static member: Option.isSome step && step.IsSome then step |> Option.defaultValue 0ValueOption.isSome step && step.IsSome then step |> ValueOption.defaultValue 0 (IsSome unchanged), both ways
  • Expression argument at a call site: ?step = (if true then step else None)?step = ValueOption.ofOption (if true then step else None), both ways
  • A voption passed to a struct parameter, when converting back: ?step = step (with step: int voption) → ?step = ValueOption.toOption step
  • Action titles: Use 'voption' for optional parameter / Use 'option' for optional parameter, exactly one action each
  • Each converted document type-checks without errors

Not offered

  • Parameter passed to a function the conversion does not handle: static member M(?x: int) = printfn "%A" x
  • Option module function that is not supported: Option.map string x
  • Override of an abstract slot: abstract M: ?x: int -> int with default _.M(?x) = defaultArg x 0
  • Caret on the member name (M() instead of the parameter
  • Parameter that is not optional: static member M(x: int option) = defaultArg x 0
  • Before F# 10 (--langversion:9.0): static member M(?x: int) = defaultArg x 0
  • File that has a signature file: static member M: ?x: int -> 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