Skip to content

Add a refactoring between reference and struct anonymous records - #20549

Open
xperiandri wants to merge 38 commits into
dotnet:mainfrom
xperiandri:feature/anonymous-record-struct-refactoring
Open

xperiandri wants to merge 38 commits into
dotnet:mainfrom
xperiandri:feature/anonymous-record-struct-refactoring

Conversation

@xperiandri

@xperiandri xperiandri commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #20548.

Offers Ctrl+. to switch between {| … |} and struct {| … |}, following the value the same way as the tuple refactoring. A copy-and-update {| r with … |} has its own form, so it converts independently of r.

🤖 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 reference and struct anonymous records Add a refactoring between reference and struct anonymous records Sep 15, 2026
@xperiandri

Copy link
Copy Markdown
Contributor Author

Covered cases

Converts

  • An anonymous record used nowhere else converts on its own, and back: printfn "%A" {| A = 1 |} → printfn "%A" struct {| A = 1 |}; a type argument list<{| A: int |}> → list<struct {| A: int |}>; a multi-line {| … |} → struct {| … |}
  • A value converts together with the annotations it flows into, and field access is left as is, both ways: let person = {| Name = "Ada"; Age = 36 |} + let copy: {| Name: string; Age: int |} = person + person.Age → struct {| Name = "Ada"; Age = 36 |} + let copy: struct {| Name: string; Age: int |} = person
  • A parameter annotation converts every argument passed to it, both literals and values, both ways: let greet (p: {| Name: string |}) + let ada = {| Name = "Ada" |} + greet ada; greet {| Name = "Bob" |} → let greet (p: struct {| Name: string |}) + let ada = struct {| Name = "Ada" |} + greet struct {| Name = "Bob" |}
  • A return type converts the result and the annotated values it is bound to, both ways: let origin () : {| X: int; Y: int |} = {| X = 0; Y = 0 |} + let start: {| X: int; Y: int |} = origin () → struct {| X: int; Y: int |} in all three places
  • A record field converts only its own values and the annotations that read it, both ways: { Info: {| Age: int |}; Tags: {| Count: int |} } + { Info = {| Age = 30 |}; Tags = {| Count = 0 |} } + let info: {| Age: int |} = person.Info → only Info becomes struct {| Age: int |}, struct {| Age = 30 |} and let info: struct {| Age: int |}; Tags is not changed
  • A copy-and-update expression converts separately from its source, both ways: the caret on let point = {| X = 1 |} gives struct {| X = 1 |} and leaves {| point with Y = 2 |} alone; the caret on with gives struct {| point with Y = 2 |} and leaves point alone
  • A value declared in a second file converts in that file: the caret on let name (p: {| Name: string |}) + name A.person in module B also rewrites let person = {| Name = "Ada" |} in A to struct {| Name = "Ada" |}; the result is checked as a new project and has no errors
  • The action titles name the target kind: Convert to struct anonymous record and Convert to reference anonymous record
  • Every result is re-type-checked and asserted to have no errors

Not offered

  • The caret is not on an anonymous record: let x = 1
  • A normal record: type Point = { X: int } + let point = { X = 1 }
  • An anonymous record inside a quotation: <@ {| A = 1 |} @>

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

useSource
(List.item index exprs)
(SyntaxNode.SynExpr tuple :: SyntaxNode.SynExpr argument :: argumentPath)
| ValueSome(struct (argument, argumentPath)), ValueNone -> this.Retarget useSource argument argumentPath

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.

🤖 🕵️ 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 |}

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

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.

🤖🕵️ 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 ") ]

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.

🤖 🕵️ Converting this valid adjacent application produces idstruct {| A = 1 |}, causing FS0039.

module M

let x = id{| A = 1 |}  // Invoke conversion on A = 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 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).

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

xperiandri and others added 23 commits October 29, 2026 22:16
…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>
@xperiandri
xperiandri force-pushed the feature/anonymous-record-struct-refactoring branch from 0fe03f4 to f82e448 Compare October 29, 2026 22:03
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 29, 2026
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>
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 29, 2026
…ents with their calls

From dotnet#20548 and dotnet#20549.

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
…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>
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 29, 2026
…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>
xperiandri added a commit to xperiandri/fsharp that referenced this pull request Oct 29, 2026
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>

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

5 participants