Skip to content

Add a refactoring between F# and .NET-compatible optional parameters - #20547

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

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

Conversation

@xperiandri

@xperiandri xperiandri commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Offers Ctrl+. to switch between ?x: T with defaultArg x c in the body and [<Optional; DefaultParameterValue(c)>] x: T, which C# and VB callers can omit too. Converting back offers ?x with defaultArg and, from F# 10, [<Struct>] ?x with defaultValueArg. F# call sites compile against both forms, so only the member is edited.

Not offered unless every use is defaultArg x c with one constant of the parameter's type, or for overrides, abstract slots, constructors, or a file with a paired .fsi.

🤖 Generated with Claude Code

…meters

?x: T is optional for F# callers and gives the body an option;
[<Optional; DefaultParameterValue(c)>] x: T is optional for C# and VB callers
too and gives the body the value. The refactoring moves the default between
defaultArg x c in the body and the attribute, adds the InteropServices open
when it is missing, and leaves call sites alone: F# accepts both forms the
same way.

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>
xperiandri and others added 2 commits September 14, 2026 20:03
…form

[<Optional; DefaultParameterValue(c)>] x: T now converts back to either
?x: T with let x = defaultArg x c, or, from F# 10, [<Struct>] ?x: T with
let x = defaultValueArg x c.

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 ·

@xperiandri xperiandri changed the title Refactoring between F# and .NET-compatible optional parameters Add a refactoring between F# and .NET-compatible optional parameters Sep 15, 2026
@xperiandri

Copy link
Copy Markdown
Contributor Author

Covered cases

Converts

  • Shadowing default, adding the missing open: ?greeting: string with let greeting = defaultArg greeting "Hello"[<Optional; DefaultParameterValue("Hello")>] greeting: string, the let line removed, open System.Runtime.InteropServices inserted; calls Greet("Ada") and greeting = "Hi" stay as they are
  • Shadowing default round trip, both ways, when the open already exists: ?step: int + let step = defaultArg step 1[<Optional; DefaultParameterValue(1)>] step: int; the same for a second parameter ?step: float with 0.5
  • Inline default: value + defaultArg step 1[<Optional; DefaultParameterValue(1)>] step: int) = value + step
  • Back with a body on the member line: step: int) = value + step?step: int) =, then let step = defaultArg step 1 and value + step on their own lines
  • Parameter with no default use: ?flag: bool (unused) → [<Optional>] flag: bool
  • Back with no DefaultParameterValue: [<Optional>] step: int?step: int + let step = defaultArg step Unchecked.defaultof<_>
  • Back to a struct optional parameter: [<Optional; DefaultParameterValue(1)>] step: int[<Struct>] ?step: int + let step = defaultValueArg step 1; [<Optional>] step: int… defaultValueArg step Unchecked.defaultof<_>
  • Action titles: the F# form offers only Use [<Optional; DefaultParameterValue>] for optional parameter; the .NET form offers Use F# '?' optional parameter, then Use F# '[<Struct>] ?' optional parameter
  • Language version gate: before F# 10 (--langversion:9.0), the .NET form offers only Use F# '?' optional parameter
  • Each converted document type-checks without errors

Not offered

  • Different defaults: defaultArg x 0 + defaultArg x 1
  • A use other than defaultArg: static member M(?x: int) = x.IsSome
  • Default that is not a constant: ?x: obj with defaultArg x (box 1)
  • Constant whose type does not match the parameter: ?x: int with defaultArg x 1.0
  • Parameter with no type annotation: static member M(?x) = defaultArg x 0
  • Struct optional parameter: [<Struct>] ?x: int with defaultValueArg x 0
  • Extra attribute in the list: [<Optional; In>] x: int
  • Override of an abstract slot: abstract M: ?x: int -> int with default _.M(?x: int) = defaultArg x 0
  • Ordinary parameter: static member M(x: int) = x
  • 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: New

Development

Successfully merging this pull request may close these issues.

1 participant