diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs
index 6487fe3c7..82c38d720 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs
@@ -1,7 +1,16 @@
-namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
+using System;
+
+namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public record Base(string A);
+ public record CopyCtor(string A)
+ {
+ protected CopyCtor(CopyCtor _)
+ {
+ }
+ }
+
public record Derived(int B) : Base(B.ToString());
public record Empty;
@@ -15,6 +24,12 @@
public string S = "abc";
}
+ public record Interface(int B) : IRecord;
+
+ public interface IRecord
+ {
+ }
+
public record Pair
{
public A First { get; init; }
@@ -24,13 +39,17 @@
public record PairWithPrimaryCtor(A First, B Second);
public record PrimaryCtor(int A, string B);
+ public record PrimaryCtorWithAttribute([RecordTest("param")] [property: RecordTest("property")][field: RecordTest("field")] int a);
public record PrimaryCtorWithField(int A, string B)
{
- public double C;
+ public double C = 1.0;
+ public string D = A + B;
}
+ public record PrimaryCtorWithInParameter(in int A, in string B);
public record PrimaryCtorWithProperty(int A, string B)
{
- public double C { get; init; }
+ public double C { get; init; } = 1.0;
+ public string D { get; } = A + B;
}
public record Properties
@@ -48,6 +67,18 @@
}
}
+ [AttributeUsage(AttributeTargets.All)]
+ public class RecordTestAttribute : Attribute
+ {
+ public RecordTestAttribute(string name)
+ {
+ }
+ }
+
+ public sealed record Sealed(string A);
+
+ public sealed record SealedDerived(int B) : Base(B.ToString());
+
public class WithExpressionTests
{
public Fields Test(Fields input)
diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 42144c5e6..fc7526041 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -1227,7 +1227,31 @@ namespace ICSharpCode.Decompiler.CSharp
if (recordDecompiler?.PrimaryConstructor != null)
{
foreach (var p in recordDecompiler.PrimaryConstructor.Parameters)
- typeDecl.PrimaryConstructorParameters.Add(typeSystemAstBuilder.ConvertParameter(p));
+ {
+ ParameterDeclaration pd = typeSystemAstBuilder.ConvertParameter(p);
+ (IProperty prop, IField field) = recordDecompiler.GetPropertyInfoByPrimaryConstructorParameter(p);
+ Syntax.Attribute[] attributes = prop.GetAttributes().Select(attr => typeSystemAstBuilder.ConvertAttribute(attr)).ToArray();
+ if (attributes.Length > 0)
+ {
+ var section = new AttributeSection {
+ AttributeTarget = "property"
+ };
+ section.Attributes.AddRange(attributes);
+ pd.Attributes.Add(section);
+ }
+ attributes = field.GetAttributes()
+ .Where(a => !PatternStatementTransform.attributeTypesToRemoveFromAutoProperties.Contains(a.AttributeType.FullName))
+ .Select(attr => typeSystemAstBuilder.ConvertAttribute(attr)).ToArray();
+ if (attributes.Length > 0)
+ {
+ var section = new AttributeSection {
+ AttributeTarget = "field"
+ };
+ section.Attributes.AddRange(attributes);
+ pd.Attributes.Add(section);
+ }
+ typeDecl.PrimaryConstructorParameters.Add(pd);
+ }
}
foreach (var type in typeDef.NestedTypes)
diff --git a/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs b/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs
index aafccba14..82494e34c 100644
--- a/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs
@@ -37,6 +37,8 @@ namespace ICSharpCode.Decompiler.CSharp
readonly CancellationToken cancellationToken;
readonly List orderedMembers;
readonly bool isInheritedRecord;
+ readonly bool isStruct;
+ readonly bool isSealed;
readonly IMethod primaryCtor;
readonly IType baseClass;
readonly Dictionary backingFieldToAutoProperty = new Dictionary();
@@ -51,7 +53,9 @@ namespace ICSharpCode.Decompiler.CSharp
this.settings = settings;
this.cancellationToken = cancellationToken;
this.baseClass = recordTypeDef.DirectBaseTypes.FirstOrDefault(b => b.Kind == TypeKind.Class);
- this.isInheritedRecord = !baseClass.IsKnownType(KnownTypeCode.Object);
+ this.isStruct = baseClass.IsKnownType(KnownTypeCode.ValueType);
+ this.isInheritedRecord = !isStruct && !baseClass.IsKnownType(KnownTypeCode.Object);
+ this.isSealed = recordTypeDef.IsSealed;
DetectAutomaticProperties();
this.orderedMembers = DetectMemberOrder(recordTypeDef, backingFieldToAutoProperty);
this.primaryCtor = DetectPrimaryConstructor();
@@ -164,13 +168,15 @@ namespace ICSharpCode.Decompiler.CSharp
if (method.IsStatic || !method.IsConstructor)
continue;
var m = method.Specialize(subst);
- if (IsPrimaryConstructor(m))
+ if (IsPrimaryConstructor(m, method))
return method;
+ primaryCtorParameterToAutoProperty.Clear();
+ autoPropertyToPrimaryCtorParameter.Clear();
}
return null;
- bool IsPrimaryConstructor(IMethod method)
+ bool IsPrimaryConstructor(IMethod method, IMethod unspecializedMethod)
{
Debug.Assert(method.IsConstructor);
var body = DecompileBody(method);
@@ -180,28 +186,37 @@ namespace ICSharpCode.Decompiler.CSharp
if (method.Parameters.Count == 0)
return false;
- if (body.Instructions.Count != method.Parameters.Count + 2)
+ var addonInst = isStruct ? 1 : 2;
+ if (body.Instructions.Count < method.Parameters.Count + addonInst)
return false;
- for (int i = 0; i < body.Instructions.Count - 2; i++)
+ for (int i = 0; i < method.Parameters.Count; i++)
{
if (!body.Instructions[i].MatchStFld(out var target, out var field, out var valueInst))
return false;
if (!target.MatchLdThis())
return false;
+ if (method.Parameters[i].IsIn)
+ {
+ if (!valueInst.MatchLdObj(out valueInst, out _))
+ return false;
+ }
if (!valueInst.MatchLdLoc(out var value))
return false;
if (!(value.Kind == VariableKind.Parameter && value.Index == i))
return false;
if (!backingFieldToAutoProperty.TryGetValue(field, out var property))
return false;
- primaryCtorParameterToAutoProperty.Add(method.Parameters[i], property);
- autoPropertyToPrimaryCtorParameter.Add(property, method.Parameters[i]);
+ primaryCtorParameterToAutoProperty.Add(unspecializedMethod.Parameters[i], property);
+ autoPropertyToPrimaryCtorParameter.Add(property, unspecializedMethod.Parameters[i]);
}
- var baseCtorCall = body.Instructions.SecondToLastOrDefault() as CallInstruction;
- if (baseCtorCall == null)
- return false;
+ if (!isStruct)
+ {
+ var baseCtorCall = body.Instructions.SecondToLastOrDefault() as CallInstruction;
+ if (baseCtorCall == null)
+ return false;
+ }
var returnInst = body.Instructions.LastOrDefault();
return returnInst != null && returnInst.MatchReturn(out var retVal) && retVal.MatchNop();
@@ -233,6 +248,8 @@ namespace ICSharpCode.Decompiler.CSharp
///
public IMethod PrimaryConstructor => primaryCtor;
+ public bool IsInheritedRecord => isInheritedRecord;
+
bool IsRecordType(IType type)
{
return type.GetDefinition() == recordTypeDef
@@ -244,13 +261,9 @@ namespace ICSharpCode.Decompiler.CSharp
///
public bool MethodIsGenerated(IMethod method)
{
- if (method.IsConstructor)
+ if (IsCopyConstructor(method))
{
- if (method.Parameters.Count == 1
- && IsRecordType(method.Parameters[0].Type))
- {
- return IsGeneratedCopyConstructor(method);
- }
+ return IsGeneratedCopyConstructor(method);
}
switch (method.Name)
@@ -309,7 +322,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
switch (property.Name)
{
- case "EqualityContract":
+ case "EqualityContract" when !isStruct:
return IsGeneratedEqualityContract(property);
default:
return IsPropertyDeclaredByPrimaryConstructor(property);
@@ -323,6 +336,24 @@ namespace ICSharpCode.Decompiler.CSharp
&& autoPropertyToPrimaryCtorParameter.ContainsKey((IProperty)property.Specialize(subst));
}
+ internal (IProperty prop, IField field) GetPropertyInfoByPrimaryConstructorParameter(IParameter parameter)
+ {
+ var prop = primaryCtorParameterToAutoProperty[parameter];
+ return (prop, autoPropertyToBackingField[prop]);
+ }
+
+ public bool IsCopyConstructor(IMethod method)
+ {
+ if (method == null)
+ return false;
+
+ Debug.Assert(method.DeclaringTypeDefinition == recordTypeDef);
+
+ return method.IsConstructor
+ && method.Parameters.Count == 1
+ && IsRecordType(method.Parameters[0].Type);
+ }
+
private bool IsGeneratedCopyConstructor(IMethod method)
{
/*
@@ -333,7 +364,7 @@ namespace ICSharpCode.Decompiler.CSharp
Debug.Assert(method.IsConstructor && method.Parameters.Count == 1);
if (method.GetAttributes().Any() || method.GetReturnTypeAttributes().Any())
return false;
- if (method.Accessibility != Accessibility.Protected)
+ if (method.Accessibility != Accessibility.Protected && (!isSealed || method.Accessibility != Accessibility.Private))
return false;
if (orderedMembers == null)
return false;
@@ -393,10 +424,10 @@ namespace ICSharpCode.Decompiler.CSharp
// protected virtual Type EqualityContract {
// [CompilerGenerated] get => typeof(R);
// }
- Debug.Assert(property.Name == "EqualityContract");
- if (property.Accessibility != Accessibility.Protected)
+ Debug.Assert(!isStruct && property.Name == "EqualityContract");
+ if (property.Accessibility != Accessibility.Protected && (!isSealed || property.Accessibility != Accessibility.Private))
return false;
- if (!(property.IsVirtual || property.IsOverride))
+ if (!(isSealed || property.IsVirtual || property.IsOverride))
return false;
if (property.IsSealed)
return false;
@@ -428,11 +459,11 @@ namespace ICSharpCode.Decompiler.CSharp
Debug.Assert(method.Name == "PrintMembers");
if (method.Parameters.Count != 1)
return false;
- if (!method.IsOverridable)
+ if (!isSealed && !method.IsOverridable)
return false;
if (method.GetAttributes().Any() || method.GetReturnTypeAttributes().Any())
return false;
- if (method.Accessibility != Accessibility.Protected)
+ if (method.Accessibility != Accessibility.Protected && (!isSealed || method.Accessibility != Accessibility.Private))
return false;
if (orderedMembers == null)
return false;
@@ -444,6 +475,15 @@ namespace ICSharpCode.Decompiler.CSharp
if (builder.Type.ReflectionName != "System.Text.StringBuilder")
return false;
int pos = 0;
+ //Roslyn 4.0.0-3.final start to insert an call to RuntimeHelpers.EnsureSufficientExecutionStack()
+ if (!isStruct && !isInheritedRecord && body.Instructions[pos] is Call
+ {
+ Arguments: { Count: 0 },
+ Method: { Name: "EnsureSufficientExecutionStack", DeclaringType: { Namespace: "System.Runtime.CompilerServices", Name: "RuntimeHelpers" } }
+ })
+ {
+ pos++;
+ }
if (isInheritedRecord)
{
// Special case: inherited record adding no new members
@@ -551,7 +591,7 @@ namespace ICSharpCode.Decompiler.CSharp
{
return false; // static fields/properties are not printed
}
- if (member.Name == "EqualityContract")
+ if (!isStruct && member.Name == "EqualityContract")
{
return false; // EqualityContract is never printed
}
@@ -617,7 +657,8 @@ namespace ICSharpCode.Decompiler.CSharp
// if (callvirt PrintMembers(ldloc this, ldloc stringBuilder)) { trueInst }
if (!body.Instructions[3].MatchIfInstruction(out var condition, out var trueInst))
return true;
- if (!(condition is CallVirt { Method: { Name: "PrintMembers" } } printMembersCall))
+ if (!((condition is CallInstruction { Method: { Name: "PrintMembers" } } printMembersCall) &&
+ (condition is CallVirt || (isSealed && condition is Call))))
return false;
if (printMembersCall.Arguments.Count != 2)
return false;
@@ -640,21 +681,20 @@ namespace ICSharpCode.Decompiler.CSharp
return false;
return toStringCall.Arguments[0].MatchLdLoc(stringBuilder);
- bool MatchAppendCall(ILInstruction inst, out string val)
+ bool MatchAppendCallWithValue(ILInstruction inst, string val)
{
- val = null;
if (!(inst is CallVirt { Method: { Name: "Append" } } call))
return false;
if (call.Arguments.Count != 2)
return false;
if (!call.Arguments[0].MatchLdLoc(stringBuilder))
return false;
- return call.Arguments[1].MatchLdStr(out val);
- }
-
- bool MatchAppendCallWithValue(ILInstruction inst, string val)
- {
- return MatchAppendCall(inst, out string tmp) && tmp == val;
+ //Roslyn 4.0.0-3.final start to use char for 1 length string
+ if (call.Method.Parameters[0].Type.IsKnownType(KnownTypeCode.Char))
+ {
+ return val != null && val.Length == 1 && call.Arguments[1].MatchLdcI4(val[0]);
+ }
+ return call.Arguments[1].MatchLdStr(out string val1) && val1 == val;
}
}
@@ -670,7 +710,7 @@ namespace ICSharpCode.Decompiler.CSharp
Debug.Assert(method.Name == "Equals" && method.Parameters.Count == 1);
if (method.Parameters.Count != 1)
return false;
- if (!method.IsOverridable)
+ if (!isSealed && !method.IsOverridable)
return false;
if (method.GetAttributes().Any() || method.GetReturnTypeAttributes().Any())
return false;
@@ -698,58 +738,61 @@ namespace ICSharpCode.Decompiler.CSharp
var conditions = UnpackLogicAndChain(returnValue);
Debug.Assert(conditions.Count >= 1);
int pos = 0;
- if (isInheritedRecord)
- {
- // call BaseClass::Equals(ldloc this, ldloc other)
- if (pos >= conditions.Count)
- return false;
- if (!(conditions[pos] is Call { Method: { Name: "Equals" } } call))
- return false;
- if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(call.Method.DeclaringType, baseClass))
- return false;
- if (call.Arguments.Count != 2)
- return false;
- if (!call.Arguments[0].MatchLdThis())
- return false;
- if (!call.Arguments[1].MatchLdLoc(other))
- return false;
- pos++;
- }
- else
+ if (!isStruct)
{
- // comp.o(ldloc other != ldnull)
- if (pos >= conditions.Count)
- return false;
- if (!conditions[pos].MatchCompNotEqualsNull(out var arg))
- return false;
- if (!arg.MatchLdLoc(other))
- return false;
- pos++;
- // call op_Equality(callvirt get_EqualityContract(ldloc this), callvirt get_EqualityContract(ldloc other))
- // Special-cased because Roslyn isn't using EqualityComparer here.
- if (pos >= conditions.Count)
- return false;
- if (!(conditions[pos] is Call { Method: { IsOperator: true, Name: "op_Equality" } } opEqualityCall))
- return false;
- if (!opEqualityCall.Method.DeclaringType.IsKnownType(KnownTypeCode.Type))
- return false;
- if (opEqualityCall.Arguments.Count != 2)
- return false;
- if (!MatchGetEqualityContract(opEqualityCall.Arguments[0], out var target1))
- return false;
- if (!MatchGetEqualityContract(opEqualityCall.Arguments[1], out var target2))
- return false;
- if (!target1.MatchLdThis())
- return false;
- if (!target2.MatchLdLoc(other))
- return false;
- pos++;
+ if (isInheritedRecord)
+ {
+ // call BaseClass::Equals(ldloc this, ldloc other)
+ if (pos >= conditions.Count)
+ return false;
+ if (!(conditions[pos] is Call { Method: { Name: "Equals" } } call))
+ return false;
+ if (!NormalizeTypeVisitor.TypeErasure.EquivalentTypes(call.Method.DeclaringType, baseClass))
+ return false;
+ if (call.Arguments.Count != 2)
+ return false;
+ if (!call.Arguments[0].MatchLdThis())
+ return false;
+ if (!call.Arguments[1].MatchLdLoc(other))
+ return false;
+ pos++;
+ }
+ else
+ {
+ // comp.o(ldloc other != ldnull)
+ if (pos >= conditions.Count)
+ return false;
+ if (!conditions[pos].MatchCompNotEqualsNull(out var arg))
+ return false;
+ if (!arg.MatchLdLoc(other))
+ return false;
+ pos++;
+ // call op_Equality(callvirt get_EqualityContract(ldloc this), callvirt get_EqualityContract(ldloc other))
+ // Special-cased because Roslyn isn't using EqualityComparer here.
+ if (pos >= conditions.Count)
+ return false;
+ if (!(conditions[pos] is Call { Method: { IsOperator: true, Name: "op_Equality" } } opEqualityCall))
+ return false;
+ if (!opEqualityCall.Method.DeclaringType.IsKnownType(KnownTypeCode.Type))
+ return false;
+ if (opEqualityCall.Arguments.Count != 2)
+ return false;
+ if (!MatchGetEqualityContract(opEqualityCall.Arguments[0], out var target1))
+ return false;
+ if (!MatchGetEqualityContract(opEqualityCall.Arguments[1], out var target2))
+ return false;
+ if (!target1.MatchLdThis())
+ return false;
+ if (!target2.MatchLdLoc(other))
+ return false;
+ pos++;
+ }
}
foreach (var member in orderedMembers)
{
if (!MemberConsideredForEquality(member))
continue;
- if (member.Name == "EqualityContract")
+ if (!isStruct && member.Name == "EqualityContract")
{
continue; // already special-cased
}
@@ -771,7 +814,7 @@ namespace ICSharpCode.Decompiler.CSharp
return false;
if (!member1.Equals(member))
return false;
- if (!target2.MatchLdLoc(other))
+ if (!(isStruct ? target2.MatchLdLoca(other) : target2.MatchLdLoc(other)))
return false;
if (!member2.Equals(member))
return false;
@@ -800,10 +843,12 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
- private static bool MatchGetEqualityContract(ILInstruction inst, out ILInstruction target)
+ private bool MatchGetEqualityContract(ILInstruction inst, out ILInstruction target)
{
target = null;
- if (!(inst is CallVirt { Method: { Name: "get_EqualityContract" } } call))
+ if (!(inst is CallInstruction { Method: { Name: "get_EqualityContract" } } call))
+ return false;
+ if (!(inst is CallVirt || (isSealed && inst is Call)))
return false;
if (call.Arguments.Count != 1)
return false;
@@ -830,7 +875,7 @@ namespace ICSharpCode.Decompiler.CSharp
return false;
if (member is IProperty property)
{
- if (property.Name == "EqualityContract")
+ if (!isStruct && property.Name == "EqualityContract")
return !isInheritedRecord;
return autoPropertyToBackingField.ContainsKey(property);
}
@@ -944,7 +989,10 @@ namespace ICSharpCode.Decompiler.CSharp
if (!deconstruct.IsOut)
return false;
- if (!ctor.Type.Equals(((ByReferenceType)deconstruct.Type).ElementType))
+ IType ctorType = ctor.Type;
+ if (ctor.IsIn)
+ ctorType = ((ByReferenceType)ctorType).ElementType;
+ if (!ctorType.Equals(((ByReferenceType)deconstruct.Type).ElementType))
return false;
if (ctor.Name != deconstruct.Name)
@@ -985,14 +1033,14 @@ namespace ICSharpCode.Decompiler.CSharp
{
target = null;
member = null;
- if (inst is CallVirt
+ if (inst is CallInstruction
{
Method:
{
AccessorKind: System.Reflection.MethodSemanticsAttributes.Getter,
AccessorOwner: IProperty property
}
- } call)
+ } call && (call is CallVirt || (isSealed && call is Call)))
{
if (call.Arguments.Count != 1)
return false;
diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
index e61bbe41c..aaa032971 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/PatternStatementTransform.cs
@@ -936,7 +936,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
"System.Runtime.CompilerServices.MethodImplAttribute"
};
- static readonly string[] attributeTypesToRemoveFromAutoProperties = new[] {
+ internal static readonly string[] attributeTypesToRemoveFromAutoProperties = new[] {
"System.Runtime.CompilerServices.CompilerGeneratedAttribute",
"System.Diagnostics.DebuggerBrowsableAttribute"
};
diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs
index c285f7fa5..d694ba5eb 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs
@@ -118,7 +118,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
&& currentCtor.Equals(record.PrimaryConstructor)
&& ci.ConstructorInitializerType == ConstructorInitializerType.Base)
{
- if (constructorDeclaration.Parent is TypeDeclaration { BaseTypes: { Count: >= 1 } } typeDecl)
+ if (record.IsInheritedRecord &&
+ constructorDeclaration.Parent is TypeDeclaration { BaseTypes: { Count: >= 1 } } typeDecl)
{
var baseType = typeDecl.BaseTypes.First();
var newBaseType = new InvocationAstType();
@@ -176,6 +177,10 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (!context.DecompileRun.RecordDecompilers.TryGetValue(ctorMethodDef.DeclaringTypeDefinition, out var record))
record = null;
+ //Filter out copy constructor of records
+ if (record != null)
+ instanceCtorsNotChainingWithThis = instanceCtorsNotChainingWithThis.Where(ctor => !record.IsCopyConstructor(ctor.GetSymbol() as IMethod)).ToArray();
+
// Recognize field or property initializers:
// Translate first statement in all ctors (if all ctors have the same statement) into an initializer.
bool allSame;
@@ -201,9 +206,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (initializer.Annotation()?.Variable.Kind == IL.VariableKind.Parameter)
{
// remove record ctor parameter assignments
- if (IsPropertyDeclaredByPrimaryCtor(fieldOrPropertyOrEvent as IProperty, record))
- initializer.Remove();
- else
+ if (!IsPropertyDeclaredByPrimaryCtor(fieldOrPropertyOrEvent as IProperty, record))
break;
}
else