From 112f32f88f8ef6428209a001b34c307472af2ef0 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Wed, 1 Jul 2026 00:16:25 +0200 Subject: [PATCH] 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 --- .../Pretty/ConstructorInitializers.cs | 25 +++++++++++++++++++ ...ransformFieldAndConstructorInitializers.cs | 18 +++++++++++-- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs index eebe03476..31837e1ec 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs +++ b/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 public struct Issue1743WithPrimaryCtor(int dummy1, int dummy2) { diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs index d817decd3..63da3e010 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs @@ -430,13 +430,27 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms if (sequence == null) return false; + bool sequenceMatchesAllCtors = true; for (int i = 1; i < constructorsNotChainedWithThis.Count; 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)) InstanceInitializers = sequence;