Browse Source

Add support for C# 12 primary constructors.

pull/3235/head
Siegfried Pammer 10 months ago
parent
commit
2043e5dd6f
  1. 13
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Debug.cs
  2. 14
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Release.cs
  3. 53
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs
  4. 63
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  5. 70
      ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs
  6. 53
      ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs
  7. 21
      ICSharpCode.Decompiler/DecompilerSettings.cs
  8. 10
      ILSpy/Properties/Resources.Designer.cs
  9. 3
      ILSpy/Properties/Resources.resx

13
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Debug.cs

@ -50,24 +50,17 @@ public static class Program
[Serializable] [Serializable]
[SpecialName] [SpecialName]
[CompilationMapping(SourceConstructFlags.Closure)] [CompilationMapping(SourceConstructFlags.Closure)]
internal sealed class getSeq_00405 : GeneratedSequenceBase<int> internal sealed class getSeq_00405(int pc, int current) : GeneratedSequenceBase<int>()
{ {
[DebuggerNonUserCode] [DebuggerNonUserCode]
[DebuggerBrowsable(DebuggerBrowsableState.Never)] [DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated] [CompilerGenerated]
public int pc; public int pc = pc;
[DebuggerNonUserCode] [DebuggerNonUserCode]
[DebuggerBrowsable(DebuggerBrowsableState.Never)] [DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated] [CompilerGenerated]
public int current; public int current = current;
public getSeq_00405(int pc, int current)
{
this.pc = pc;
this.current = current;
base._002Ector();
}
public override int GenerateNext(ref IEnumerable<int> next) public override int GenerateNext(ref IEnumerable<int> next)
{ {

14
ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Release.cs

@ -1,4 +1,3 @@
// C:\Users\Siegfried\Documents\Visual Studio 2017\Projects\ConsoleApp13\ConsoleApplication1\bin\Release\ConsoleApplication1.exe // C:\Users\Siegfried\Documents\Visual Studio 2017\Projects\ConsoleApp13\ConsoleApplication1\bin\Release\ConsoleApplication1.exe
// ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null // ConsoleApplication1, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
// Global type: <Module> // Global type: <Module>
@ -51,24 +50,17 @@ public static class Program
[Serializable] [Serializable]
[SpecialName] [SpecialName]
[CompilationMapping(SourceConstructFlags.Closure)] [CompilationMapping(SourceConstructFlags.Closure)]
internal sealed class getSeq_00405 : GeneratedSequenceBase<int> internal sealed class getSeq_00405(int pc, int current) : GeneratedSequenceBase<int>()
{ {
[DebuggerNonUserCode] [DebuggerNonUserCode]
[DebuggerBrowsable(DebuggerBrowsableState.Never)] [DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated] [CompilerGenerated]
public int pc; public int pc = pc;
[DebuggerNonUserCode] [DebuggerNonUserCode]
[DebuggerBrowsable(DebuggerBrowsableState.Never)] [DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated] [CompilerGenerated]
public int current; public int current = current;
public getSeq_00405(int pc, int current)
{
this.pc = pc;
this.current = current;
base._002Ector();
}
public override int GenerateNext(ref IEnumerable<int> next) public override int GenerateNext(ref IEnumerable<int> next)
{ {

53
ICSharpCode.Decompiler.Tests/TestCases/Pretty/ConstructorInitializers.cs

@ -80,5 +80,58 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public unsafe static int StaticSizeOf = sizeof(SimpleStruct); public unsafe static int StaticSizeOf = sizeof(SimpleStruct);
public unsafe int SizeOf = sizeof(SimpleStruct); public unsafe int SizeOf = sizeof(SimpleStruct);
} }
#if CS120
public class ClassWithPrimaryCtorUsingGlobalParameter(int a)
{
public void Print()
{
Console.WriteLine(a);
}
}
public class ClassWithPrimaryCtorUsingGlobalParameterAssignedToField(int a)
{
private readonly int a = a;
public void Print()
{
Console.WriteLine(a);
}
}
public class ClassWithPrimaryCtorUsingGlobalParameterAssignedToFieldAndUsedInMethod(int a)
{
#pragma warning disable CS9124 // Parameter is captured into the state of the enclosing type and its value is also used to initialize a field, property, or event.
private readonly int _a = a;
#pragma warning restore CS9124 // Parameter is captured into the state of the enclosing type and its value is also used to initialize a field, property, or event.
public void Print()
{
Console.WriteLine(a);
}
}
public class ClassWithPrimaryCtorUsingGlobalParameterAssignedToProperty(int a)
{
public int A { get; set; } = a;
public void Print()
{
Console.WriteLine(A);
}
}
public class ClassWithPrimaryCtorUsingGlobalParameterAssignedToEvent(EventHandler a)
{
public event EventHandler A = a;
public void Print()
{
Console.WriteLine(this.A);
}
}
#endif
} }
} }

63
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -337,6 +337,8 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
if (settings.AnonymousMethods && IsAnonymousMethodCacheField(field, metadata)) if (settings.AnonymousMethods && IsAnonymousMethodCacheField(field, metadata))
return true; return true;
if (settings.UsePrimaryConstructorSyntaxForNonRecordTypes && IsPrimaryConstructorParameterBackingField(field, metadata))
return true;
if (settings.AutomaticProperties && IsAutomaticPropertyBackingField(field, metadata, out var propertyName)) if (settings.AutomaticProperties && IsAutomaticPropertyBackingField(field, metadata, out var propertyName))
{ {
if (!settings.GetterOnlyAutomaticProperties && IsGetterOnlyProperty(propertyName)) if (!settings.GetterOnlyAutomaticProperties && IsGetterOnlyProperty(propertyName))
@ -390,6 +392,11 @@ namespace ICSharpCode.Decompiler.CSharp
return false; return false;
} }
static bool IsPrimaryConstructorParameterBackingField(SRM.FieldDefinition field, MetadataReader metadata)
{
var name = metadata.GetString(field.Name);
return name.StartsWith("<", StringComparison.Ordinal) && name.EndsWith(">P", StringComparison.Ordinal);
}
static bool IsSwitchOnStringCache(SRM.FieldDefinition field, MetadataReader metadata) static bool IsSwitchOnStringCache(SRM.FieldDefinition field, MetadataReader metadata)
{ {
@ -1303,12 +1310,12 @@ namespace ICSharpCode.Decompiler.CSharp
// e.g. DelegateDeclaration // e.g. DelegateDeclaration
return entityDecl; return entityDecl;
} }
bool isRecord = typeDef.Kind switch { bool isRecordLike = typeDef.Kind switch {
TypeKind.Class => settings.RecordClasses && typeDef.IsRecord, TypeKind.Class => (settings.RecordClasses && typeDef.IsRecord) || settings.UsePrimaryConstructorSyntaxForNonRecordTypes,
TypeKind.Struct => settings.RecordStructs && typeDef.IsRecord, TypeKind.Struct => (settings.RecordStructs && typeDef.IsRecord) || settings.UsePrimaryConstructorSyntaxForNonRecordTypes,
_ => false, _ => false,
}; };
RecordDecompiler recordDecompiler = isRecord ? new RecordDecompiler(typeSystem, typeDef, settings, CancellationToken) : null; RecordDecompiler recordDecompiler = isRecordLike ? new RecordDecompiler(typeSystem, typeDef, settings, CancellationToken) : null;
if (recordDecompiler != null) if (recordDecompiler != null)
decompileRun.RecordDecompilers.Add(typeDef, recordDecompiler); decompileRun.RecordDecompilers.Add(typeDef, recordDecompiler);
@ -1318,33 +1325,41 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
ParameterDeclaration pd = typeSystemAstBuilder.ConvertParameter(p); ParameterDeclaration pd = typeSystemAstBuilder.ConvertParameter(p);
(IProperty prop, IField field) = recordDecompiler.GetPropertyInfoByPrimaryConstructorParameter(p); (IProperty prop, IField field) = recordDecompiler.GetPropertyInfoByPrimaryConstructorParameter(p);
Syntax.Attribute[] attributes = prop.GetAttributes().Select(attr => typeSystemAstBuilder.ConvertAttribute(attr)).ToArray();
if (attributes.Length > 0) if (prop != null)
{ {
var section = new AttributeSection { var attributes = prop?.GetAttributes().Select(attr => typeSystemAstBuilder.ConvertAttribute(attr)).ToArray();
AttributeTarget = "property" if (attributes?.Length > 0)
}; {
section.Attributes.AddRange(attributes); var section = new AttributeSection {
pd.Attributes.Add(section); AttributeTarget = "property"
};
section.Attributes.AddRange(attributes);
pd.Attributes.Add(section);
}
} }
attributes = field.GetAttributes() if (field != null && (recordDecompiler.FieldIsGenerated(field) || typeDef.IsRecord))
.Where(a => !PatternStatementTransform.attributeTypesToRemoveFromAutoProperties.Contains(a.AttributeType.FullName))
.Select(attr => typeSystemAstBuilder.ConvertAttribute(attr)).ToArray();
if (attributes.Length > 0)
{ {
var section = new AttributeSection { var attributes = field.GetAttributes()
AttributeTarget = "field" .Where(a => !PatternStatementTransform.attributeTypesToRemoveFromAutoProperties.Contains(a.AttributeType.FullName))
}; .Select(attr => typeSystemAstBuilder.ConvertAttribute(attr)).ToArray();
section.Attributes.AddRange(attributes); if (attributes.Length > 0)
pd.Attributes.Add(section); {
var section = new AttributeSection {
AttributeTarget = "field"
};
section.Attributes.AddRange(attributes);
pd.Attributes.Add(section);
}
} }
typeDecl.PrimaryConstructorParameters.Add(pd); typeDecl.PrimaryConstructorParameters.Add(pd);
} }
} }
// With C# 9 records, the relative order of fields and properties matters: // With C# 9 records, the relative order of fields and properties matters:
IEnumerable<IMember> fieldsAndProperties = recordDecompiler?.FieldsAndProperties IEnumerable<IMember> fieldsAndProperties = isRecordLike && typeDef.IsRecord
?? typeDef.Fields.Concat<IMember>(typeDef.Properties); ? recordDecompiler.FieldsAndProperties
: typeDef.Fields.Concat<IMember>(typeDef.Properties);
// For COM interop scenarios, the relative order of virtual functions/properties matters: // For COM interop scenarios, the relative order of virtual functions/properties matters:
IEnumerable<IMember> allOrderedMembers = RequiresNativeOrdering(typeDef) ? GetMembersWithNativeOrdering(typeDef) : IEnumerable<IMember> allOrderedMembers = RequiresNativeOrdering(typeDef) ? GetMembersWithNativeOrdering(typeDef) :
@ -1481,6 +1496,10 @@ namespace ICSharpCode.Decompiler.CSharp
{ {
return; return;
} }
if (recordDecompiler?.FieldIsGenerated(field) == true)
{
return;
}
entityDecl = DoDecompile(field, decompileRun, decompilationContext.WithCurrentMember(field)); entityDecl = DoDecompile(field, decompileRun, decompilationContext.WithCurrentMember(field));
entityMap.Add(field, entityDecl); entityMap.Add(field, entityDecl);
break; break;

70
ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs

@ -43,8 +43,8 @@ namespace ICSharpCode.Decompiler.CSharp
readonly IType baseClass; readonly IType baseClass;
readonly Dictionary<IField, IProperty> backingFieldToAutoProperty = new Dictionary<IField, IProperty>(); readonly Dictionary<IField, IProperty> backingFieldToAutoProperty = new Dictionary<IField, IProperty>();
readonly Dictionary<IProperty, IField> autoPropertyToBackingField = new Dictionary<IProperty, IField>(); readonly Dictionary<IProperty, IField> autoPropertyToBackingField = new Dictionary<IProperty, IField>();
readonly Dictionary<IParameter, IProperty> primaryCtorParameterToAutoProperty = new Dictionary<IParameter, IProperty>(); readonly Dictionary<IParameter, IMember> primaryCtorParameterToAutoPropertyOrBackingField = new Dictionary<IParameter, IMember>();
readonly Dictionary<IProperty, IParameter> autoPropertyToPrimaryCtorParameter = new Dictionary<IProperty, IParameter>(); readonly Dictionary<IMember, IParameter> autoPropertyOrBackingFieldToPrimaryCtorParameter = new Dictionary<IMember, IParameter>();
public RecordDecompiler(IDecompilerTypeSystem dts, ITypeDefinition recordTypeDef, DecompilerSettings settings, CancellationToken cancellationToken) public RecordDecompiler(IDecompilerTypeSystem dts, ITypeDefinition recordTypeDef, DecompilerSettings settings, CancellationToken cancellationToken)
{ {
@ -78,6 +78,8 @@ namespace ICSharpCode.Decompiler.CSharp
bool IsAutoProperty(IProperty p, out IField field) bool IsAutoProperty(IProperty p, out IField field)
{ {
field = null; field = null;
if (p.IsStatic)
return false;
if (p.Parameters.Count != 0) if (p.Parameters.Count != 0)
return false; return false;
if (p.Getter != null) if (p.Getter != null)
@ -158,8 +160,18 @@ namespace ICSharpCode.Decompiler.CSharp
IMethod DetectPrimaryConstructor() IMethod DetectPrimaryConstructor()
{ {
if (!settings.UsePrimaryConstructorSyntax) if (recordTypeDef.IsRecord)
return null; {
if (!settings.UsePrimaryConstructorSyntax)
return null;
}
else
{
if (!settings.UsePrimaryConstructorSyntaxForNonRecordTypes)
return null;
if (isStruct)
return null;
}
var subst = recordTypeDef.AsParameterizedType().GetSubstitution(); var subst = recordTypeDef.AsParameterizedType().GetSubstitution();
foreach (var method in recordTypeDef.Methods) foreach (var method in recordTypeDef.Methods)
@ -170,8 +182,8 @@ namespace ICSharpCode.Decompiler.CSharp
var m = method.Specialize(subst); var m = method.Specialize(subst);
if (IsPrimaryConstructor(m, method)) if (IsPrimaryConstructor(m, method))
return method; return method;
primaryCtorParameterToAutoProperty.Clear(); primaryCtorParameterToAutoPropertyOrBackingField.Clear();
autoPropertyToPrimaryCtorParameter.Clear(); autoPropertyOrBackingFieldToPrimaryCtorParameter.Clear();
} }
return null; return null;
@ -205,10 +217,18 @@ namespace ICSharpCode.Decompiler.CSharp
return false; return false;
if (!(value.Kind == VariableKind.Parameter && value.Index == i)) if (!(value.Kind == VariableKind.Parameter && value.Index == i))
return false; return false;
if (!backingFieldToAutoProperty.TryGetValue(field, out var property)) IMember backingMember;
return false; if (backingFieldToAutoProperty.TryGetValue(field, out var property))
primaryCtorParameterToAutoProperty.Add(unspecializedMethod.Parameters[i], property); {
autoPropertyToPrimaryCtorParameter.Add(property, unspecializedMethod.Parameters[i]); backingMember = property;
}
else
{
backingMember = field;
}
primaryCtorParameterToAutoPropertyOrBackingField.Add(unspecializedMethod.Parameters[i], backingMember);
autoPropertyOrBackingFieldToPrimaryCtorParameter.Add(backingMember, unspecializedMethod.Parameters[i]);
} }
if (!isStruct) if (!isStruct)
@ -261,6 +281,9 @@ namespace ICSharpCode.Decompiler.CSharp
/// </summary> /// </summary>
public bool MethodIsGenerated(IMethod method) public bool MethodIsGenerated(IMethod method)
{ {
if (!recordTypeDef.IsRecord)
return false;
if (IsCopyConstructor(method)) if (IsCopyConstructor(method))
{ {
return IsGeneratedCopyConstructor(method); return IsGeneratedCopyConstructor(method);
@ -320,6 +343,9 @@ namespace ICSharpCode.Decompiler.CSharp
internal bool PropertyIsGenerated(IProperty property) internal bool PropertyIsGenerated(IProperty property)
{ {
if (!recordTypeDef.IsRecord)
return false;
switch (property.Name) switch (property.Name)
{ {
case "EqualityContract" when !isStruct: case "EqualityContract" when !isStruct:
@ -329,17 +355,35 @@ namespace ICSharpCode.Decompiler.CSharp
} }
} }
internal bool FieldIsGenerated(IField field)
{
if (!settings.UsePrimaryConstructorSyntaxForNonRecordTypes)
return false;
var name = field.Name;
return name.StartsWith("<", StringComparison.Ordinal)
&& name.EndsWith(">P", StringComparison.Ordinal)
&& field.IsCompilerGenerated();
}
public bool IsPropertyDeclaredByPrimaryConstructor(IProperty property) public bool IsPropertyDeclaredByPrimaryConstructor(IProperty property)
{ {
var subst = recordTypeDef.AsParameterizedType().GetSubstitution(); var subst = recordTypeDef.AsParameterizedType().GetSubstitution();
return primaryCtor != null return primaryCtor != null
&& autoPropertyToPrimaryCtorParameter.ContainsKey((IProperty)property.Specialize(subst)); && autoPropertyOrBackingFieldToPrimaryCtorParameter.ContainsKey((IProperty)property.Specialize(subst));
} }
internal (IProperty prop, IField field) GetPropertyInfoByPrimaryConstructorParameter(IParameter parameter) internal (IProperty prop, IField field) GetPropertyInfoByPrimaryConstructorParameter(IParameter parameter)
{ {
var prop = primaryCtorParameterToAutoProperty[parameter]; var member = primaryCtorParameterToAutoPropertyOrBackingField[parameter];
return (prop, autoPropertyToBackingField[prop]); if (member is IField field)
return (null, field);
return ((IProperty)member, autoPropertyToBackingField[(IProperty)member]);
}
internal IParameter GetPrimaryConstructorParameterFromBackingField(IField field)
{
return autoPropertyOrBackingFieldToPrimaryCtorParameter[field];
} }
public bool IsCopyConstructor(IMethod method) public bool IsCopyConstructor(IMethod method)

53
ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs

@ -16,7 +16,6 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -24,7 +23,9 @@ using System.Reflection;
using ICSharpCode.Decompiler.CSharp.Resolver; using ICSharpCode.Decompiler.CSharp.Resolver;
using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
using ICSharpCode.Decompiler.Semantics;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Util;
using SRM = System.Reflection.Metadata; using SRM = System.Reflection.Metadata;
@ -37,10 +38,12 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
public class TransformFieldAndConstructorInitializers : DepthFirstAstVisitor, IAstTransform public class TransformFieldAndConstructorInitializers : DepthFirstAstVisitor, IAstTransform
{ {
TransformContext context; TransformContext context;
Dictionary<IField, IL.ILVariable> fieldToVariableMap;
public void Run(AstNode node, TransformContext context) public void Run(AstNode node, TransformContext context)
{ {
this.context = context; this.context = context;
this.fieldToVariableMap = new();
try try
{ {
@ -56,6 +59,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
finally finally
{ {
this.context = null; this.context = null;
this.fieldToVariableMap = null;
} }
} }
@ -131,6 +135,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
newBaseType.BaseType = baseType; newBaseType.BaseType = baseType;
ci.Arguments.MoveTo(newBaseType.Arguments); ci.Arguments.MoveTo(newBaseType.Arguments);
} }
if (constructorDeclaration.Parent is TypeDeclaration { PrimaryConstructorParameters: var parameters })
{
foreach (var (cpd, ppd) in constructorDeclaration.Parameters.Zip(parameters))
{
ppd.CopyAnnotationsFrom(cpd);
}
}
constructorDeclaration.Remove(); constructorDeclaration.Remove();
} }
} }
@ -203,18 +214,19 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (fieldOrPropertyOrEventDecl is CustomEventDeclaration) if (fieldOrPropertyOrEventDecl is CustomEventDeclaration)
break; break;
Expression initializer = m.Get<Expression>("initializer").Single(); Expression initializer = m.Get<Expression>("initializer").Single();
// 'this'/'base' cannot be used in initializers // 'this'/'base' cannot be used in initializers
if (initializer.DescendantsAndSelf.Any(n => n is ThisReferenceExpression || n is BaseReferenceExpression)) if (initializer.DescendantsAndSelf.Any(n => n is ThisReferenceExpression || n is BaseReferenceExpression))
break; break;
var v = initializer.Annotation<ILVariableResolveResult>()?.Variable;
if (initializer.Annotation<ILVariableResolveResult>()?.Variable.Kind == IL.VariableKind.Parameter) if (v?.Kind == IL.VariableKind.Parameter)
{ {
// remove record ctor parameter assignments // remove record ctor parameter assignments
if (!IsPropertyDeclaredByPrimaryCtor(fieldOrPropertyOrEvent as IProperty, record)) if (!IsPropertyDeclaredByPrimaryCtor(fieldOrPropertyOrEvent, record))
break; break;
isStructPrimaryCtor = true; isStructPrimaryCtor = true;
if (fieldOrPropertyOrEvent is IField f)
fieldToVariableMap.Add(f, v);
} }
else else
{ {
@ -264,11 +276,21 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
} }
} }
bool IsPropertyDeclaredByPrimaryCtor(IProperty p, RecordDecompiler record) bool IsPropertyDeclaredByPrimaryCtor(IMember m, RecordDecompiler record)
{ {
if (p == null || record == null) if (record == null)
return false; return false;
return record.IsPropertyDeclaredByPrimaryConstructor(p); switch (m)
{
case IProperty p:
return record.IsPropertyDeclaredByPrimaryConstructor(p);
case IField f:
return true;
case IEvent e:
return true;
default:
return false;
}
} }
void RemoveSingleEmptyConstructor(IEnumerable<AstNode> members, ITypeDefinition contextTypeDefinition) void RemoveSingleEmptyConstructor(IEnumerable<AstNode> members, ITypeDefinition contextTypeDefinition)
@ -443,5 +465,20 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return false; return false;
} }
} }
public override void VisitIdentifier(Identifier identifier)
{
if (identifier.Parent?.GetSymbol() is not IField field)
{
return;
}
if (!fieldToVariableMap.TryGetValue(field, out var v))
{
return;
}
identifier.Parent.RemoveAnnotations<MemberResolveResult>();
identifier.Parent.AddAnnotation(new ILVariableResolveResult(v));
identifier.ReplaceWith(Identifier.Create(v.Name));
}
} }
} }

21
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -162,12 +162,13 @@ namespace ICSharpCode.Decompiler
if (languageVersion < CSharp.LanguageVersion.CSharp12_0) if (languageVersion < CSharp.LanguageVersion.CSharp12_0)
{ {
refReadOnlyParameters = false; refReadOnlyParameters = false;
usePrimaryConstructorSyntaxForNonRecordTypes = false;
} }
} }
public CSharp.LanguageVersion GetMinimumRequiredVersion() public CSharp.LanguageVersion GetMinimumRequiredVersion()
{ {
if (refReadOnlyParameters) if (refReadOnlyParameters || usePrimaryConstructorSyntaxForNonRecordTypes)
return CSharp.LanguageVersion.CSharp12_0; return CSharp.LanguageVersion.CSharp12_0;
if (scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators) if (scopedRef || requiredMembers || numericIntPtr || utf8StringLiterals || unsignedRightShift || checkedOperators)
return CSharp.LanguageVersion.CSharp11_0; return CSharp.LanguageVersion.CSharp11_0;
@ -2015,6 +2016,24 @@ namespace ICSharpCode.Decompiler
} }
} }
bool usePrimaryConstructorSyntaxForNonRecordTypes = true;
/// <summary>
/// Use primary constructor syntax with classes and structs.
/// </summary>
[Category("C# 12.0 / VS 2022.8")]
[Description("DecompilerSettings.UsePrimaryConstructorSyntaxForNonRecordTypes")]
public bool UsePrimaryConstructorSyntaxForNonRecordTypes {
get { return usePrimaryConstructorSyntaxForNonRecordTypes; }
set {
if (usePrimaryConstructorSyntaxForNonRecordTypes != value)
{
usePrimaryConstructorSyntaxForNonRecordTypes = value;
OnPropertyChanged();
}
}
}
bool separateLocalVariableDeclarations = false; bool separateLocalVariableDeclarations = false;
/// <summary> /// <summary>

10
ILSpy/Properties/Resources.Designer.cs generated

@ -1478,6 +1478,16 @@ namespace ICSharpCode.ILSpy.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to Use primary constructor syntax for non-record types.
/// </summary>
public static string DecompilerSettings_UsePrimaryConstructorDecompilerSettings_SyntaxForNonRecordTypes {
get {
return ResourceManager.GetString("DecompilerSettings.UsePrimaryConstructorDecompilerSettings.SyntaxForNonRecordType" +
"s", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to Use primary constructor syntax with records. /// Looks up a localized string similar to Use primary constructor syntax with records.
/// </summary> /// </summary>

3
ILSpy/Properties/Resources.resx

@ -516,6 +516,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.UsePatternBasedFixedStatement" xml:space="preserve"> <data name="DecompilerSettings.UsePatternBasedFixedStatement" xml:space="preserve">
<value>Use pattern-based fixed statement</value> <value>Use pattern-based fixed statement</value>
</data> </data>
<data name="DecompilerSettings.UsePrimaryConstructorDecompilerSettings.SyntaxForNonRecordTypes" xml:space="preserve">
<value>Use primary constructor syntax for non-record types</value>
</data>
<data name="DecompilerSettings.UsePrimaryConstructorSyntax" xml:space="preserve"> <data name="DecompilerSettings.UsePrimaryConstructorSyntax" xml:space="preserve">
<value>Use primary constructor syntax with records</value> <value>Use primary constructor syntax with records</value>
</data> </data>

Loading…
Cancel
Save