diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Playstation.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Playstation.cs index ccea9b125..1f7abcc8f 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Playstation.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Playstation.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Threading; @@ -226,6 +227,79 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.Playstation } } + internal class Issue3610 + { + private struct CtorDoubleAssignmentTest + { +#if EXPECTED_OUTPUT + public bool Value = false; + + public CtorDoubleAssignmentTest(string arg1, int arg2) + { + Value = true; + } +#else + public bool Value; + + public CtorDoubleAssignmentTest(string arg1, int arg2) + { + Value = false; + Value = true; + } +#endif + } + + private struct CtorDoubleAssignmentTest2 + { + public bool Value = true; + + public CtorDoubleAssignmentTest2(string arg1, int arg2) + { + Value = false; + } + } + + private class FieldInitTest + { + public bool Flag = true; + public Func Action = (int a) => a; + public string Value; + + public FieldInitTest(string value) + { + Value = value; + } + } + + private abstract class PCFieldInitTest(StringComparison value) + { + private StringComparison _value = value; + + public bool Func() + { + return value == StringComparison.Ordinal; + } + } + + private class RecordTest + { + private interface IInterface + { + T[] Objects { get; } + } + + protected record Record(T[] Objects) : IInterface + { + public Record(List objects) + : this(objects.ToArray()) + { + } + } + } + + private abstract record RecordTest2(Guid[] Guids); + } + public record NamedParameter(string name, object? value, bool encode = true) : Parameter(Ensure.NotEmptyString(name, "name"), value, encode); [DebuggerDisplay("{DebuggerDisplay()}")] diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PlaystationPreferPrimary.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PlaystationPreferPrimary.cs index 9aa4e1e33..8b66169f0 100644 --- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PlaystationPreferPrimary.cs +++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/PlaystationPreferPrimary.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Threading; @@ -201,6 +202,79 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty.PlaystationPreferPrimary } } + + internal class Issue3610 + { + private struct CtorDoubleAssignmentTest + { +#if EXPECTED_OUTPUT + public bool Value = false; + + public CtorDoubleAssignmentTest(string arg1, int arg2) + { + Value = true; + } +#else + public bool Value; + + public CtorDoubleAssignmentTest(string arg1, int arg2) + { + Value = false; + Value = true; + } +#endif + } + + private struct CtorDoubleAssignmentTest2 + { + public bool Value = true; + + public CtorDoubleAssignmentTest2(string arg1, int arg2) + { + Value = false; + } + } + + private class FieldInitTest + { + public bool Flag = true; + public Func Action = (int a) => a; + public string Value; + + public FieldInitTest(string value) + { + Value = value; + } + } + + private abstract class PCFieldInitTest(StringComparison value) + { + private StringComparison _value = value; + + public bool Func() + { + return value == StringComparison.Ordinal; + } + } + + private class RecordTest + { + private interface IInterface + { + T[] Objects { get; } + } + + protected record Record(T[] Objects) : IInterface + { + public Record(List objects) + : this(objects.ToArray()) + { + } + } + } + + private abstract record RecordTest2(Guid[] Guids); + } public record NamedParameter(string name, object? value, bool encode = true) : Parameter(Ensure.NotEmptyString(name, "name"), value, encode); [DebuggerDisplay("{DebuggerDisplay()}")] diff --git a/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs b/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs index 64f750ba4..4bd52daee 100644 --- a/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs @@ -177,6 +177,8 @@ namespace ICSharpCode.Decompiler.CSharp Dictionary ctorCallChain = new Dictionary(); + Accessibility minAccessibility = recordTypeDef.IsAbstract ? Accessibility.Protected : Accessibility.Public; + foreach (var method in recordTypeDef.Methods) { cancellationToken.ThrowIfCancellationRequested(); @@ -185,10 +187,10 @@ namespace ICSharpCode.Decompiler.CSharp if (method.IsStatic || !method.IsConstructor) continue; var m = method.Specialize(subst); - var body = DecompileBody(method, allTransforms: method.Accessibility == Accessibility.Public && !recordTypeDef.IsRecord); + var body = DecompileBody(method, allTransforms: method.Accessibility >= minAccessibility && !recordTypeDef.IsRecord); if (body == null) continue; - if (primaryCtor == null && method.Accessibility == Accessibility.Public && IsPrimaryConstructorBody(m, method, body)) + if (primaryCtor == null && method.Accessibility >= minAccessibility && IsPrimaryConstructorBody(m, method, body)) { primaryCtor = method; } @@ -199,7 +201,7 @@ namespace ICSharpCode.Decompiler.CSharp // if a chained to a constructor of a different type, give up if (calledCtor != null && calledCtor.DeclaringTypeDefinition?.Equals(method.DeclaringTypeDefinition) != true) return null; - ctorCallChain[method] = calledCtor; + ctorCallChain[method] = (IMethod)calledCtor?.MemberDefinition; } if (primaryCtor == null) { @@ -272,6 +274,10 @@ namespace ICSharpCode.Decompiler.CSharp } if (offset < method.Parameters.Count) { + if (primaryCtorParameterToAutoPropertyOrBackingField.ContainsKey(unspecializedMethod.Parameters[offset])) + return false; + if (autoPropertyOrBackingFieldToPrimaryCtorParameter.ContainsKey(backingMember)) + return false; primaryCtorParameterToAutoPropertyOrBackingField.Add(unspecializedMethod.Parameters[offset], backingMember); autoPropertyOrBackingFieldToPrimaryCtorParameter.Add(backingMember, unspecializedMethod.Parameters[offset]); } diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs index ee2270660..de3b59496 100644 --- a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs +++ b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs @@ -264,7 +264,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms { bool referencesParameter = initializer.Annotations.OfType().Any(inst => inst.Descendants .OfType() - .Any(inst => inst.Variable.Kind == VariableKind.Parameter)); + .Any(inst => inst.Variable.Kind == VariableKind.Parameter && ctorMethodDef.Equals(inst.Variable.Function.Method))); if (referencesParameter) break; } @@ -287,44 +287,33 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } if (allSame) { - foreach (var ctor in instanceCtorsNotChainingWithThis) - ctor.Body.First().Remove(); - if (fieldOrPropertyOrEventDecl == null) - continue; - if (ctorIsUnsafe && IntroduceUnsafeModifier.IsUnsafe(initializer)) + if (fieldOrPropertyOrEventDecl is PropertyDeclaration { Initializer.IsNull: true } pd) { - fieldOrPropertyOrEventDecl.Modifiers |= Modifiers.Unsafe; + pd.Initializer = initializer.Detach(); } - if (fieldOrPropertyOrEventDecl is PropertyDeclaration pd) + else if (fieldOrPropertyOrEventDecl is FieldDeclaration or EventDeclaration) { - pd.Initializer = initializer.Detach(); + var vdecl = fieldOrPropertyOrEventDecl.GetChildrenByRole(Roles.Variable).Single(); + if (vdecl.Initializer.IsNull) + { + vdecl.Initializer = initializer.Detach(); + } + else + { + break; + } } - else + if (ctorIsUnsafe && fieldOrPropertyOrEventDecl != null && IntroduceUnsafeModifier.IsUnsafe(initializer)) { - fieldOrPropertyOrEventDecl.GetChildrenByRole(Roles.Variable).Single().Initializer = initializer.Detach(); + fieldOrPropertyOrEventDecl.Modifiers |= Modifiers.Unsafe; } + foreach (var ctor in instanceCtorsNotChainingWithThis) + ctor.Body.First().Remove(); } } while (allSame); } } - bool IsPropertyDeclaredByPrimaryCtor(IMember m, RecordDecompiler record) - { - if (record == null) - return false; - switch (m) - { - case IProperty p: - return record.IsPropertyDeclaredByPrimaryConstructor(p); - case IField f: - return record.PrimaryConstructor != null; - case IEvent e: - return record.PrimaryConstructor != null; - default: - return false; - } - } - void RemoveSingleEmptyConstructor(IEnumerable members, ITypeDefinition contextTypeDefinition) { // if we're outside of a type definition skip this altogether @@ -390,9 +379,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms continue; } var fieldOrPropertyDecl = members.FirstOrDefault(f => f.GetSymbol() == fieldOrProperty) as EntityDeclaration; - if (fieldOrPropertyDecl == null) - break; - if (ctorIsUnsafe && IntroduceUnsafeModifier.IsUnsafe(assignment.Right)) + if (ctorIsUnsafe && fieldOrPropertyDecl != null && IntroduceUnsafeModifier.IsUnsafe(assignment.Right)) { fieldOrPropertyDecl.Modifiers |= Modifiers.Unsafe; } @@ -416,7 +403,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms } } } - else if (fieldOrPropertyDecl is PropertyDeclaration { IsAutomaticProperty: true } pd) + else if (fieldOrPropertyDecl is PropertyDeclaration { IsAutomaticProperty: true, Initializer.IsNull: true } pd) { pd.Initializer = assignment.Right.Detach(); }