Repository navigation
Add a refactoring between reference and struct tuples - #20548
xperiandri wants to merge 10 commits into
Conversation
✅ Release notes checked
|
fb6c03d to
512e8cb
Compare
This comment has been minimized.
This comment has been minimized.
Covered casesConverts
Not offered
|
T-Gro
left a comment
There was a problem hiding this comment.
🤖 🕵️ AI review — verify independently.
| /// Whether the tuple is the argument list of a method, constructor or union case call rather than a tuple value. | ||
| let isArgumentList (tuple: SynExpr) (path: SyntaxVisitorPath) = | ||
| match path with | ||
| | SyntaxNode.SynExpr(SynExpr.Paren(expr = inner) as paren) :: SyntaxNode.SynExpr(SynExpr.App(flag = ExprAtomicFlag.Atomic; argExpr = arg) | SynExpr.New( |
There was a problem hiding this comment.
🤖 🕵️ [P2] Converting 1, 2 passes one struct-tuple argument to Math.Max — valid method calls with whitespace before ( stop compiling.
module M
let biggest = System.Math.Max (1, 2)
// Convert "1, 2" to a struct tuple.
// Result: System.Math.Max struct (1, 2)There was a problem hiding this comment.
Fixed in 4cfee1f: a parenthesized tuple is an argument list when the applied name resolves to a method or constructor with one argument list (or a union case of several fields), whatever the spacing. Test: No action with System.Math.Max (1, 2) and Calc().Add (1, 2).
| this.Retarget source result (SyntaxNode.SynMatchClause clause :: childPath) | ||
| | SynExpr.Sequential(expr2 = last) -> this.Retarget source last childPath | ||
| | SynExpr.LetOrUse letOrUse -> this.Retarget source letOrUse.Body childPath | ||
| | _ -> () |
There was a problem hiding this comment.
🤖 🕵️ [P2] Converting (x, y) leaves make returning a reference tuple through try/finally — the converted pattern fails to typecheck.
module M
let make () =
try (1, 2)
finally ()
let (x, y) = make ()
// Convert "x, y" to a struct tuple.There was a problem hiding this comment.
Fixed in 4cfee1f: results now flow into and out of the try body of try/finally, and the body and handlers of try/with. Test: Result flowing out of try converts every branch of it.
|
|
||
| if isWholeAnnotation || isGenericArgument sourceText keyword.Start span.End then | ||
| [ | ||
| TextChange(TextSpan(keyword.Start, keyword.Length + 1), "") |
There was a problem hiding this comment.
🤖 🕵️ [P2] Converting this type to a reference tuple deletes the comment's opening ( — the result is invalid syntax.
module M
type R = { Pair: struct (* note *) (int * int) }
// Convert "int * int" to a reference tuple.
// Result: type R = { Pair: * note *) (int * int }There was a problem hiding this comment.
Fixed in 4cfee1f: only struct and the blanks after it are removed, and the tuple's ( is found past any comments. Test: Comment between struct and its parenthesis is kept.
| /// `struct ` to insert in front of the `(` at the position, after a space when it would run into a name: `f(a, b)`. | ||
| let private structAt (sourceText: SourceText) (position: int) = | ||
| match if position > 0 then sourceText[position - 1] else ' ' with | ||
| | c when Char.IsLetterOrDigit c || c = '_' || c = '\'' || c = '`' || c = ')' || c = ']' -> " struct " |
There was a problem hiding this comment.
🤖 🕵️ [P2] Converting (a,b) appends struct to the function identifier ending in U+0301 — its callers fail with an undefined-name error.
module M
let f́(a,b) = a+b
let x = f́ (1,2)
// Convert "a,b" to a struct tuple.
// Result: let f́struct (a,b) = a+bThere was a problem hiding this comment.
Fixed in 4cfee1f: the check for a preceding name now uses the lexer's identifier character categories (letters, Nl, Nd, Pc, Mn, Mc, Cf, '). Test: Struct keyword is separated from a name ending in a combining mark.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
…ents with their calls From dotnet#20548 and dotnet#20549. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…et#20549) up to their pull request heads Their calls to SymbolHelpers.getSymbolUses ask for RelatedSymbolUseKind.AllInCode, as the XML doc rename work on this branch requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…annotated patterns The conversion now also follows a tuple through - a function in parentheses, `(sum) (1, 2)`; - a name bound to a lambda, its parameters and its result; - a function alias and a partial application, `let alias = make`; - a named argument, `M(p = pair)`; - an annotated tuple pattern, `((a, b): int * int)`, from the pattern, the annotation or the value; - a value whose binding head is in parentheses, `let (pair: int * int) = (1, 2)`. It is no longer offered where the result cannot compile or nothing would change: - on the annotation of a member's only argument, which is its parameter list; - on the argument of a union case whose only field is declared a tuple; - on the argument of a method in parentheses, `(c.Add) (1, 2)`; - on a tuple over several lines without parentheses, which cannot change in place. `struct` is kept apart from type arguments the parenthesis follows: `f<int> struct (1, 2)`. This is b7bc303 of dotnet#20548 as dotnet#20549 merged it (3abaa70) into the engine both kinds share: `StructConversion.fs` and `StructPropagation.fs` take what went into `TupleConversion.fs` and `TuplePropagation.fs` there. A member's parameter list is told by the kind, so the annotation of a member's only argument stays convertible for an anonymous record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port of the dotnet#20549 compaction (feature/anonymous-record-struct-refactoring 4c7066a..5be006c) to all-fixes. The range includes the pull request's merge of the compacted tuple refactoring (dotnet#20548), so this one commit covers both pull requests: all-fixes has the generalized engine of dotnet#20549 (StructPropagation.fs, TupleConversion.fs as a kind of that engine) and no TuplePropagation.fs, and the merge kept that layout. - dotnet#20548: Compact the tuple refactoring sources - dotnet#20548: Share the cross-file refactoring test setup - dotnet#20549: Compact the shared struct conversion sources - dotnet#20549: Share the cross-file test setup of the conversion tests StructPropagation.fs reads the related uses once now (ForEachUse) and keeps the RelatedSymbolUseKind.AllInCode argument that all-fixes needs there. RefactorTestFramework.fs merges the pull request's helpers with those of the Extract suites that the earlier commits of this series put there. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Ctrl+. on a tuple expression, pattern or annotated tuple type converts it to the other kind and follows the value through the solution. From dotnet#20548 (release note left out: 18.vNext.md has local edits). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
From dotnet#20548: Find All References did not see the second document added to a single-file test solution. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
From dotnet#20548. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ents with their calls From dotnet#20548 and dotnet#20549. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…et#20549) up to their pull request heads Their calls to SymbolHelpers.getSymbolUses ask for RelatedSymbolUseKind.AllInCode, as the XML doc rename work on this branch requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…annotated patterns The conversion now also follows a tuple through - a function in parentheses, `(sum) (1, 2)`; - a name bound to a lambda, its parameters and its result; - a function alias and a partial application, `let alias = make`; - a named argument, `M(p = pair)`; - an annotated tuple pattern, `((a, b): int * int)`, from the pattern, the annotation or the value; - a value whose binding head is in parentheses, `let (pair: int * int) = (1, 2)`. It is no longer offered where the result cannot compile or nothing would change: - on the annotation of a member's only argument, which is its parameter list; - on the argument of a union case whose only field is declared a tuple; - on the argument of a method in parentheses, `(c.Add) (1, 2)`; - on a tuple over several lines without parentheses, which cannot change in place. `struct` is kept apart from type arguments the parenthesis follows: `f<int> struct (1, 2)`. This is b7bc303 of dotnet#20548 as dotnet#20549 merged it (3abaa70) into the engine both kinds share: `StructConversion.fs` and `StructPropagation.fs` take what went into `TupleConversion.fs` and `TuplePropagation.fs` there. A member's parameter list is told by the kind, so the annotation of a member's only argument stays convertible for an anonymous record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port of the dotnet#20549 compaction (feature/anonymous-record-struct-refactoring 4c7066a..5be006c) to all-fixes. The range includes the pull request's merge of the compacted tuple refactoring (dotnet#20548), so this one commit covers both pull requests: all-fixes has the generalized engine of dotnet#20549 (StructPropagation.fs, TupleConversion.fs as a kind of that engine) and no TuplePropagation.fs, and the merge kept that layout. - dotnet#20548: Compact the tuple refactoring sources - dotnet#20548: Share the cross-file refactoring test setup - dotnet#20549: Compact the shared struct conversion sources - dotnet#20549: Share the cross-file test setup of the conversion tests StructPropagation.fs reads the related uses once now (ForEachUse) and keeps the RelatedSymbolUseKind.AllInCode argument that all-fixes needs there. RefactorTestFramework.fs merges the pull request's helpers with those of the Extract suites that the earlier commits of this series put there. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Ctrl+. on a tuple expression, pattern or annotated tuple type converts it to the other kind and follows the value through the solution. From dotnet#20548 (release note left out: 18.vNext.md has local edits). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
From dotnet#20548: Find All References did not see the second document added to a single-file test solution. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
From dotnet#20548. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ents with their calls From dotnet#20548 and dotnet#20549. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…et#20549) up to their pull request heads Their calls to SymbolHelpers.getSymbolUses ask for RelatedSymbolUseKind.AllInCode, as the XML doc rename work on this branch requires. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…annotated patterns The conversion now also follows a tuple through - a function in parentheses, `(sum) (1, 2)`; - a name bound to a lambda, its parameters and its result; - a function alias and a partial application, `let alias = make`; - a named argument, `M(p = pair)`; - an annotated tuple pattern, `((a, b): int * int)`, from the pattern, the annotation or the value; - a value whose binding head is in parentheses, `let (pair: int * int) = (1, 2)`. It is no longer offered where the result cannot compile or nothing would change: - on the annotation of a member's only argument, which is its parameter list; - on the argument of a union case whose only field is declared a tuple; - on the argument of a method in parentheses, `(c.Add) (1, 2)`; - on a tuple over several lines without parentheses, which cannot change in place. `struct` is kept apart from type arguments the parenthesis follows: `f<int> struct (1, 2)`. This is b7bc303 of dotnet#20548 as dotnet#20549 merged it (3abaa70) into the engine both kinds share: `StructConversion.fs` and `StructPropagation.fs` take what went into `TupleConversion.fs` and `TuplePropagation.fs` there. A member's parameter list is told by the kind, so the annotation of a member's only argument stays convertible for an anonymous record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port of the dotnet#20549 compaction (feature/anonymous-record-struct-refactoring 4c7066a..5be006c) to all-fixes. The range includes the pull request's merge of the compacted tuple refactoring (dotnet#20548), so this one commit covers both pull requests: all-fixes has the generalized engine of dotnet#20549 (StructPropagation.fs, TupleConversion.fs as a kind of that engine) and no TuplePropagation.fs, and the merge kept that layout. - dotnet#20548: Compact the tuple refactoring sources - dotnet#20548: Share the cross-file refactoring test setup - dotnet#20549: Compact the shared struct conversion sources - dotnet#20549: Share the cross-file test setup of the conversion tests StructPropagation.fs reads the related uses once now (ForEachUse) and keeps the RelatedSymbolUseKind.AllInCode argument that all-fixes needs there. RefactorTestFramework.fs merges the pull request's helpers with those of the Extract suites that the earlier commits of this series put there. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Ctrl+. on a tuple expression, pattern or annotated tuple type converts it to the other kind and follows the value through the solution: annotations of values, parameters, record fields and function results it flows through, tuple patterns taking it apart, and the arguments and values flowing into it. Uses that cannot be followed (fst, snd, generic collections) are left for the compiler to report. CreateWithCodeAndDependency now tells FCS about both files, so the second file can be type-checked. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adding the second document to a single-file solution left Find All References unable to see either file, so a chain through a function's call sites stopped at the first file. The synthetic project gives both files to the checker the way AddReturnTypeTests and FindReferencesTests set up theirs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The synthetic project's checker reads the other file from disk, so checking the refactored document saw the old definition; a struct tuple pattern happens to accept a reference tuple, which hid that. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ents with their calls The only argument of a member or constructor is its parameter list, not a tuple, so it is no longer offered. A tuple that is a whole curried argument of a function or member now converts the matching argument at every call, and `struct` no longer runs into a name the parenthesis follows (`f(a, b)`). 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>
…y and match aliases A parenthesized tuple applied to a method, a constructor or a union case of several fields is an argument list however the call is spaced. Results flow through try/finally and try/with, a match clause that names the value passes it on, and removing `struct` from a type keeps a comment before its parenthesis. `struct` is kept apart from a name ending in any identifier character the lexer accepts, combining marks included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…annotated patterns The conversion now also follows a tuple through - a function in parentheses, `(sum) (1, 2)`; - a name bound to a lambda, its parameters and its result; - a function alias and a partial application, `let alias = make`; - a named argument, `M(p = pair)`; - an annotated tuple pattern, `((a, b): int * int)`, from the pattern, the annotation or the value; - a value whose binding head is in parentheses, `let (pair: int * int) = (1, 2)`. It is no longer offered where the result cannot compile or nothing would change: - on the annotation of a member's only argument, which is its parameter list; - on the argument of a union case whose only field is declared a tuple; - on the argument of a method in parentheses, `(c.Add) (1, 2)`; - on a tuple over several lines without parentheses, which cannot change in place. `struct` is kept apart from type arguments the parenthesis follows: `f<int> struct (1, 2)`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Delete the comments the code says by itself and name what they described, share the edit code of tuple expressions and patterns, share the use scan of the propagation engine and keep every function at three nested levels or fewer. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Move the test helpers into RefactorTestFramework, let them add the module M wrapper and run the two-file refactoring with its re-check as a new project, and assert both directions of a conversion in one helper. 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>
e32822e to
c1cc431
Compare
Offers
Ctrl+.on a tuple expression, pattern or annotated tuple type to switch it between a reference and a struct tuple. The value is followed through annotations, patterns, record fields, function results and call arguments across the solution, so the result still type-checks. What can't be followed (fst,snd, generic collections) is left for the compiler to report.Not offered for method argument lists, the only argument of a member (that is its parameter list), inside quotations, or when the file has a paired
.fsi.🤖 Generated with Claude Code