Browse Source

Demote an escaping first element instead of giving up on the pattern

A nested designation whose temporary is still read elsewhere is retried with
that variable demoted to a designator leaf. The check that the first tuple
element must be assigned ran before that retry, and every leaf of a wrongly
nested first element precedes the assigned ones, so the pattern looked like it
started mid-way and was rejected before the retry could restore it. The flat
deconstruction was lost for a shape that has one.

Assisted-by: Claude:claude-opus-5[1m]:Claude Code
pull/3869/merge
Siegfried Pammer 1 month ago committed by Siegfried Pammer
parent
commit
fdc4c7c16d
  1. 10
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/DeconstructionTests.cs
  2. 24
      ICSharpCode.Decompiler/IL/Transforms/DeconstructionTransform.cs

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

@ -551,6 +551,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty @@ -551,6 +551,16 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
#endif
}
// Same, but the escaping element is in the first position. Every leaf of the
// wrongly nested node precedes the assigned ones there, so the retry that demotes
// it has to be reached before the pattern is judged to start mid-way.
public void LocalVariable_TupleInner_FirstElementUsedOutside()
{
var (tuple2, value) = GetTuple<(int, int), int>();
Console.WriteLine(tuple2.Item1);
Console.WriteLine(value);
}
public void ForEach_Nested_TupleInner()
{
foreach (var (value, (value2, value3)) in GetList<(int, (int, int))>())

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

@ -210,22 +210,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms @@ -210,22 +210,26 @@ namespace ICSharpCode.Decompiler.IL.Transforms
// the forwarding fixup in MatchAssignments.
if (!anyAssignments && !(rootCall != null && rootCall.NestedCalls.Any(c => c != null)))
return false;
// first tuple element may not be discarded,
// otherwise we would run this transform on a suffix of the actual pattern.
if (deconstructionResults[0] == null)
return false;
// A nested tuple designation only holds if the pattern consumed every read of
// its temporary; a remaining read means the value escapes the designation.
// Retry with the variable as a plain designator leaf, which restores the flat
// deconstruction the escaping read needs.
// deconstruction the escaping read needs. This has to be decided before the
// leaf check below: every leaf of a wrongly nested first element precedes the
// assigned ones, so that check would report the pattern as starting mid-way
// and give up on a designation the retry can still make work.
var escaped = EscapedTupleNodes();
if (escaped == null)
if (escaped != null)
{
endPos = pos;
return true;
doNotNest ??= new HashSet<ILVariable>();
doNotNest.UnionWith(escaped);
continue;
}
doNotNest ??= new HashSet<ILVariable>();
doNotNest.UnionWith(escaped);
// first tuple element may not be discarded,
// otherwise we would run this transform on a suffix of the actual pattern.
if (deconstructionResults[0] == null)
return false;
endPos = pos;
return true;
}
List<ILVariable>? EscapedTupleNodes()

Loading…
Cancel
Save