Skip to content

Add dataflow support for deconstruction assignments - #131624

Merged
sbomer merged 23 commits into
dotnet:mainfrom
sbomer:fix-deconstruction-dataflow-123767
Aug 8, 2026
Merged

Add dataflow support for deconstruction assignments#131624
sbomer merged 23 commits into
dotnet:mainfrom
sbomer:fix-deconstruction-dataflow-123767

Conversation

@sbomer

@sbomer sbomer commented Jul 30, 2026

Copy link
Copy Markdown
Member

Fixes #123767.

The ILLink Roslyn analyzer's dataflow visitor did not handle IDeconstructionAssignmentOperation, so RequiresUnreferencedCode and DynamicallyAccessedMembers diagnostics were silently skipped for values flowing through deconstruction, including assignments and deconstruction in foreach.

Summary of changes

  • Add dataflow support for tuple expressions, tuple-typed values, instance and extension Deconstruct methods, nested deconstruction, and user-defined conversions.
  • Support local, field, property, parameter, discard, array-element, explicit-indexer, implicit-indexer, and nested tuple targets.
  • Preserve C# evaluation order by evaluating target locations before source values and performing target writes only after all source values and nested Deconstruct calls have been evaluated.
  • Preserve flow-captured target receivers and index arguments so target locations are evaluated once and retain the values selected before source-side mutations.
  • Correctly map receivers for classic extension methods and C# 14 extension-block Deconstruct methods.
  • Treat outer or nested [DoesNotReturn] Deconstruct calls as unreachable and suppress every target assignment on that path.

Testing

Added regression coverage for deconstruction sources, targets, evaluation ordering, flow captures, extension receivers, user-defined conversions, and direct and nested [DoesNotReturn] methods. The DataFlow suites pass across:

  • ILLink.RoslynAnalyzer.Tests
  • Mono.Linker.Tests
  • ILCompiler.Trimming.Tests
  • ILTrim.Tests

Two unsupported ILTrim scenarios are isolated as standalone expected failures instead of excluding the shared deconstruction coverage:

  • DeconstructFieldTarget exposes an existing ILTrim.Core gap in static-field write dataflow.
  • DeconstructUserDefinedConversion exposes an existing ILTrim.Core gap in tracking a user-defined conversion operator's return value.

The Roslyn analyzer, linker, and NativeAOT tests retain coverage for both cases. ILLink/ILTrim and NativeAOT also conservatively report the expected warning after [DoesNotReturn] calls because the IL-based tools do not treat that attribute as a reachability contract; those expectations are scoped to the IL-based tools while the Roslyn analyzer verifies the path is unreachable.

Note

This content was created with assistance from AI.

sbomer and others added 8 commits July 30, 2026 11:25
Fixes dotnet#123767: the ILLink Roslyn analyzer's dataflow
visitor did not handle IDeconstructionAssignmentOperation, so
DynamicallyAccessedMembers annotations were silently dropped across
deconstruction assignments and foreach-variable deconstruction.

- LocalDataFlowVisitor: add VisitDeconstructionAssignment, which uses
  the semantic model's GetDeconstructionInfo (IOperation does not
  expose the Deconstruct-method/conversion recipe on
  IDeconstructionAssignmentOperation) together with the existing
  Target IOperation tree to evaluate all source values before
  assigning any target, preserving correct swap semantics such as
  (first, second) = (second, first).
- TrimAnalysisVisitor: add GetTupleElementValue override consumed by
  the new evaluation path.
- Add DeconstructTupleSwapSuccess test case alongside the existing
  DeconstructTupleSwap warning case, covering the no-warning path
  where annotations propagate correctly across a tuple swap.

Assisted-by: Copilot:claude-sonnet-5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
Deconstruction assignment targets can have side-effecting sub-expressions
that identify a write location, e.g. the receiver of a property/indexer
target or the array reference/index of an array-element target. Roslyn's
own lowering evaluates these before touching the right-hand side
(GetAssignmentTargetsAndSideEffects), matching left-to-right evaluation
order, but our previous implementation only visited them as part of
performing the write in AssignDeconstruction, which runs after the
entire source side has already been evaluated.

Add VisitDeconstructionTargetSideEffects, a pre-pass over the target
tree that visits these sub-expressions (without performing any write)
before the source is visited. The same sub-expressions are visited
again later when AssignDeconstruction performs the actual write;
revisiting the same IOperation node is safe because the trim analysis
pattern store merges patterns keyed by IOperation identity, the same
tolerance already relied on elsewhere in this file (see ProcessAssignment
for flow-capture targets with multiple captured references).

Add a regression test (DeconstructPropertyTargetSideEffect) covering a
property target whose receiver is a side-effecting, warning-producing
expression, to lock in that the warning fires exactly once.

Assisted-by: Copilot:claude-sonnet-5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
Extract TryVisitAssignmentTargetInstance, a shared helper covering the
subset and order of a target operation's side-effecting sub-expressions
(receiver instance, and/or index/argument sub-expressions) that
ProcessSingleTargetAssignment already visits before evaluating the
value being assigned. This is a pure extract-method refactor of
ProcessSingleTargetAssignment with no behavior change: the same
sub-expressions are visited in the same order as before, for every
target kind, including IPropertyReferenceOperation's explicit indexer
Arguments, which are intentionally left out of the shared helper
because they are only visited after the value today (a known,
pre-existing ordering quirk in ProcessSingleTargetAssignment, distinct
from the implicit System.Index-based indexer and array-element cases,
which already visit their index arguments before the value).

VisitDeconstructionTargetSideEffects now calls this same shared helper
instead of a hand-duplicated switch. This means deconstruction targets
evaluate side effects using the exact same subset/order as ordinary
assignment targets, including the explicit-indexer-Arguments gap,
rather than the previous, inconsistent behavior where deconstruction
pre-visited indexer Arguments that ordinary assignment does not. If the
underlying evaluation-order gap is fixed later, fixing it in this one
shared place will fix it for both assignment forms.

No test or behavior changes; verified zero regressions across the
analyzer, linker, ILTrim, and ILC/NativeAOT DataFlow test suites.

Assisted-by: Copilot:claude-sonnet-5

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
…nstruction targets

VisitDeconstructionTargetSideEffects previously pre-visited an explicit
indexer's Arguments before the source, but ProcessSingleTargetAssignment
(used for ordinary assignments) only visits those same Arguments after
the value due to a pre-existing ordering quirk. This mismatch meant
deconstruction assignment targets and ordinary assignment targets could
report side effects in a different relative order for the same shape of
target (e.g. 'this[F()] = ...' vs '(this[F()], b) = ...').

Rather than fixing the pre-existing quirk (out of scope here) or sharing
logic via a new helper (reverted in the prior commit as too broad a
refactor for this fix), simply stop pre-visiting the indexer Arguments in
VisitDeconstructionTargetSideEffects. This makes deconstruction targets
consistent with ordinary assignment targets again, preserving today's
(admittedly imperfect) evaluation order rather than introducing a new,
narrower divergence.

Verified no regressions across all four toolchains:
- ILLink.RoslynAnalyzer.Tests DataFlow: 81 passed, 2 skipped
- Mono.Linker.Tests DataFlowTests: 80 passed
- ILTrim.Tests DataFlowTests: 43 passed, 37 skipped
- ILCompiler.Trimming.Tests DataFlow (Release): 80 passed

Assisted-by: Copilot:claude-sonnet-5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
…ites

VisitDeconstructionTargetSideEffects, EvaluateDeconstruction, and
AssignDeconstruction each already unwrap their target parameter as the
first statement on entry (required for their recursive calls, which pass
targetTuple.Elements[i] without pre-unwrapping). The top-level calls from
VisitDeconstructionAssignment were redundantly unwrapping operation.Target
before passing it in, duplicating work each method already does itself.
Pass operation.Target directly and let each method's own unwrap-on-entry
handle it, matching the recursive calls.

Verified no regressions across all four toolchains:
- ILLink.RoslynAnalyzer.Tests DataFlow: 81 passed, 2 skipped
- Mono.Linker.Tests DataFlowTests: 80 passed
- ILTrim.Tests DataFlowTests: 43 passed, 37 skipped
- ILCompiler.Trimming.Tests DataFlow (Release): 80 passed

Assisted-by: Copilot:claude-sonnet-5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
DeconstructPropertyTargetSideEffect covers a parameterless property
target's receiver side effect, but no test exercised an explicit
indexer target (IPropertyReferenceOperation with Arguments) - exactly
the case the last two commits changed the evaluation order for. Add
DeconstructIndexerTargetSideEffect, which uses an indexer target whose
receiver and index argument are both side-effecting
[RequiresUnreferencedCode] calls, verifying each is visited exactly
once (no duplicate warnings) despite being visited from two different
places (the target-side-effects pre-pass for the receiver, and the
actual write for the index argument).

Verified across all four toolchains:
- ILLink.RoslynAnalyzer.Tests DataFlow: 81 passed, 2 skipped
- Mono.Linker.Tests DataFlowTests: 80 passed
- ILTrim.Tests DataFlowTests: 43 passed, 37 skipped
- ILCompiler.Trimming.Tests DataFlow (Release): 80 passed

Assisted-by: Copilot:claude-sonnet-5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
Closes the rest of the test-coverage gaps identified for deconstruction
dataflow: field target, parameter target, discard target, array-element
target (with side-effect ordering, like the indexer case), implicit
System.Index-based indexer target, and a source element requiring a
user-defined conversion operator (verifying EvaluateDeconstruction's
conversion.MethodSymbol-is-not-null branch runs without crashing or
producing an unexpected warning).

DeconstructFieldTarget exposes a pre-existing, unrelated ILTrim
limitation in static field write dataflow tracking (the same class of
issue that already causes the whole, unrelated FieldDataFlow.cs test to
be entirely skipped for ILTrim - see ILTrimExpectedFailures.txt). Both
the Roslyn analyzer and NativeAOT ILCompiler correctly detect this
case; only ILTrim.Core's separate dataflow engine does not. Since
ILTrim's known-limitation mechanism only supports skipping a whole test
case (not one assertion within it), and Tool.NativeAot-based
ExpectedWarning filtering can't distinguish ILTrim from ILC (they share
the flag), add ConstructedTypesDataFlow to ILTrimExpectedFailures.txt
rather than weakening the assertion or dropping the test - preserving
full verification under the other three toolchains.

Verified across all four toolchains:
- ILLink.RoslynAnalyzer.Tests DataFlow: 81 passed, 2 skipped
- Mono.Linker.Tests DataFlowTests: 80 passed
- ILTrim.Tests DataFlowTests: 42 passed, 38 skipped (ConstructedTypesDataFlow now a known failure)
- ILCompiler.Trimming.Tests DataFlow (Release): 80 passed

Assisted-by: Copilot:claude-sonnet-5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
Copilot AI review requested due to automatic review settings July 30, 2026 23:01
@github-actions github-actions Bot added the area-Tools-ILLink .NET linker development as well as trimming analyzers label Jul 30, 2026
@dotnet-policy-service dotnet-policy-service Bot added the linkable-framework Issues associated with delivering a linker friendly framework label Jul 30, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @agocke, @dotnet/illink
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR extends the ILLink Roslyn analyzer’s local dataflow analysis to understand deconstruction assignments (IDeconstructionAssignmentOperation), and updates trimming/dataflow test coverage to exercise the new behavior across multiple toolchains.

Changes:

  • Implement deconstruction assignment evaluation + assignment sequencing in LocalDataFlowVisitor, including tuple element handling.
  • Extend TrimAnalysisVisitor to model tuple-element values for tuple-typed sources.
  • Add/adjust regression tests in ConstructedTypesDataFlow.cs, and mark the ILTrim suite as an expected failure.
Show a summary per file
File Description
src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs Adds VisitDeconstructionAssignment support and supporting helpers for deconstruction evaluation/assignment.
src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs Implements tuple-element value modeling needed by deconstruction-from-tuple-type sources.
src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ConstructedTypesDataFlow.cs Adds deconstruction regression coverage across many target/source forms and adjusts expected diagnostics.
src/coreclr/tools/ILTrim.Tests/ILTrimExpectedFailures.txt Marks the full DataFlow.ConstructedTypesDataFlow suite as an expected ILTrim failure.

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment thread src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs Outdated
sbomer and others added 7 commits July 31, 2026 10:31
EvaluateDeconstruction keyed the synthesized Deconstruct() call on
'source ?? target'. If the source expression already had its own
tracked call pattern (e.g. it's itself a method call), reusing that
IOperation as the key made the pattern store merge two unrelated
calls (mismatched called method/argument count), crashing in Release
and asserting in Debug. Flagged by Copilot's review of
dotnet#131624.

Fix by keying on 'target' (the tuple pattern) instead, which is never
used as a call operation key elsewhere and is unique per nesting
level. Call HandleMethodCall directly rather than through
HandleMethodCallHelper, since the latter's DoesNotReturnIf handling
assumes an IInvocationOperation/IObjectCreationOperation.

Added DeconstructMethodCallSource, a regression test with a
[RequiresUnreferencedCode] method call as the Deconstruct() source.
Verified it crashes the analyzer without the fix and passes cleanly
with it.

Verified across all four toolchains:
- ILLink.RoslynAnalyzer.Tests DataFlow: 81 passed, 2 skipped
- Mono.Linker.Tests DataFlowTests: 80 passed
- ILTrim.Tests DataFlowTests: 42 passed, 38 skipped (unchanged)
- ILCompiler.Trimming.Tests DataFlow (Release): 80 passed

Assisted-by: Copilot:claude-sonnet-5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
Two review fixes.

1. A deconstruction element that goes through a user-defined conversion
   operator produced TopValue. TopValue is the empty ValueSet, and
   TrimAnalysisAssignmentPattern.ReportDiagnostics iterates over the
   source values, so an empty source meant zero checks and zero
   diagnostics - silently dropping the annotation check. Assigning such
   a value to an annotated target produced no warning, while the
   equivalent non-deconstruction assignment correctly warned IL2074.

   Model the converted value the same way VisitConversion does, as the
   conversion operator's return value, via a new GetConversionValue
   hook. This path is only reachable when the conversion is described
   by the DeconstructionInfo (a tuple-typed source); for a tuple
   literal the conversion appears as an IConversionOperation in the
   tree and was already handled by VisitConversion.

   DeconstructWithUserDefinedConversion couldn't catch this because its
   target type isn't tracked for dataflow, so it passed regardless of
   the value produced. Added
   DeconstructUserDefinedConversionToAnnotatedTarget, which fails
   without this fix.

2. Re-disable the two Debug.Asserts in VisitFlowCaptureReference and
   VisitPropertyReference. Deconstruction was not the only thing
   reaching them: increment/decrement and coalescing assignment are not
   handled and fall back to the base visitor, which visits the write
   target directly, so a plain 'obj.Property++', 'obj.Property ??= x'
   or 'obj[i]++' fails the assert. Since the analyzer is built in the
   repo's own configuration, that turns into an AD0001 analyzer
   exception and breaks the build in Debug/Checked builds. Handling
   those operations is unrelated to deconstruction, so leave the
   asserts off and record what still needs to happen before they can be
   enabled.

Verified across all four toolchains:
- ILLink.RoslynAnalyzer.Tests DataFlow: 81 passed, 2 skipped
- Mono.Linker.Tests DataFlowTests: 80 passed
- ILTrim.Tests DataFlowTests: 42 passed, 38 skipped
- ILCompiler.Trimming.Tests DataFlow (Release): 80 passed

Assisted-by: Copilot:claude-opus-5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
VisitConversion and GetConversionValue had the same logic, with a
comment asking to keep them in sync. Have VisitConversion call
GetConversionValue instead, so there's a single implementation and
the two paths can't drift.

Verified: full ILLink.RoslynAnalyzer.Tests suite, 1201 passed, 9 skipped.

Assisted-by: Copilot:claude-opus-5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
A deconstruction evaluates the sub-expressions that identify a target
location (a receiver, or an index) before reading any source value, but
the write can only happen after all source values have been read. The
write went back and visited those same sub-expressions a second time,
which meant they were analyzed twice with the state from before and
after the source. Both observations end up merged into the same
operation's analysis, so a value that the expression can never actually
see is treated as if it could reach it.

For example, in

    Type type = annotated;
    (GetHolder(type).AnnotatedProperty, other) = ((type = unannotated), new object());

GetHolder is called before the source runs, so only the annotated value
can ever reach it, but the analyzer also saw the second visit where
'type' holds the unannotated value and reported a warning for a call
that can't happen.

Remember the values produced when evaluating the target sub-expressions
and reuse them when performing the write, instead of evaluating the
expressions again. Ordinary assignments evaluate the target and the
value in one pass and never record anything, so they are unaffected.

Verified across all four toolchains:
- ILLink.RoslynAnalyzer.Tests: 1201 passed, 9 skipped (full suite)
- Mono.Linker.Tests DataFlowTests: 80 passed
- ILTrim.Tests DataFlowTests: 42 passed, 38 skipped
- ILCompiler.Trimming.Tests DataFlow (Release): 80 passed

Assisted-by: Copilot:claude-opus-5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
Keep each deconstruction's saved target values in a local dictionary and pass them only through its target evaluation and write paths. Nested deconstructions naturally use their own call-frame state, removing the visitor field and save/restore logic.

Assisted-by: Copilot:gpt-5.6-sol

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
Move the two unsupported ILTrim deconstruction scenarios into standalone shared test cases so ConstructedTypesDataFlow continues to run. Keep strict analyzer checks for the moved warnings.

Assisted-by: Copilot:gpt-5.6-sol

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d

Assisted-by: GitHub Copilot CLI:gpt-5.6-sol
Copilot AI review requested due to automatic review settings August 3, 2026 17:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

Suppressed comments (2)

src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs:769

  • The synthesized Deconstruct() call is dispatched via HandleMethodCall directly, which bypasses the base visitor’s DoesNotReturn handling. If a user-defined Deconstruct method is annotated with [DoesNotReturn], the current implementation would still proceed to evaluate nested values and assign targets, potentially producing warnings/state changes after a non-returning call.

Consider explicitly honoring DoesNotReturn here by resetting state to Top and stopping deconstruction assignment processing when the attribute is present.

                HandleMethodCall(
                    deconstructMethod,
                    instanceValue,
                    arguments.MoveToImmutable(),
                    target,

src/coreclr/tools/ILTrim.Tests/ILTrimExpectedFailures.txt:87

  • The PR description says only DeconstructFieldTarget is added to ILTrimExpectedFailures.txt due to an ILTrim limitation, but this change also adds DeconstructUserDefinedConversion. Either the description should be updated to explain why this additional case is expected to fail under ILTrim, or the extra entry should be removed if it’s unintended.
DataFlow.DeconstructFieldTarget
DataFlow.DeconstructUserDefinedConversion
  • Files reviewed: 8/8 changed files
  • Comments generated: 0 new

sbomer added 2 commits August 3, 2026 13:53
Map C# extension-block receivers using metadata parameter positions so synthesized Deconstruct calls propagate receiver annotations correctly.

Assisted-by: GitHub Copilot:gpt-5.6-sol

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
Propagate DoesNotReturn through nested deconstruction evaluation, suppress target assignments on unreachable paths, and cover direct and nested cases across analyzer and IL-based tools.

Assisted-by: GitHub Copilot:gpt-5.6-sol

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
Copilot AI review requested due to automatic review settings August 3, 2026 20:54
Copilot AI review requested due to automatic review settings August 5, 2026 17:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 10/10 changed files
  • Comments generated: 1

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d

Assisted-by: GitHub Copilot CLI:gpt-5.6-sol
Copilot AI review requested due to automatic review settings August 6, 2026 18:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Note

This error may be related to your runner configuration. You can now configure runners for Copilot code review separately from Copilot cloud agent by creating a copilot-code-review.yml file with your setup steps. Read the docs for details.

Comment thread src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs Outdated
Comment thread src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs Outdated
Comment thread src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs Outdated
sbomer added 3 commits August 6, 2026 20:50
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d

Assisted-by: GitHub Copilot CLI:gpt-5.6-sol
Assisted-by: GitHub Copilot CLI:GPT-5.6 Sol

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
Assisted-by: GitHub Copilot CLI:GPT-5.6 Sol

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
Copilot AI review requested due to automatic review settings August 7, 2026 18:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot's findings

Suppressed comments (1)

src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ConstructedTypesDataFlow.cs:311

  • The comment describing array-element deconstruction target evaluation appears to be outdated. The deconstruction visitor now saves target sub-expression values and reuses them during the write (via savedTargetValues / VisitTargetSubExpression), so these sub-expressions should not be analyzed twice in the deconstruction path. Please update the comment to reflect the current behavior.
            // Like DeconstructIndexerTargetSideEffect, but for an array element target
            // (IArrayElementReferenceOperation). Unlike the explicit indexer case, both the array
            // reference and the index are pre-visited by VisitDeconstructionTargetSideEffects (there's
            // no equivalent to the indexer-Arguments ordering quirk here), so both are visited twice
            // (once ahead of the source, once again performing the write) - verifying each still
  • Files reviewed: 10/10 changed files
  • Comments generated: 0 new

@sbomer
sbomer enabled auto-merge (squash) August 7, 2026 21:39
@sbomer
sbomer merged commit 778a989 into dotnet:main Aug 8, 2026
130 of 132 checks passed
@github-project-automation github-project-automation Bot moved this to Done in AppModel Aug 8, 2026
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-rc1 milestone Aug 10, 2026
@sbomer
sbomer deleted the fix-deconstruction-dataflow-123767 branch August 10, 2026 16:23
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Aug 11, 2026
Fixes dotnet#123767.

The ILLink Roslyn analyzer's dataflow visitor did not handle
`IDeconstructionAssignmentOperation`, so `RequiresUnreferencedCode` and
`DynamicallyAccessedMembers` diagnostics were silently skipped for
values flowing through deconstruction, including assignments and
deconstruction in `foreach`.

## Summary of changes

- Add dataflow support for tuple expressions, tuple-typed values,
instance and extension `Deconstruct` methods, nested deconstruction, and
user-defined conversions.
- Support local, field, property, parameter, discard, array-element,
explicit-indexer, implicit-indexer, and nested tuple targets.
- Preserve C# evaluation order by evaluating target locations before
source values and performing target writes only after all source values
and nested `Deconstruct` calls have been evaluated.
- Preserve flow-captured target receivers and index arguments so target
locations are evaluated once and retain the values selected before
source-side mutations.
- Correctly map receivers for classic extension methods and C# 14
extension-block `Deconstruct` methods.
- Treat outer or nested `[DoesNotReturn] Deconstruct` calls as
unreachable and suppress every target assignment on that path.

## Testing

Added regression coverage for deconstruction sources, targets,
evaluation ordering, flow captures, extension receivers, user-defined
conversions, and direct and nested `[DoesNotReturn]` methods. The
DataFlow suites pass across:

- `ILLink.RoslynAnalyzer.Tests`
- `Mono.Linker.Tests`
- `ILCompiler.Trimming.Tests`
- `ILTrim.Tests`

Two unsupported ILTrim scenarios are isolated as standalone expected
failures instead of excluding the shared deconstruction coverage:

- `DeconstructFieldTarget` exposes an existing ILTrim.Core gap in
static-field write dataflow.
- `DeconstructUserDefinedConversion` exposes an existing ILTrim.Core gap
in tracking a user-defined conversion operator's return value.

The Roslyn analyzer, linker, and NativeAOT tests retain coverage for
both cases. ILLink/ILTrim and NativeAOT also conservatively report the
expected warning after `[DoesNotReturn]` calls because the IL-based
tools do not treat that attribute as a reachability contract; those
expectations are scoped to the IL-based tools while the Roslyn analyzer
verifies the path is unreachable.

> [!NOTE]
> This content was created with assistance from AI.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b4dc4554-6607-4109-ab22-56a5815b294d
sbomer added a commit that referenced this pull request Aug 11, 2026
…132086)

## Summary

Adds a regression test for #117122, a crash (`Debug.Assert` failure in
`VisitImplicitIndexerReference`) hit by the ILLink Roslyn analyzer when
analyzing a tuple-swap deconstruction assignment involving `Span<T>`
elements accessed via the `^` (Index) operator, e.g.:

```csharp
Span<int> span = stackalloc int[4];
(span[^1], span[^2]) = (span[^2], span[^1]);
```

## Root cause

`GetValueUsageInfo` returns `ValueUsageInfo.Write` (without
`.Reference`) for operations on the left side of a deconstruction
assignment. `VisitImplicitIndexerReference` previously asserted that any
`Write`-flagged implicit indexer reference must also be `Reference`, an
assumption that didn't hold for deconstruction-assignment targets.

This is already fixed on `main` by #131624 ("Add dataflow support for
deconstruction assignments"), which added proper
`VisitDeconstructionAssignment` handling that routes deconstruction
targets through dedicated assignment-processing logic instead of the
generic operation visitor that could trip the old assert. That change
closes #123767, and as a side effect also fixes this crash — verified by
testing with only that fix applied (no separate patch needed here).

## Testing

Folded the new test case (`TestTupleSwap`) into the existing
`ImplicitIndexerAccess` nested test class in `PropertyDataFlow.cs`,
since it already covers directly related `^`-indexer and `Span<T>` write
scenarios. Verified the test:
- Reproduces the crash against code prior to #131624.
- Passes with #131624 applied.
- Passes across all three tools that share this test-case file: the
Roslyn analyzer (`ILLink.RoslynAnalyzer.Tests`), the IL trimmer
(`Mono.Linker.Tests`), and NativeAOT (`ILCompiler.Trimming.Tests`).

> [!NOTE]
> This content was created with assistance from AI.

Fixes #117122

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fc762140-27e7-4d82-9f89-a55a3d8b7d78
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-Tools-ILLink .NET linker development as well as trimming analyzers linkable-framework Issues associated with delivering a linker friendly framework

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

ILLink analyzer hole for deconstruction assignments

3 participants