Skip to content

Two back-to-back deconstructions in one method are not recognized #4059

Description

@siegfriedpammer

Two deconstructions of the same source type in one method are not recognized: the first one alone
decompiles correctly, but as soon as a second follows, both fall back to raw Deconstruct calls.

Repro

public class Src<T, T2>
{
    public void Deconstruct(out T a, out T2 b) { a = default; b = default; }
}

public class C
{
    private static Src<string, string> GetSource() => null;

    public static void One()
    {
        var (value, value2) = GetSource();
        Console.WriteLine(value);
        Console.WriteLine(value2);
    }

    public static void TwoBackToBack()
    {
        var (value, value2) = GetSource();
        Console.WriteLine(value);
        Console.WriteLine(value2);
        var (value3, value4) = GetSource();
        Console.WriteLine(value3);
        Console.WriteLine(value4);
    }
}

One round-trips. TwoBackToBack decompiles to:

public static void TwoBackToBack()
{
    GetSource().Deconstruct(out var a, out var b);
    string value = a;
    string value2 = b;
    Console.WriteLine(value);
    Console.WriteLine(value2);
    GetSource().Deconstruct(out b, out a);   // note: same locals, swapped
    string value3 = b;
    string value4 = a;
    Console.WriteLine(value3);
    Console.WriteLine(value4);
}

Why

csc reuses the same two out-slot temporaries for both calls, and the second call receives them in
the opposite order (out b, out a). DeconstructionTransform matches a deconstruction against
locals that the pattern owns; locals already written by an earlier deconstruction in the same
method do not satisfy that, so neither call is recognized.

Splitting the shared out-slot temporaries into per-deconstruction locals before pattern matching
should let both match independently.

Scope

Reproduced with -c Release on the standalone case above, and in DeconstructionTests on all
eight compiler configurations the fixture runs (Roslyn 2.10, 3.11, 4.14 and latest, with and
without Optimize), so it is not compiler-version specific.

Found while porting shape coverage onto DeconstructionTests: 19 of 20 additional shapes pass,
this is the one that does not. The test case is checked in commented out, referencing this issue.

Written by an AI agent (Claude) on Siegfried's behalf.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugDecompilerThe decompiler engine itself

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions