diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs
index 82c38d720..30c0402da 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs
@@ -1,126 +1,231 @@
using System;
+using System.Runtime.InteropServices;
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
- public record Base(string A);
-
- public record CopyCtor(string A)
+ internal class RecordClasses
{
- protected CopyCtor(CopyCtor _)
+ public record Base(string A);
+
+ public record CopyCtor(string A)
{
+ protected CopyCtor(CopyCtor _)
+ {
+ }
}
- }
- public record Derived(int B) : Base(B.ToString());
+ public record Derived(int B) : Base(B.ToString());
- public record Empty;
+ public record Empty;
- public record Fields
- {
- public int A;
- public double B = 1.0;
- public object C;
- public dynamic D;
- public string S = "abc";
- }
+ public record Fields
+ {
+ public int A;
+ public double B = 1.0;
+ public object C;
+ public dynamic D;
+ public string S = "abc";
+ }
- public record Interface(int B) : IRecord;
+ public record Interface(int B) : IRecord;
- public interface IRecord
- {
- }
+ public interface IRecord
+ {
+ }
- public record Pair
- {
- public A First { get; init; }
- public B Second { get; init; }
- }
+ public record Pair
+ {
+ public A First { get; init; }
+ public B Second { get; init; }
+ }
- public record PairWithPrimaryCtor(A First, B Second);
+ 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 = 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; } = 1.0;
- public string D { get; } = A + B;
- }
+ 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 = 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; } = 1.0;
+ public string D { get; } = A + B;
+ }
- public record Properties
- {
- public int A { get; set; }
- public int B { get; }
- public int C => 43;
- public object O { get; set; }
- public string S { get; set; }
- public dynamic D { get; set; }
+ public record Properties
+ {
+ public int A { get; set; }
+ public int B { get; }
+ public int C => 43;
+ public object O { get; set; }
+ public string S { get; set; }
+ public dynamic D { get; set; }
+
+ public Properties()
+ {
+ B = 42;
+ }
+ }
- public Properties()
+ [AttributeUsage(AttributeTargets.All)]
+ public class RecordTestAttribute : Attribute
{
- B = 42;
+ public RecordTestAttribute(string name)
+ {
+ }
}
- }
- [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)
+ {
+ return input with {
+ A = 42,
+ B = 3.141,
+ C = input
+ };
+ }
+ public Fields Test2(Fields input)
+ {
+ return input with {
+ A = 42,
+ B = 3.141,
+ C = input with {
+ A = 43
+ }
+ };
+ }
}
- }
- public sealed record Sealed(string A);
+ public abstract record WithNestedRecords
+ {
+ public record A : WithNestedRecords
+ {
+ public override string AbstractProp => "A";
+ }
+
+ public record B : WithNestedRecords
+ {
+ public override string AbstractProp => "B";
+
+ public int? Value { get; set; }
+ }
- public sealed record SealedDerived(int B) : Base(B.ToString());
+ public record DerivedGeneric : Pair where T : struct
+ {
+ public bool Flag;
+ }
- public class WithExpressionTests
+ public abstract string AbstractProp { get; }
+ }
+
+ }
+ internal class RecordStructs
{
- public Fields Test(Fields input)
+ public record struct Base(string A);
+
+ public record CopyCtor(string A)
+ {
+ protected CopyCtor(CopyCtor _)
+ {
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential, Size = 1)]
+ public record struct Empty;
+
+ public record struct Fields
{
- return input with {
- A = 42,
- B = 3.141,
- C = input
- };
+ public int A;
+ public double B;
+ public object C;
+ public dynamic D;
+ public string S;
}
- public Fields Test2(Fields input)
+
+ public record struct Interface(int B) : IRecord;
+
+ public interface IRecord
{
- return input with {
- A = 42,
- B = 3.141,
- C = input with {
- A = 43
- }
- };
}
- }
- public abstract record WithNestedRecords
- {
- public record A : WithNestedRecords
+ public record struct Pair
{
- public override string AbstractProp => "A";
+ public A First { get; init; }
+ public B Second { get; init; }
}
- public record B : WithNestedRecords
+ public record struct PairWithPrimaryCtor(A First, B Second);
+
+ public record struct PrimaryCtor(int A, string B);
+ public record struct PrimaryCtorWithAttribute([RecordTest("param")] [property: RecordTest("property")][field: RecordTest("field")] int a);
+ public record struct PrimaryCtorWithField(int A, string B)
{
- public override string AbstractProp => "B";
+ public double C = 1.0;
+ public string D = A + B;
+ }
+ public record struct PrimaryCtorWithInParameter(in int A, in string B);
+ public record struct PrimaryCtorWithProperty(int A, string B)
+ {
+ public double C { get; init; } = 1.0;
+ public string D { get; } = A + B;
+ }
- public int? Value { get; set; }
+ public record struct Properties
+ {
+ public int A { get; set; }
+ public int B { get; }
+ public int C => 43;
+ public object O { get; set; }
+ public string S { get; set; }
+ public dynamic D { get; set; }
+
+ public Properties()
+ {
+ A = 41;
+ B = 42;
+ O = null;
+ S = "Hello";
+ D = null;
+ }
}
- public record DerivedGeneric : Pair where T : struct
+ [AttributeUsage(AttributeTargets.All)]
+ public class RecordTestAttribute : Attribute
{
- public bool Flag;
+ public RecordTestAttribute(string name)
+ {
+ }
}
- public abstract string AbstractProp { get; }
+ public class WithExpressionTests
+ {
+ public Fields Test(Fields input)
+ {
+ return input with {
+ A = 42,
+ B = 3.141,
+ C = input
+ };
+ }
+ public Fields Test2(Fields input)
+ {
+ return input with {
+ A = 42,
+ B = 3.141,
+ C = input with {
+ A = 43
+ }
+ };
+ }
+ }
}
}
namespace System.Runtime.CompilerServices
diff --git a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
index 52078eb82..0e9b6ad54 100644
--- a/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
@@ -505,6 +505,7 @@ namespace ICSharpCode.Decompiler.CSharp
typeSystemAstBuilder.UseNullableSpecifierForValueTypes = settings.LiftNullables;
typeSystemAstBuilder.SupportInitAccessors = settings.InitAccessors;
typeSystemAstBuilder.SupportRecordClasses = settings.RecordClasses;
+ typeSystemAstBuilder.SupportRecordStructs = settings.RecordStructs;
return typeSystemAstBuilder;
}
@@ -1264,7 +1265,11 @@ namespace ICSharpCode.Decompiler.CSharp
// e.g. DelegateDeclaration
return entityDecl;
}
- bool isRecord = settings.RecordClasses && typeDef.IsRecord;
+ bool isRecord = typeDef.Kind switch {
+ TypeKind.Class => settings.RecordClasses && typeDef.IsRecord,
+ TypeKind.Struct => settings.RecordStructs && typeDef.IsRecord,
+ _ => false,
+ };
RecordDecompiler recordDecompiler = isRecord ? new RecordDecompiler(typeSystem, typeDef, settings, CancellationToken) : null;
if (recordDecompiler != null)
decompileRun.RecordDecompilers.Add(typeDef, recordDecompiler);
@@ -1311,7 +1316,7 @@ namespace ICSharpCode.Decompiler.CSharp
IEnumerable allOrderedMembers = RequiresNativeOrdering(typeDef) ? GetMembersWithNativeOrdering(typeDef) :
fieldsAndProperties.Concat(typeDef.Events).Concat(typeDef.Methods);
- var allOrderedEntities = typeDef.NestedTypes.Concat(allOrderedMembers);
+ var allOrderedEntities = typeDef.NestedTypes.Concat(allOrderedMembers).ToArray();
// Decompile members that are not compiler-generated.
foreach (var entity in allOrderedEntities)
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
index 93408ef5f..839fdbb33 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpAmbience.cs
@@ -232,6 +232,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
astBuilder.UseNullableSpecifierForValueTypes = (ConversionFlags & ConversionFlags.UseNullableSpecifierForValueTypes) != 0;
astBuilder.SupportInitAccessors = (ConversionFlags & ConversionFlags.SupportInitAccessors) != 0;
astBuilder.SupportRecordClasses = (ConversionFlags & ConversionFlags.SupportRecordClasses) != 0;
+ astBuilder.SupportRecordStructs = (ConversionFlags & ConversionFlags.SupportRecordStructs) != 0;
return astBuilder;
}
diff --git a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
index 30779a0e3..8f1bcc552 100644
--- a/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
+++ b/ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
@@ -1579,6 +1579,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
WriteKeyword(Roles.RecordKeyword);
braceStyle = policy.ClassBraceStyle;
break;
+ case ClassType.RecordStruct:
+ WriteKeyword(Roles.RecordStructKeyword);
+ WriteKeyword(Roles.StructKeyword);
+ braceStyle = policy.StructBraceStyle;
+ break;
default:
WriteKeyword(Roles.ClassKeyword);
braceStyle = policy.ClassBraceStyle;
@@ -1602,7 +1607,7 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{
constraint.AcceptVisitor(this);
}
- if (typeDeclaration.ClassType == ClassType.RecordClass && typeDeclaration.Members.Count == 0)
+ if (typeDeclaration.ClassType is (ClassType.RecordClass or ClassType.RecordStruct) && typeDeclaration.Members.Count == 0)
{
Semicolon();
}
diff --git a/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs b/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs
index 82494e34c..7bf416ab0 100644
--- a/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs
+++ b/ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs
@@ -721,6 +721,9 @@ namespace ICSharpCode.Decompiler.CSharp
return false;
if (!body.Instructions[0].MatchReturn(out var returnValue))
return false;
+ // special case for empty record struct; always returns true;
+ if (returnValue.MatchLdcI4(1))
+ return true;
var variables = body.Ancestors.OfType().Single().Variables;
var other = variables.Single(v => v.Kind == VariableKind.Parameter && v.Index == 0);
Debug.Assert(IsRecordType(other.Type));
@@ -908,6 +911,9 @@ namespace ICSharpCode.Decompiler.CSharp
return false;
if (!body.Instructions[0].MatchReturn(out var returnValue))
return false;
+ // special case for empty record struct; always returns false;
+ if (returnValue.MatchLdcI4(0))
+ return true;
var hashedMembers = new List();
bool foundBaseClassHash = false;
if (!Visit(returnValue))
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/TypeDeclaration.cs b/ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/TypeDeclaration.cs
index baec135cf..a60d12843 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/TypeDeclaration.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/TypeDeclaration.cs
@@ -35,9 +35,13 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
Interface,
Enum,
///
- /// C# 9 'record'
+ /// C# 9 'record class'
///
RecordClass,
+ ///
+ /// C# 10 'record struct'
+ ///
+ RecordStruct,
}
///
@@ -62,6 +66,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
case ClassType.Class:
return GetChildByRole(Roles.ClassKeyword);
case ClassType.Struct:
+ case ClassType.RecordStruct:
return GetChildByRole(Roles.StructKeyword);
case ClassType.Interface:
return GetChildByRole(Roles.InterfaceKeyword);
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/Roles.cs b/ICSharpCode.Decompiler/CSharp/Syntax/Roles.cs
index 076cf9411..88b92c5d5 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/Roles.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/Roles.cs
@@ -89,6 +89,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
public static readonly TokenRole StructKeyword = new TokenRole("struct");
public static readonly TokenRole ClassKeyword = new TokenRole("class");
public static readonly TokenRole RecordKeyword = new TokenRole("record");
+ public static readonly TokenRole RecordStructKeyword = new TokenRole("record");
}
}
diff --git a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
index d37877ec2..9106e7850 100644
--- a/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
+++ b/ICSharpCode.Decompiler/CSharp/Syntax/TypeSystemAstBuilder.cs
@@ -219,6 +219,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
/// Controls whether C# 9 "record" class types are supported.
///
public bool SupportRecordClasses { get; set; }
+
+ ///
+ /// Controls whether C# 10 "record" struct types are supported.
+ ///
+ public bool SupportRecordStructs { get; set; }
#endregion
#region Convert Type
@@ -1775,6 +1780,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
modifiers |= Modifiers.Ref;
}
}
+ if (SupportRecordStructs && typeDefinition.IsRecord)
+ {
+ classType = ClassType.RecordStruct;
+ }
break;
case TypeKind.Enum:
classType = ClassType.Enum;
diff --git a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs
index d8d824a98..48ebc81cf 100644
--- a/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs
+++ b/ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs
@@ -61,67 +61,68 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public override void VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration)
{
- if (!(constructorDeclaration.Body.Statements.FirstOrDefault() is ExpressionStatement stmt))
- return;
var currentCtor = (IMethod)constructorDeclaration.GetSymbol();
- ConstructorInitializer ci;
- switch (stmt.Expression)
+ ConstructorInitializer ci = null;
+ if (constructorDeclaration.Body.Statements.FirstOrDefault() is ExpressionStatement stmt)
{
- // Pattern for reference types:
- // this..ctor(...);
- case InvocationExpression invocation:
- if (!(invocation.Target is MemberReferenceExpression mre) || mre.MemberName != ".ctor")
- return;
- if (!(invocation.GetSymbol() is IMethod ctor && ctor.IsConstructor))
- return;
- ci = new ConstructorInitializer();
- var target = mre.Target;
- // Ignore casts, those might be added if references are missing.
- if (target is CastExpression cast)
- target = cast.Expression;
- if (target is ThisReferenceExpression or BaseReferenceExpression)
- {
- if (ctor.DeclaringTypeDefinition == currentCtor.DeclaringTypeDefinition)
+ switch (stmt.Expression)
+ {
+ // Pattern for reference types:
+ // this..ctor(...);
+ case InvocationExpression invocation:
+ if (!(invocation.Target is MemberReferenceExpression mre) || mre.MemberName != ".ctor")
+ return;
+ if (!(invocation.GetSymbol() is IMethod ctor && ctor.IsConstructor))
+ return;
+ ci = new ConstructorInitializer();
+ var target = mre.Target;
+ // Ignore casts, those might be added if references are missing.
+ if (target is CastExpression cast)
+ target = cast.Expression;
+ if (target is ThisReferenceExpression or BaseReferenceExpression)
+ {
+ if (ctor.DeclaringTypeDefinition == currentCtor.DeclaringTypeDefinition)
+ ci.ConstructorInitializerType = ConstructorInitializerType.This;
+ else
+ ci.ConstructorInitializerType = ConstructorInitializerType.Base;
+ }
+ else
+ return;
+ // Move arguments from invocation to initializer:
+ invocation.Arguments.MoveTo(ci.Arguments);
+ // Add the initializer: (unless it is the default 'base()')
+ if (!(ci.ConstructorInitializerType == ConstructorInitializerType.Base && ci.Arguments.Count == 0))
+ constructorDeclaration.Initializer = ci.CopyAnnotationsFrom(invocation);
+ // Remove the statement:
+ stmt.Remove();
+ break;
+ // Pattern for value types:
+ // this = new TSelf(...);
+ case AssignmentExpression assignment:
+ if (!(assignment.Right is ObjectCreateExpression oce && oce.GetSymbol() is IMethod ctor2 && ctor2.DeclaringTypeDefinition == currentCtor.DeclaringTypeDefinition))
+ return;
+ ci = new ConstructorInitializer();
+ if (assignment.Left is ThisReferenceExpression)
ci.ConstructorInitializerType = ConstructorInitializerType.This;
else
- ci.ConstructorInitializerType = ConstructorInitializerType.Base;
- }
- else
- return;
- // Move arguments from invocation to initializer:
- invocation.Arguments.MoveTo(ci.Arguments);
- // Add the initializer: (unless it is the default 'base()')
- if (!(ci.ConstructorInitializerType == ConstructorInitializerType.Base && ci.Arguments.Count == 0))
- constructorDeclaration.Initializer = ci.CopyAnnotationsFrom(invocation);
- // Remove the statement:
- stmt.Remove();
- break;
- // Pattern for value types:
- // this = new TSelf(...);
- case AssignmentExpression assignment:
- if (!(assignment.Right is ObjectCreateExpression oce && oce.GetSymbol() is IMethod ctor2 && ctor2.DeclaringTypeDefinition == currentCtor.DeclaringTypeDefinition))
- return;
- ci = new ConstructorInitializer();
- if (assignment.Left is ThisReferenceExpression)
- ci.ConstructorInitializerType = ConstructorInitializerType.This;
- else
+ return;
+ // Move arguments from invocation to initializer:
+ oce.Arguments.MoveTo(ci.Arguments);
+ // Add the initializer: (unless it is the default 'base()')
+ if (!(ci.ConstructorInitializerType == ConstructorInitializerType.Base && ci.Arguments.Count == 0))
+ constructorDeclaration.Initializer = ci.CopyAnnotationsFrom(oce);
+ // Remove the statement:
+ stmt.Remove();
+ break;
+ default:
return;
- // Move arguments from invocation to initializer:
- oce.Arguments.MoveTo(ci.Arguments);
- // Add the initializer: (unless it is the default 'base()')
- if (!(ci.ConstructorInitializerType == ConstructorInitializerType.Base && ci.Arguments.Count == 0))
- constructorDeclaration.Initializer = ci.CopyAnnotationsFrom(oce);
- // Remove the statement:
- stmt.Remove();
- break;
- default:
- return;
+ }
}
if (context.DecompileRun.RecordDecompilers.TryGetValue(currentCtor.DeclaringTypeDefinition, out var record)
- && currentCtor.Equals(record.PrimaryConstructor)
- && ci.ConstructorInitializerType == ConstructorInitializerType.Base)
+ && currentCtor.Equals(record.PrimaryConstructor))
{
if (record.IsInheritedRecord &&
+ ci?.ConstructorInitializerType == ConstructorInitializerType.Base &&
constructorDeclaration.Parent is TypeDeclaration { BaseTypes: { Count: >= 1 } } typeDecl)
{
var baseType = typeDecl.BaseTypes.First();
@@ -172,7 +173,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (instanceCtorsNotChainingWithThis.Length > 0)
{
var ctorMethodDef = instanceCtorsNotChainingWithThis[0].GetSymbol() as IMethod;
- if (ctorMethodDef != null && ctorMethodDef.DeclaringType.IsReferenceType == false)
+ ITypeDefinition declaringTypeDefinition = ctorMethodDef?.DeclaringTypeDefinition;
+ if (ctorMethodDef != null && declaringTypeDefinition?.IsReferenceType == false && !declaringTypeDefinition.IsRecord)
return;
bool ctorIsUnsafe = instanceCtorsNotChainingWithThis.All(c => c.HasModifier(Modifiers.Unsafe));
@@ -180,13 +182,14 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (!context.DecompileRun.RecordDecompilers.TryGetValue(ctorMethodDef.DeclaringTypeDefinition, out var record))
record = null;
- //Filter out copy constructor of records
+ // 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;
+ bool isPrimaryCtor = declaringTypeDefinition.IsReferenceType == true && declaringTypeDefinition.IsRecord;
do
{
Match m = fieldInitializerPattern.Match(instanceCtorsNotChainingWithThis[0].Body.FirstOrDefault());
@@ -211,11 +214,12 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
// remove record ctor parameter assignments
if (!IsPropertyDeclaredByPrimaryCtor(fieldOrPropertyOrEvent as IProperty, record))
break;
+ isPrimaryCtor = true;
}
else
{
// cannot transform if member is not found
- if (fieldOrPropertyOrEventDecl == null)
+ if (fieldOrPropertyOrEventDecl == null || !isPrimaryCtor)
break;
}
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index 9ae621a4c..e4316ce1f 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -145,6 +145,7 @@ namespace ICSharpCode.Decompiler
if (languageVersion < CSharp.LanguageVersion.CSharp10_0)
{
fileScopedNamespaces = false;
+ recordStructs = false;
}
if (languageVersion < CSharp.LanguageVersion.CSharp11_0)
{
@@ -156,7 +157,7 @@ namespace ICSharpCode.Decompiler
{
if (parameterNullCheck)
return CSharp.LanguageVersion.CSharp11_0;
- if (fileScopedNamespaces)
+ if (fileScopedNamespaces || recordStructs)
return CSharp.LanguageVersion.CSharp10_0;
if (nativeIntegers || initAccessors || functionPointers || forEachWithGetEnumeratorExtension
|| recordClasses || withExpressions || usePrimaryConstructorSyntax || covariantReturns)
@@ -262,6 +263,24 @@ namespace ICSharpCode.Decompiler
}
}
+ bool recordStructs = true;
+
+ ///
+ /// Use C# 10 record structs.
+ ///
+ [Category("C# 10.0 / VS 2022")]
+ [Description("DecompilerSettings.RecordStructs")]
+ public bool RecordStructs {
+ get { return recordStructs; }
+ set {
+ if (recordStructs != value)
+ {
+ recordStructs = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
bool withExpressions = true;
///
diff --git a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
index 03e5e2176..c77ebf855 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/TransformCollectionAndObjectInitializers.cs
@@ -105,6 +105,13 @@ namespace ICSharpCode.Decompiler.IL.Transforms
initInst = ci.Arguments.Single();
break;
default:
+ var typeDef = v.Type.GetDefinition();
+ if (context.Settings.WithExpressions && typeDef?.IsReferenceType == false && typeDef.IsRecord)
+ {
+ instType = v.Type;
+ blockKind = BlockKind.WithInitializer;
+ break;
+ }
return false;
}
int initializerItemsCount = 0;
diff --git a/ICSharpCode.Decompiler/Output/IAmbience.cs b/ICSharpCode.Decompiler/Output/IAmbience.cs
index 4dc853247..d496204b7 100644
--- a/ICSharpCode.Decompiler/Output/IAmbience.cs
+++ b/ICSharpCode.Decompiler/Output/IAmbience.cs
@@ -105,6 +105,10 @@ namespace ICSharpCode.Decompiler.Output
/// Support record classes.
///
SupportRecordClasses = 0x20000,
+ ///
+ /// Support record structs.
+ ///
+ SupportRecordStructs = 0x40000,
StandardConversionFlags = ShowParameterNames |
ShowAccessibility |
diff --git a/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs b/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs
index 8574dd681..2b0672307 100644
--- a/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/ITypeDefinition.cs
@@ -88,7 +88,7 @@ namespace ICSharpCode.Decompiler.TypeSystem
Nullability NullableContext { get; }
///
- /// Gets whether the type has the necessary members to be considered a C# 9 record type.
+ /// Gets whether the type has the necessary members to be considered a C# 9 record or C# 10 record struct type.
///
bool IsRecord { get; }
}
diff --git a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs
index e7542643a..433047091 100644
--- a/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs
+++ b/ICSharpCode.Decompiler/TypeSystem/Implementation/MetadataTypeDefinition.cs
@@ -758,13 +758,13 @@ namespace ICSharpCode.Decompiler.TypeSystem.Implementation
private bool ComputeIsRecord()
{
- if (Kind != TypeKind.Class)
+ if (Kind != TypeKind.Class && Kind != TypeKind.Struct)
return false;
var metadata = module.metadata;
var typeDef = metadata.GetTypeDefinition(handle);
bool opEquality = false;
bool opInequality = false;
- bool clone = false;
+ bool clone = Kind == TypeKind.Struct;
foreach (var methodHandle in typeDef.GetMethods())
{
var method = metadata.GetMethodDefinition(methodHandle);
diff --git a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs
index ab437bfa4..c3ca4c165 100644
--- a/ILSpy/Languages/CSharpHighlightingTokenWriter.cs
+++ b/ILSpy/Languages/CSharpHighlightingTokenWriter.cs
@@ -231,9 +231,11 @@ namespace ICSharpCode.ILSpy
case "class":
case "interface":
case "delegate":
- case "record":
color = referenceTypeKeywordsColor;
break;
+ case "record":
+ color = role == Roles.RecordKeyword ? referenceTypeKeywordsColor : valueTypeKeywordsColor;
+ break;
case "select":
case "group":
case "by":
diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs
index 38e675d4b..abdafa2ec 100644
--- a/ILSpy/Languages/CSharpLanguage.cs
+++ b/ILSpy/Languages/CSharpLanguage.cs
@@ -735,6 +735,10 @@ namespace ICSharpCode.ILSpy
{
flags |= ConversionFlags.SupportRecordClasses;
}
+ if (settings.RecordStructs)
+ {
+ flags |= ConversionFlags.SupportRecordStructs;
+ }
if (settings.InitAccessors)
{
flags |= ConversionFlags.SupportInitAccessors;