Browse Source

End a call-rooted deconstruction pattern at an unrelated assignment

An assignment whose value is not one of the deconstruction's elements used
to reject the whole match, so a custom deconstruction followed by any
unrelated assignment stayed an explicit Deconstruct call. For a pattern
rooted in a Deconstruct call the element list is fixed by the call's
out-arguments, so such an assignment simply ends the pattern and stays after
the deconstruct instruction.

Tuple-rooted patterns keep rejecting: their element list is discovered from
the assignments, so ending early would misread a suffix of the assignments
as the whole pattern and fabricate discards for the elements before it.

Assisted-by: Claude:claude-opus-5:Claude Code
pull/3869/merge
Siegfried Pammer 1 month ago committed by Siegfried Pammer
parent
commit
6a368ba745
  1. 14
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs
  2. 16
      ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs

14
ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs

@ -159,6 +159,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -159,6 +159,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
return default((T, T2, T3));
}
private int GetInt()
{
return 0;
}
private AssignmentTargets Get(int i)
{
return null;
@ -171,6 +176,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -171,6 +176,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
Console.WriteLine(myInt4);
}
public void LocalVariable_NoConversion_Custom_UnrelatedAssignmentAfter()
{
var (myInt3, myInt4) = GetSource<MyInt?, MyInt>();
int value = GetInt();
Console.WriteLine(myInt3);
Console.WriteLine(myInt4);
Console.WriteLine(value);
}
public void LocalVariable_NoConversion_Tuple()
{
var (myInt, myInt2) = GetTuple<MyInt?, MyInt>();

16
ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs

@ -202,7 +202,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -202,7 +202,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
if (!MatchConversions(block, ref pos, out var conversions, out var conversionStLocs, ref delayedActions))
return false;
if (!MatchAssignments(block, ref pos, conversions, conversionStLocs, ref delayedActions))
if (!MatchAssignments(block, ref pos, conversions, conversionStLocs, ref delayedActions,
allowUnrelatedAssignments: deconstructMethod != null))
return false;
// first tuple element may not be discarded,
// otherwise we would run this transform on a suffix of the actual pattern.
@ -356,7 +357,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -356,7 +357,8 @@ namespace ICSharpCode.Decompiler.IL.Transforms
bool MatchAssignments(Block block, ref int pos,
Dictionary<ILVariable, ConversionInfo> conversions,
List<StLoc> conversionStLocs,
ref Action<DeconstructInstruction>? delayedActions)
ref Action<DeconstructInstruction>? delayedActions,
bool allowUnrelatedAssignments)
{
int previousIndex = -1;
int conversionStLocIndex = 0;
@ -364,6 +366,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -364,6 +366,16 @@ namespace ICSharpCode.Decompiler.IL.Transforms
while (MatchAssignment(block.Instructions.ElementAtOrDefault(pos), out var targetType, out var valueInst, out var addAssignment))
{
int index = FindIndex(valueInst, out var tupleAccessAdjustment);
if (index < 0 && allowUnrelatedAssignments)
{
// For a Deconstruct call the element list is fixed by the call's
// out-arguments, so an assignment whose value is unrelated to the
// deconstruction just ends the pattern and stays after the deconstruct
// instruction. (For tuples the elements are discovered from the
// assignments, so ending early would misread a suffix as the pattern:
// keep rejecting there.)
break;
}
if (index <= previousIndex)
return false;
AddMissingAssignmentsForConversions(index, ref delayedActions);

Loading…
Cancel
Save