From 588c243929bb899e2520a1220cc9136dab2e0271 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 2 Nov 2025 11:47:10 +0100 Subject: [PATCH] #3593: ObjectInitializers: allow castclass instructions wrapping the init instruction to support the pattern used for covariant returns on non-supporting platforms such as .NET 4.x. --- .../TestCases/Pretty/InitializerTests.cs | 19 +++++++++++++++++++ ...ransformCollectionAndObjectInitializers.cs | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs index bad7cbccd..02f377974 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/InitializerTests.cs @@ -229,6 +229,11 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests public string S = "abc"; public Item I; } + + public record DerivedFields : Fields + { + public ConsoleKey E; + } #endif public interface IData @@ -1145,6 +1150,20 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.InitializerTests } }; } + + public DerivedFields DerivedRecordTest(DerivedFields input) + { + return input with { + A = 42, + D = 43, + I = new Item { + Value7 = input with { + A = 44, + D = 45 + } + } + }; + } #endif private TData GenericObjectInitializer() where TData : IData, new() diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs index b3d90d935..68c028776 100644 --- a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs +++ b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs @@ -47,6 +47,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms var insertionPos = initInst.ChildIndex; var siblings = initInst.Parent!.Children; IMethod currentMethod = context.Function.Method!; + // we allow a castclass instruction to wrap the init instruction: + // this is needed, for example, for inherited record types used on .NET runtimes (e.g., .NET 4.x), + // where covariant return types are not supported. + if (initInst.MatchCastClass(out var arg, out var targetType)) + { + initInst = arg; + } switch (initInst) { case NewObj newObjInst: @@ -100,6 +107,10 @@ namespace ICSharpCode.Decompiler.IL.Transforms } return; } + if (targetType != null) + { + instType = targetType; + } // Copy-propagate stack slot holding an 'ldloca' of the variable if (pos < block.Instructions.Count && block.Instructions[pos + 1] is StLoc { Variable: { Kind: VariableKind.StackSlot, IsSingleDefinition: true }, Value: LdLoca ldLoca } stLocStack && ldLoca.Variable == v) {