Skip to content

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

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

xperiandri wants to merge 9 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

@github-actions

github-actions Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

✅ Release notes checked


✅ Found changes and release notes in following paths:

Change path Release notes path Description
`vsintegration/src` docs/release-notes/.VisualStudio/18.vNext.md

@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

This comment has been minimized.

@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

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

let defaultText =
match defaultValue with
| ValueSome value -> sourceText.ToString(spanOf sourceText value.Range)
| ValueNone -> "Unchecked.defaultof<_>"

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.

🤖 🕵️ [P2] Converting x to F# ?x changes C.M() from true to false — an omitted obj receives null instead of Missing.Value.

open System.Runtime.InteropServices

type C() =
    static member M([<Optional>] x: obj) =
        obj.ReferenceEquals(x, System.Reflection.Missing.Value)

printfn "%b" (C.M())

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6a6a694: for an F# member, [<Optional>] without DefaultParameterValue passes Missing.Value only when the parameter type is obj (infos.fs, isObjTyAnyNullness). Every other type gets DefaultValue, which matches Unchecked.defaultof. So the conversion is no longer offered when the parameter's type, with abbreviations stripped, is System.Object. Test: the [<Optional>] x: obj case of No action.

| Some(node,
SyntaxNode.SynExpr(SynExpr.App(isInfix = false; funcExpr = SingleIdent func; argExpr = arg) as inner) :: SyntaxNode.SynExpr(SynExpr.App(
isInfix = false; funcExpr = funcExpr; argExpr = (SynExpr.Const _ as defaultValue)) as application) :: rest) when
isSame arg node && hasText "defaultArg" func && isSame funcExpr inner

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.

🤖 🕵️ [P2] Converting ?x to .NET optional changes C.M() from 99 to 0 — matching defaultArg by spelling removes a call to a different function.

let defaultArg (_: int option) (_: int) = 99

type C() =
    static member M(?x: int) = defaultArg x 0

printfn "%d" (C.M())

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6a6a694: defaultArg must now resolve by symbol to Microsoft.FSharp.Core.Operators.defaultArg. The check goes through an isFSharpCoreSymbol helper that is also in #20545/#20546, and it will move into a shared helper module later. Test: the user-defined defaultArg case of No action.

match found, attributeText with
| ValueSome found, ValueSome attributeText ->
[
TextChange(TextSpan((spanOf sourceText optionalValRange).Start, 1), attributeText)

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.

🤖 🕵️ [P2] Converting only ?y to .NET optional produces FS1212 — the remaining F# optional ?x precedes the converted parameter.

type C() =
    static member M(?x: int, ?y: int) =
        defaultArg x 1 + defaultArg y 2

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6a6a694: the .NET form is no longer offered when a ? parameter comes before the target in the same member. Test: the M(?x: int, ?y: int) case of No action.


let struct (prefix, defaultFunction) =
if asStruct then
struct ("[<Struct>] ?", "defaultValueArg")

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.

🤖 🕵️ [P2] Converting x to [<Struct>] ?x leaves a formerly valid caller failing with FS0001 — Some 1 supplies option, but the parameter now requires voption.

open System.Runtime.InteropServices

type C =
    static member M([<Optional; DefaultParameterValue(0)>] x: int) = x

let result = C.M(?x = Some 1)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6a6a694 by rewriting the callers, as #20546 does. Deciding not to offer it would need the same solution-wide reference search at lightbulb time; rewriting does that search only when the action runs. The struct action now changes the solution. ?x = Some v/None become ValueSome v/ValueNone when they are the FSharp.Core cases, and any other value is wrapped in ValueOption.ofOption. Test: Converting to a struct optional parameter converts optional arguments to value options.

/// The whole line of `let x = defaultArg x c` when that line does nothing but rebind the parameter.
let private tryShadowingLine (sourceText: SourceText) (name: string) (application: SynExpr) (path: SyntaxVisitorPath) =
match path with
| SyntaxNode.SynBinding(SynBinding(headPat = SynPat.Named(ident = SynIdent(ident, _)); expr = rhs; trivia = trivia)) :: SyntaxNode.SynExpr(SynExpr.LetOrUse letOrUse) :: _ when

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.

🤖 🕵️ [P2] Converting ?x to .NET optional deletes let mutable x, leaving x <- x + 1 failing with FS0027 because the parameter is immutable.

type C() =
    static member M(?x: int) =
        let mutable x = defaultArg x 0
        x <- x + 1
        x

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6a6a694: a let mutable shadow is no longer removed. It becomes let mutable x = x. Test: Mutable shadowing keeps its binding.

found
|| match node with
| SyntaxNode.SynModule(SynModuleDecl.Open(target = SynOpenDeclTarget.ModuleOrNamespace(longId = SynLongIdent(id = ids)))) ->
(ids |> List.map _.idText) = interopServices

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.

🤖 🕵️ [P2] Converting ?x to .NET optional produces FS0039 for both attributes — an open in another module suppresses the import needed at C.M.

module Other =
    open System.Runtime.InteropServices
    let x = 1

type C() =
    static member M(?x: int) = defaultArg x 0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6a6a694: an open System.Runtime.InteropServices counts only when it ends before the member, inside a module or namespace that encloses the member. Otherwise the open is inserted. Tests: Open in another module does not count as in scope, Open in an enclosing module counts as in scope.

@T-Gro T-Gro added the AI-reviewed PR reviewed by AI review council label Sep 18, 2026
@T-Gro
T-Gro self-requested a review September 18, 2026 15:07
@T-Gro T-Gro added the vsintegration-only Changes only Visual Studio integration, plus optional docs, release notes, or tests label Sep 21, 2026
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

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

🤖 🕵️ Re-reviewed all recent changes as of 891d0c5; LGTM

@xperiandri
xperiandri force-pushed the feature/optional-parameter-default-value-refactoring branch from 891d0c5 to 22767fd Compare October 8, 2026 01:11

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

🤖 🕵️ Re-reviewed all recent changes as of 22767fd; LGTM

@xperiandri
xperiandri force-pushed the feature/optional-parameter-default-value-refactoring branch from 22767fd to 90a557e Compare October 10, 2026 22:28

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

The diff is still way too big - apply the NoBloat instructions and CodeCompaction skill.

Especially the massive code comments would benefit not just with drastic reduction, but ideally with well named functions/arguments/bindings/types making the intention clear.

If a well named piece of code just tells what the comment says - delete the comment.

  • This diff adds 32 new code comment lines and 2999 new code comment chars.
  • Comments bypass the type system and become stale maintenance state. Keep only durable public contracts; express implementation intent through names, functions, active patterns, discriminated unions, and types.

@github-project-automation github-project-automation Bot moved this from New to In Progress in F# Compiler and Tooling Oct 13, 2026
@xperiandri
xperiandri force-pushed the feature/optional-parameter-default-value-refactoring branch from 20e796b to 8e2d352 Compare October 15, 2026 00:47
@xperiandri

Copy link
Copy Markdown
Contributor Author

Commits ddb5850 (source) and d52a564 (tests) apply the compaction: added comment lines 32 -> 1 and comment characters 2999 -> 123, the one left being the license header, with the explanations now carried by names such as FSharpOptional and DotNetOptional, fsharpCoreNameAt, tryAnnotatedParameter, hasOnlyOptionalAttributes, passesMissingValueWhenOmitted and assertCompilesAsProject. ComputeRefactoringsAsync went from nine nested levels of control flow to three (actionsFor and changedSolution are their own functions), tryForm and tryParameter from four levels to two, and the repeated symbol lookup and the two document actions became tryGetSymbolUse and documentAction. The tests went from 17 methods to 12 and from 976 to 658 lines with the same 47 cases (every input and expected result is kept, the module M header and the System.Runtime.InteropServices open come from two helpers, and each theory row has a name), so the PR now adds 1709 lines instead of 2041. Left as is: tryParameter still folds the whole tree, since ParsedInput.tryPick stops at the caret with different edge rules and would change behaviour.

@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 found, attributes with
| ValueSome found, ValueSome attributes when not (hasOptionalValBefore optionalValRange.Start headPat) ->
let qualifier =
if hasInteropServicesOpenAt parameter.MemberName.idRange.Start parseTree then

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.

🤖🕵️ Local attribute capture changes an omitted argument from 7 to 0.

open System.Runtime.InteropServices
type DefaultParameterValueAttribute(value: int) = inherit System.Attribute()
type C =
    static member M(?x: int) = defaultArg x 7
let result = C.M()

match trivia.EqualsRange with
| Some equals when body.Range.StartLine > equals.EndLine ->
let line = sourceText.Lines[Line.toZ body.Range.StartLine]
ValueSome(TextChange(TextSpan(line.Start, 0), $"{String(' ', body.Range.StartColumn)}{declaration}{lineBreak}"))

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.

🤖🕵️ Generated binding lands inside a block comment; the expression fails with FS0001/FS0043.

static member M([<Optional; DefaultParameterValue(7)>] x: int) =
    (* first
       second *) x + 1

xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 19, 2026
…form

From dotnet#20547 (release note left out: 18.vNext.md has local
edits).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 19, 2026
… commenting them

Port of the dotnet#20547 compaction (feature/optional-parameter-default-value-refactoring
8e2d352..d52a564) to all-fixes:

- Name the optional parameter default value conversion steps instead of commenting them
- Parameterize the optional parameter default value conversion tests

The refactoring's member lookup is the one call site of getSymbolUses; it keeps
the RelatedSymbolUseKind.AllInCode argument that all-fixes needs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 19, 2026
…form

From dotnet#20547 (release note left out: 18.vNext.md has local
edits).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 19, 2026
… commenting them

Port of the dotnet#20547 compaction (feature/optional-parameter-default-value-refactoring
8e2d352..d52a564) to all-fixes:

- Name the optional parameter default value conversion steps instead of commenting them
- Parameterize the optional parameter default value conversion tests

The refactoring's member lookup is the one call site of getSymbolUses; it keeps
the RelatedSymbolUseKind.AllInCode argument that all-fixes needs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 23, 2026
…form

From dotnet#20547 (release note left out: 18.vNext.md has local
edits).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 23, 2026
… commenting them

Port of the dotnet#20547 compaction (feature/optional-parameter-default-value-refactoring
8e2d352..d52a564) to all-fixes:

- Name the optional parameter default value conversion steps instead of commenting them
- Parameterize the optional parameter default value conversion tests

The refactoring's member lookup is the one call site of getSymbolUses; it keeps
the RelatedSymbolUseKind.AllInCode argument that all-fixes needs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 23, 2026
…form

From dotnet#20547 (release note left out: 18.vNext.md has local
edits).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 23, 2026
… commenting them

Port of the dotnet#20547 compaction (feature/optional-parameter-default-value-refactoring
8e2d352..d52a564) to all-fixes:

- Name the optional parameter default value conversion steps instead of commenting them
- Parameterize the optional parameter default value conversion tests

The refactoring's member lookup is the one call site of getSymbolUses; it keeps
the RelatedSymbolUseKind.AllInCode argument that all-fixes needs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 29, 2026
…form

From dotnet#20547 (release note left out: 18.vNext.md has local
edits).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 29, 2026
… commenting them

Port of the dotnet#20547 compaction (feature/optional-parameter-default-value-refactoring
8e2d352..d52a564) to all-fixes:

- Name the optional parameter default value conversion steps instead of commenting them
- Parameterize the optional parameter default value conversion tests

The refactoring's member lookup is the one call site of getSymbolUses; it keeps
the RelatedSymbolUseKind.AllInCode argument that all-fixes needs.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
xperiandri and others added 9 commits October 30, 2026 00:54
…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>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…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>
Resolve defaultArg by symbol, not spelling. Do not offer the F# form for
an [<Optional>] obj without a default, whose omitted value is Missing.Value,
nor the .NET form after an earlier ? parameter (FS1212). Keep a mutable
shadowing binding, look for the InteropServices open only in modules that
enclose the member, and rewrite ?x = option arguments when converting to a
struct optional parameter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… names

Converting `[<Optional; DefaultParameterValue(c)>] x: T` to `?x: T`:

- Read the attributes from the parameter's symbol: they must be the
  `OptionalAttribute` and at most the `DefaultParameterValueAttribute` of
  `System.Runtime.InteropServices`, and the parameter may have no other
  attribute, in whichever attribute list it is written.
- Require the default constant to be of the parameter's type, as the
  compiler does before it passes one, and take that type from the symbol
  in both directions.
- Do not offer it for a byref or byref-like parameter type, nor when a
  parameter that is not a `?` one follows (FS1212).
- Write `defaultArg` and `defaultValueArg` with their full names where the
  short name resolves to something else, and recognise a qualified
  `defaultArg` on the way back.
- Keep a comment between `=` and the body, and the backticks of an escaped
  parameter name.

Converting to `[<Struct>] ?x: T`:

- Offer it only when every F# project that depends on the member's project
  takes value options for optional parameters.
- Replace the whole `Some` or `None` identifier at call sites, with the
  full name of the value option case where the short one is shadowed.

Converting `?x: T` to the .NET form:

- Write the attributes with their namespace instead of inserting
  `open System.Runtime.InteropServices`, unless it is open at the member.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… commenting them

The doc comments of ConvertOptionalParameterDefaultValue.fs move into names:
the Form cases FSharpOptional and DotNetOptional, fsharpCoreNameAt,
IdentsAndLast, tryAnnotatedParameter, hasOnlyOptionalAttributes,
tryShadowingLineSpan, whiteSpaceBeforeBody, passesMissingValueWhenOmitted,
isUseInMemberBody and projectAndDependentsSupportStructOptionals. Only the
license header stays.

ComputeRefactoringsAsync nested nine levels of control flow. The actions are
now built by actionsFor and the solution by changedSolution, so the provider
nests three. tryForm and tryParameter go from four levels to two through
tryDotNetOptional and searchResultAt.

Repeated pieces are merged: tryGetSymbolUse serves the provider and
isFSharpCoreSymbol, documentAction builds the two document actions,
Range.rangeContainsPos replaces hand-written position comparisons and
SingleIdent replaces isOperator.

tryGetSymbolUse, isInFSharpCore and isFSharpCoreSymbol are the same text as in
ConvertOptionalParameterStruct.fs of the optional parameter struct
refactoring (PR dotnet#20546); the two pull requests keep their own copies.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The module header and the System.Runtime.InteropServices open come from two
helpers instead of being written into every test string. The one-off
conversion facts and the two struct conversion theories become rows of three
named theories, every row of the theories gets a name so a failure says which
scenario it was, and the comment about re-checking a synthetic project is the
name of a helper, assertCompilesAsProject.

Every input and every expected result is kept: 47 cases before and after.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Replaying the branch onto main left some files short of the branch's own content; this restores it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@xperiandri
xperiandri force-pushed the feature/optional-parameter-default-value-refactoring branch from d52a564 to eedf260 Compare October 30, 2026 01:02

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

🤖 🕵️ Re-reviewed all recent changes as of eedf260; LGTM

This branch has not been deployed

No deployments
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 AI-reviewed PR reviewed by AI review council vsintegration-only Changes only Visual Studio integration, plus optional docs, release notes, or tests

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

4 participants