Browse Source

Lift chained ctor calls when sibling constructors' field inits diverge

A type's leading field assignments are extracted to field declarations
only when every constructor that does not chain with this() agrees on
them. When they disagree, the analyzer gave up on the whole type, so the
remaining constructors' this()/base() calls were never lifted into
initializers -- a struct with two divergent constructors plus a chaining
one rendered the chain as `this = new TSelf(...)` instead of `: this(...)`.

Chain lifting does not depend on the shared-initializer extraction, so a
mismatch now just skips the extraction (the assignments stay in the
bodies) and the transform continues. Primary constructors still bail,
since their parameters drive the initializers that must be extracted.

Assisted-by: Claude:claude-opus-4-8:Claude Code
pull/3863/head
Siegfried Pammer 7 days ago committed by Siegfried Pammer
parent
commit
112f32f88f
  1. 25
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs
  2. 18
      ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs

25
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs

@ -69,6 +69,31 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
} }
} }
// Multiple constructors that do not chain with this and assign fields differently must not
// prevent the remaining constructor's this(...) chain from being lifted to an initializer.
public struct StructWithDivergentCtorsAndThisChain
{
public int X;
public int Y;
public StructWithDivergentCtorsAndThisChain(int x, int y)
{
X = x;
Y = y;
}
public StructWithDivergentCtorsAndThisChain(int x)
{
X = x;
Y = 0;
}
public StructWithDivergentCtorsAndThisChain(string s)
: this(s.Length)
{
}
}
#if CS120 #if CS120
public struct Issue1743WithPrimaryCtor(int dummy1, int dummy2) public struct Issue1743WithPrimaryCtor(int dummy1, int dummy2)
{ {

18
ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs

@ -430,13 +430,27 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (sequence == null) if (sequence == null)
return false; return false;
bool sequenceMatchesAllCtors = true;
for (int i = 1; i < constructorsNotChainedWithThis.Count; i++) for (int i = 1; i < constructorsNotChainedWithThis.Count; i++)
{ {
if (!sequence.IsMatch(constructorsNotChainedWithThis[i])) if (!sequence.IsMatch(constructorsNotChainedWithThis[i]))
return false; {
sequenceMatchesAllCtors = false;
break;
}
} }
if (!isPrimaryCtor) if (!sequenceMatchesAllCtors)
{
// The non-this-chained constructors disagree on their leading field
// assignments, so there is no shared field-initializer sequence to extract.
// A primary constructor must extract its initializers (its parameters drive
// them), so bail; otherwise keep the assignments in the bodies but continue,
// so the this(...)/base(...) chains still get lifted to initializers.
if (isPrimaryCtor)
return false;
}
else if (!isPrimaryCtor)
{ {
if (!sequence.Statements.Any(s => s.DependsOnConstructorBody)) if (!sequence.Statements.Any(s => s.DependsOnConstructorBody))
InstanceInitializers = sequence; InstanceInitializers = sequence;

Loading…
Cancel
Save