Skip to content

Add a refactoring between option and struct partial active patterns - #20545

Open
xperiandri wants to merge 3 commits into
dotnet:mainfrom
xperiandri:feature/active-pattern-struct-refactoring
Open

xperiandri wants to merge 3 commits into
dotnet:mainfrom
xperiandri:feature/active-pattern-struct-refactoring

Conversation

@xperiandri

@xperiandri xperiandri commented Sep 14, 2026

Copy link
Copy Markdown
Contributor
let (|Even|_|) x = if x % 2 = 0 then Some () else None
// ⟷
[<return: Struct>]
let (|Even|_|) x = if x % 2 = 0 then ValueSome () else ValueNone

Match sites don't change, so only the definition is edited. A local active pattern can't carry the attribute, so it converts to struct only on F# 9 or later. Not offered when a result isn't built from Some/None (or ValueSome/ValueNone), or when the file has a paired .fsi.

🤖 Generated with Claude Code

…patterns

A single-case partial active pattern whose results are all Some/None (or all
ValueSome/ValueNone) switches between the two: the result cases and the return
type annotation are renamed, and [<return: Struct>] is added or removed. Match
sites need no change. Local active patterns cannot carry attributes, so there
the value option return alone makes them struct, which needs F# 9.

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

@kerams

kerams commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

I appreciate all these PRs, they're bringing F# (VS) tooling closer to what C#'s had for ages, but I feel like you're just creating an ever-increasing mountain of work to review. I get anxious with every new PR notification, and it's not even my job to review them. Maybe take a break for a couple of weeks, making sure the existing PRs get over the line, and see where things stand afterwards?

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-returning and struct active patterns Add a refactoring between option and struct partial active patterns Sep 15, 2026
@xperiandri

Copy link
Copy Markdown
Contributor Author

Covered cases

Converts

  • Module-level pattern gets [<return: Struct>] on its own line above let: let (|Even|_|) x = if x % 2 = 0 then Some () else None[<return: Struct>] + let (|Even|_|) x = if x % 2 = 0 then ValueSome () else ValueNone
  • match branches: | v when v > 0 -> Some v | _ -> None| v when v > 0 -> ValueSome v | _ -> ValueNone
  • A raising branch plus try … with: if s.Length = 0 then failwith "empty" else try Some(int s) with _ -> None… try ValueSome(int s) with _ -> ValueNone
  • Return type annotation: (x: int) : unit option = … Some () else None(x: int) : unit voption = … ValueSome () else ValueNone
  • Local active pattern inside let f v = … changes only Some/NoneValueSome/ValueNone, with no attribute added
  • An existing attribute stays and the new one goes on the next line: [<CompiledName("EvenPattern")>][<CompiledName("EvenPattern")>] + [<return: Struct>]
  • In a let rec … and group, only the pattern under the caret converts, with the attribute placed after and: and (|B|_|) x = …and [<return: Struct>] (|B|_|) x = … ValueSome () else ValueNone
  • Back, from an annotation with no attribute: : unit voption = … ValueSome () else ValueNone: unit option = … Some () else None
  • Back, removing [<return: Struct>] both from the same line ([<return: Struct>] let (|Even|_|) x = …let (|Even|_|) x = …) and from a shared list ([<CompiledName("EvenPattern"); return: Struct>][<CompiledName("EvenPattern")>])
  • Round trip gives back the original text for the match, CompiledName, rec … and, module-level and local cases
  • Action titles: Use struct return for active pattern / Use option return for active pattern, exactly one action each
  • Each converted document type-checks without errors

Not offered

  • Patterns that are not single-case partial: let (|Even|Odd|) x = if x % 2 = 0 then Even else Odd, let (|A|B|_|) x = …
  • Results not all built from cases of one option kind: Some x |> Option.filter (fun v -> v > 0), : unit option = failwith "never", then Some () else ValueNone
  • Boolean-returning pattern: let (|Even|_|) x = x % 2 = 0
  • Caret on Some in the body instead of the pattern header
  • Ordinary function: let even x = if x % 2 = 0 then Some () else None
  • Return annotation that is not an option: let (|Even|_|) x : int list = …
  • Local active pattern converted to struct before F# 9 (--langversion:8.0)
  • File that has a signature file: val (|Even|_|): int -> unit option

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.

3 participants