Repository navigation
Add a refactoring between reference and struct anonymous records - #20549
xperiandri wants to merge 38 commits into
Conversation
✅ Release notes checked
|
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.
| useSource | ||
| (List.item index exprs) | ||
| (SyntaxNode.SynExpr tuple :: SyntaxNode.SynExpr argument :: argumentPath) | ||
| | ValueSome(struct (argument, argumentPath)), ValueNone -> this.Retarget useSource argument argumentPath |
There was a problem hiding this comment.
🤖 🕵️ Converting a also converts the independent b argument, producing FS0001 against its explicit reference-record type.
module M
let identity<'T> (x: 'T) = x
let a = identity {| A = 1 |} // Invoke conversion on A = 1.
let b = identity<{| B: int |}> {| B = 2 |}There was a problem hiding this comment.
Fixed in 3021452: propagation stops at a parameter, result or value whose declared type involves a generic type parameter, so converting a leaves b alone. Test: Generic parameter stops the conversion.
There was a problem hiding this comment.
🤖🕵️ New alias forwarding bypasses the fixed generic boundary and converts independent b, producing FS0001.
let identity<'T> (x: 'T) = x
let alias = identity
let a = alias {| A = 1 |} // Convert this record to struct.
let b: {| B: int |} = alias {| B = 2 |}| match isStruct, toStruct with | ||
| | true, true | ||
| | false, false -> [] | ||
| | false, true -> [ TextChange(TextSpan((spanOf sourceText m).Start, 0), "struct ") ] |
There was a problem hiding this comment.
🤖 🕵️ Converting this valid adjacent application produces idstruct {| A = 1 |}, causing FS0039.
module M
let x = id{| A = 1 |} // Invoke conversion on A = 1.There was a problem hiding this comment.
Fixed in 3021452: struct inserted before {| gets a leading space after an identifier character (by the lexer's categories) or a closing bracket, using the helper shared with tuples. Test: Struct keyword is separated from a name the record follows (id{| A = 1 |} and a name ending in U+0301).
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.
This comment has been minimized.
This comment has been minimized.
…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>
…n anonymous record
A parameter, a result or a value whose declared type involves a generic
type parameter is not followed, so converting one call of `identity` leaves
the others alone. `struct` inserted in front of `{|` gets a space after a
name or closing bracket. Anonymous-record tests cover try, match aliases,
generic parameters and the spacing.
Co-Authored-By: Claude Opus 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 Opus 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>
…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>
A function in parentheses, a name bound to a lambda, a function alias, a partial application, a named argument, a binding head in parentheses and type arguments before the record all go through code the tuple refactoring shares with this one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d of generic parts
`{| Value: 'T |}` and `'a * 'b` keep their own form whatever the parts are
instantiated with, so a function that returns or takes one converts with all of
its calls. A parameter, result or value declared `'T` still stops the conversion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ework The tuple and the anonymous record suites each had their own copy of the caret, text, error and action helpers. They now live in `RefactorTestFramework` and take the provider; each suite keeps only the three wrappers that name its provider. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port the compaction of the tuple engine: delete the comments the code says by itself, name what they described, share the edit code of tuple expressions and patterns, share the use scan of the propagation engine, put the character and trivia helpers of the conversions in one place, and keep every function at three nested levels or fewer. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The tuple and anonymous record tests run the two-file refactoring with its re-check as a new project, the round-trip assertion and the module M wrapper through RefactorTestFramework instead of copying them. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d8b637c2-ce3f-47e0-bf7f-ea6809e16eb6
* Provision Git for DartLab PR checkout and Apex tests Provision the Git distribution bundled with DartLab and expose it to both snapshot checkout and independent Apex build/test processes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 34f3e808-c404-4337-8e8c-76fcea75b3ed * Keep reviewed DartLab runs valid as main advances Remove base-SHA freshness checks while keeping reviewed-commit identity. Test a verified PR merge for current heads or the exact reviewed commit after newer pushes. Revalidate PR state and target immediately before checkout in both modes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 34f3e808-c404-4337-8e8c-76fcea75b3ed * Set the CloudTest workspace for SBOM packaging Set AGENT_WORKFOLDER to the repository root for the Apex process and its build children so MicroBuild receives a nonempty AddSBOM RootDirectory on CloudTest. Cover missing and stale caller workspace values without modifying the caller environment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 34f3e808-c404-4337-8e8c-76fcea75b3ed * Hand off Apex test results to CloudTest Copy Apex TRX results and attachments into CloudTest LoggingDirectory even when setup or build fails. Preserve the original failure, report copy errors separately, and reject successful builds that produce no TRX results. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 34f3e808-c404-4337-8e8c-76fcea75b3ed * Use repository token for DartLab membership checks Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 63fc8b67-6c73-4a2b-b892-afa58db5d1e0 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 34f3e808-c404-4337-8e8c-76fcea75b3ed Copilot-Session: 63fc8b67-6c73-4a2b-b892-afa58db5d1e0
* Backflow from https://github.com/dotnet/dotnet / c36f5f6 build 335390 Diff: https://github.com/dotnet/dotnet/compare/08c9136025fb143926499e81c06608572e204ba4..c36f5f641e3dea81f9ab824316f7a2d168277ea3 From: dotnet/dotnet@08c9136 To: dotnet/dotnet@c36f5f6 [[ commit created by automation ]] * Update dependencies from build 335390 No dependency updates to commit [[ commit created by automation ]] * Fix CI failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix CI failures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: dotnet-maestro[bot] <dotnet-maestro[bot]@users.noreply.github.com> Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Tomas Grosup <Tomas.Grosup@gmail.com>
…rd pending moves (dotnet#20655) * Support suspension inside try/with in runtime-async sequences Lower producer try/with to RuntimeAsyncSequenceHelpers.EnumerateTryWith, a new FSharp.Core compiler-services helper. The source, filter and handler become independent runtime-async iterators driven with the enumeration's cancellation token, so awaits are allowed in the protected body, filters and handlers. The source is disposed before the handler runs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Reject overlapping MoveNextAsync calls on runtime-async sequences Generated sequence types no longer re-implement IAsyncEnumerator directly, so interface calls dispatch through GeneratedRuntimeAsyncSequenceBase. The base rejects a second MoveNextAsync, or a DisposeAsync, while a move is pending. The generated MoveNextAsync releases the guard from a finally block through CompleteMoveNext, before its result completes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Use the reference sample builders in runtime-async tests Replace the runtime-async test fixtures' sample builders with copies of the reference async2 (cancellable), runtimeTask and asyncSeq2 builders and their test suites. The producer/consumer asyncSeq builder and its tests are removed; asyncSeq2 covers async sequences through __runtimeAsyncSequence, including stack-safe yield! and YieldFromFinal handoff. Tests share the builders through withSampleBuilders. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Document runtime-async sequence try/with, move guard and sample builders Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Restore async enumerable builder tests on asyncSeq2 and harden fixtures Bring back RuntimeAsyncEnumerableTests (try/with, tail recursion, pull-driven and concurrent MoveNext coverage), now running on asyncSeq2. asyncSeq2 accepts F# Async as a cold source that receives the enumeration token. New builder tests cover cancellation, unmatched exceptions escaping try/with unwrapped, and deep non-tail yield!. The MergeSources overlap test checks start order with gated awaitables instead of elapsed time. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Move runtime-async sequence release notes to 11.0.200 with PR link Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Limit runtime-async doc to the compiler feature; describe sample builders in their folder Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Lower runtime-async try/with inside the MoveNextAsync state machine Replace the FSharp.Core EnumerateTryWith helper with in-machine handling: - MoveNextAsync catches faults and re-enters the jump table, which routes each state to its innermost pending finally or handler, so bodies, guards, handlers and finally blocks can all await. - DisposeAsync sets a disposing flag and runs MoveNextAsync once more to execute pending finally blocks, skipping handlers. - GeneratedRuntimeAsyncSequenceBase implements DisposeAsync; generated types override a sync Close instead. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Lower all runtime-async try/with shapes in the state machine Remove the Seq.collect fallback. Non-delayed sources are enumerated inside the protected region, and filters that the handler does not already encode run before the handler. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * preserve EC in async2 trampoline * Test async2 sibling execution contexts Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
* Add deterministic regression triage inputs and reconciliation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Repair fair regression triage reads and stable evidence snapshots Preserve the saved d5a129190000abbe3c6299050a4dfbc86bcc06b2 implementation recovered as 3bbd7fd on the newer cdb9dc5 base. Persist trusted read ages across queue removal, schedule outstanding work before completed rechecks, and bound consistency passes for discussion and timeline evidence. Recognize Markdown-local references without fetching arbitrary URLs. Inherited baseline: 62 passing tests. Recovery RED: 28 failures out of 107; human-decision hash mutation also fails both required tests. Final GREEN: 123 tests, covering default-budget drain, restart, moving pages, metadata races, and linked-only corrections. Classification corpus unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix regression triage reference boundaries and scan validation Preserve adjacent Markdown links while masking URL fragments, recognize punctuation and emphasis around issue references, and share label validation between snapshots and listings. Reject malformed listing state or labels before filtering and retain only successful page boundaries. RED: 26 new acceptance failures on inherited 6af22cd. GREEN: both prescribed Node suites pass 149 tests; saved baseline passes 62 tests. Human-decision hash mutation fails both human cases while bot cases pass. Node syntax, diff checks and Fantomas pass; frozen corpus and recovered/newer-base history are unchanged. The requested dotnet Release command cannot start because the pinned 11.0.100-rc.1.26420.103 SDK is absent; this sprint requires no F# product build. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Guard regression triage publication with durable CAS memory Validate a single model proposal batch against trusted run-bound evidence, persist publication intents, and reconcile guarded label and clarification effects. Add versioned signed state commits, a no-write staged sink, and production adapter recovery tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix regression triage recovery and final freshness guards Separate prior clarification attempts from current analysis publication, recheck the target after linked evidence reads, and validate durable clarification and intent fields. Cover crash recovery, malformed memory and known-unsent incomplete evidence reads. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Separate prior label recovery from current triage decisions Retain unresolved prior label attempts independently so their receipts cannot complete or block newer clarification and classification work. Cover crash transitions, same-input retries, late receipts and the final target-read handoff; consolidate duplicate authentication and policy tests. Validation: both Node suites pass 373 tests; syntax, formatter and diff checks pass. Release FSharp.slnx build passes. Full solution tests finish with 35 failures (19501 passed, 537 skipped); no product files are changed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Bound missing-ledger clarification history by issue creation Persist a recovery-time boundary instead of suppressing all future questions. Keep ambiguous histories pending, migrate legacy terminal noops, and reconcile recovered creation timestamps without replaying unresolved comment attempts. Add existing-ledger controls and missing branch/file, boundary, migration and receipt recovery coverage. Both prescribed Node suites pass all 396 tests; syntax, formatting and diff checks pass. The clean Release solution build passes with zero warnings or errors. Full solution tests have reported failures; results are retained in the session logs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add independent reported regression triage workflow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix regression triage handoff and reconciliation Accept the pinned framework's empty ingestion-error envelope and preserve JSON proposal citations through its typed validation config. Keep failed ingestion fail-closed. Include qualified GitHub dependencies in freshness checks and reserve analysis capacity by pending age independently of snapshot reads. Cover the actual pinned MCP/ingestion path, continuous backlog churn, linked corrections, staged restarts and exact failed-request attempt counts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Prevent oversized triage reports from starving complete work Check model content bounds before fair candidate selection and refill batch-capacity rejections from already-read snapshots. Keep incomplete work pending without increasing read budgets. Cover mixed stable and changing backlogs, staged restarts, exact UTF-8 entry bounds and batch refill. All 475 deterministic tests and 22 actual-model fixtures pass; pinned workflow compilation remains unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Align regression triage proposals with pinned HTTP transport Cap proposal batches at 64000 UTF-8 bytes before publication, matching the pinned HTTP string-offload threshold. Exercise official tool generation, HTTP requests, ingestion and staged publication at ASCII, Unicode and escaped-content boundaries. Validated 495 deterministic tests with no skips, 22 fresh gpt-5.6-sol fixtures and staged checks, and reproducible GH AW v0.76.1 compilation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Cover regression triage verification gaps Add timestamp-only restart checks, interleaved clarification claim races, and cross-repository identity and citation coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix regression triage retry fairness and redirected evidence Rotate the reserved classification slot by persisted selection age so unresolved historical work cannot starve later reports. Use canonical API identities for redirected evidence while preserving root publication scope and freshness guards. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Reject transferred triage roots before batch admission Preserve incomplete pending history while allowing in-repository reports and discovery progress to publish across restarts. Keep linked redirects and the independent publisher scope guard intact. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Preserve triage clarification identity and require verified completion Carry clarification history across issue transfers, require human provenance for durable corrections, and fail active workflow runs without successful publication. Cover isolated reference origins and restart behavior. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Stabilize non-inline forwarding closure test fixture Keep the forwarding characterization independent of FSharp.Core inlining policy by using an explicitly non-inline recursive target. Preserve both existing allocation assertions and test bodies; List.forall2 is now inline in the locally built Core. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Add release notes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Retry rejected triage labels and preserve transferred human decisions Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Retain the successful collector binding on downstream reruns Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Replace regression triage framework with compact workflow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: efda83b5-9bca-463f-9f91-af5b6883ee3e * Constrain regression triage activation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: efda83b5-9bca-463f-9f91-af5b6883ee3e --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: efda83b5-9bca-463f-9f91-af5b6883ee3e Copilot-Session: 20b10bf6-6a7c-483a-8d09-d521e7ddd73d
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d602cc67-3f25-4432-81d4-77b542f7dce3
Replaying the branch onto main left some files short of the branch's own content; this restores it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0fe03f4 to
f82e448
Compare
Ctrl+. inside an anonymous record expression or annotated type converts
it between {| ... |} and struct {| ... |}, following the value through
the solution; the propagation engine is shared with the tuple
refactoring. From dotnet#20549 (release note left out:
18.vNext.md has local edits).
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>
Stacked on #20548.
Offers
Ctrl+.to switch between{| … |}andstruct {| … |}, following the value the same way as the tuple refactoring. A copy-and-update{| r with … |}has its own form, so it converts independently ofr.🤖 Generated with Claude Code