From 22ce4c44205b6583befe9230da25cf47e30a51a5 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 27 Sep 2026 14:22:32 +0200 Subject: [PATCH] Handle required members on record copy constructors Roslyn annotates record copy constructors with SetsRequiredMembersAttribute when records have required members. Treat that attribute as allowed only on constructor-owned synthesized record members so copy constructors are recognized and required init auto-properties do not leak backing-field accesses. Assisted-by: OpenCode:openai/gpt-5.5:OpenCode --- .../TestCases/Pretty/Records.cs | 9 +++++++++ ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs | 14 ++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs index 99e34e73e..1e078e553 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs @@ -88,6 +88,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty } } +#if CS110 + public record WithRequiredMembers + { + public required Type ServiceType { get; init; } + + public required object ServiceKey { get; init; } + } +#endif + [AttributeUsage(AttributeTargets.All)] public class RecordTestAttribute : Attribute { diff --git a/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs b/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs index 00627ab15..9d3115f9a 100644 --- a/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs @@ -707,12 +707,14 @@ namespace ICSharpCode.Decompiler.CSharp && IsRecordType(method.Parameters[0].Type); } - private bool IsAllowedAttribute(IAttribute attribute) + private bool IsAllowedAttribute(IEntity owner, IAttribute attribute) { switch (attribute.AttributeType.ReflectionName) { case "System.Runtime.CompilerServices.CompilerGeneratedAttribute": return true; + case "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute": + return owner is IMethod { IsConstructor: true }; default: return false; } @@ -726,7 +728,7 @@ namespace ICSharpCode.Decompiler.CSharp leave IL_0000 (nop) */ Debug.Assert(method.IsConstructor && method.Parameters.Count == 1); - if (method.GetAttributes().Any(attr => !IsAllowedAttribute(attr)) || method.GetReturnTypeAttributes().Any()) + if (method.GetAttributes().Any(attr => !IsAllowedAttribute(method, attr)) || method.GetReturnTypeAttributes().Any()) return false; if (method.Accessibility != Accessibility.Protected && (!isSealed || method.Accessibility != Accessibility.Private)) return false; @@ -839,7 +841,7 @@ namespace ICSharpCode.Decompiler.CSharp return false; if (!isSealed && !method.IsOverridable) return false; - if (method.GetAttributes().Any(attr => !IsAllowedAttribute(attr)) || method.GetReturnTypeAttributes().Any()) + if (method.GetAttributes().Any(attr => !IsAllowedAttribute(method, attr)) || method.GetReturnTypeAttributes().Any()) return false; if (method.Accessibility != Accessibility.Protected && (!isSealed || method.Accessibility != Accessibility.Private)) return false; @@ -1019,7 +1021,7 @@ namespace ICSharpCode.Decompiler.CSharp return false; if (method.IsSealed) return false; - if (method.GetAttributes().Any(attr => !IsAllowedAttribute(attr)) || method.GetReturnTypeAttributes().Any()) + if (method.GetAttributes().Any(attr => !IsAllowedAttribute(method, attr)) || method.GetReturnTypeAttributes().Any()) return false; var body = DecompileBody(method); if (body == null) @@ -1093,7 +1095,7 @@ namespace ICSharpCode.Decompiler.CSharp return false; if (!isSealed && !method.IsOverridable) return false; - if (method.GetAttributes().Any(attr => !IsAllowedAttribute(attr)) || method.GetReturnTypeAttributes().Any()) + if (method.GetAttributes().Any(attr => !IsAllowedAttribute(method, attr)) || method.GetReturnTypeAttributes().Any()) return false; if (orderedMembers == null) return false; @@ -1283,7 +1285,7 @@ namespace ICSharpCode.Decompiler.CSharp return false; if (!method.IsOverride || method.IsSealed) return false; - if (method.GetAttributes().Any(attr => !IsAllowedAttribute(attr)) || method.GetReturnTypeAttributes().Any()) + if (method.GetAttributes().Any(attr => !IsAllowedAttribute(method, attr)) || method.GetReturnTypeAttributes().Any()) return false; if (orderedMembers == null) return false;