Browse Source

Add support for primary constructor syntax.

pull/2276/head
Siegfried Pammer 4 years ago
parent
commit
a5858f1694
  1. 7
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Debug.cs
  2. 7
      ICSharpCode.Decompiler.Tests/TestCases/ILPretty/FSharpLoops_Release.cs
  3. 20
      ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs
  4. 11
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  5. 82
      ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs
  6. 174
      ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs
  7. 15
      ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs
  8. 5
      ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/TypeDeclaration.cs
  9. 3
      ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs
  10. 70
      ICSharpCode.Decompiler/CSharp/Syntax/InvocationAstType.cs
  11. 49
      ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs
  12. 2
      ICSharpCode.Decompiler/DecompileRun.cs
  13. 19
      ICSharpCode.Decompiler/DecompilerSettings.cs
  14. 1
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  15. 9
      ILSpy/Properties/Resources.Designer.cs
  16. 3
      ILSpy/Properties/Resources.resx

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

@ -53,15 +53,18 @@ public static class Program @@ -53,15 +53,18 @@ public static class Program
[DebuggerNonUserCode]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated]
public int pc = pc;
public int pc;
[DebuggerNonUserCode]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated]
public int current = current;
public int current;
public getSeq_00405(int pc, int current)
{
this.pc = pc;
this.current = current;
base._002Ector();
}
public override int GenerateNext(ref IEnumerable<int> next)

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

@ -54,15 +54,18 @@ public static class Program @@ -54,15 +54,18 @@ public static class Program
[DebuggerNonUserCode]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated]
public int pc = pc;
public int pc;
[DebuggerNonUserCode]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[CompilerGenerated]
public int current = current;
public int current;
public getSeq_00405(int pc, int current)
{
this.pc = pc;
this.current = current;
base._002Ector();
}
public override int GenerateNext(ref IEnumerable<int> next)

20
ICSharpCode.Decompiler.Tests/TestCases/Pretty/Records.cs

@ -1,8 +1,10 @@ @@ -1,8 +1,10 @@
namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
{
public record Empty
{
}
public record Base(string A);
public record Derived(int B) : Base(B.ToString());
public record Empty;
public record Fields
{
@ -19,6 +21,18 @@ @@ -19,6 +21,18 @@
public B Second { get; init; }
}
public record PairWithPrimaryCtor<A, B>(A First, B Second);
public record PrimaryCtor(int A, string B);
public record PrimaryCtorWithField(int A, string B)
{
public double C;
}
public record PrimaryCtorWithProperty(int A, string B)
{
public double C { get; init; }
}
public record Properties
{
public int A { get; set; }

11
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -1169,7 +1169,16 @@ namespace ICSharpCode.Decompiler.CSharp @@ -1169,7 +1169,16 @@ namespace ICSharpCode.Decompiler.CSharp
return entityDecl;
}
bool isRecord = settings.RecordClasses && typeDef.IsRecord;
RecordDecompiler recordDecompiler = isRecord ? new RecordDecompiler(typeSystem, typeDef, CancellationToken) : null;
RecordDecompiler recordDecompiler = isRecord ? new RecordDecompiler(typeSystem, typeDef, settings, CancellationToken) : null;
if (recordDecompiler != null)
decompileRun.RecordDecompilers.Add(typeDef, recordDecompiler);
if (recordDecompiler?.PrimaryConstructor != null)
{
foreach (var p in recordDecompiler.PrimaryConstructor.Parameters)
typeDecl.PrimaryConstructorParameters.Add(typeSystemAstBuilder.ConvertParameter(p));
}
foreach (var type in typeDef.NestedTypes)
{
if (!type.MetadataToken.IsNil && !MemberIsHidden(module.PEFile, type.MetadataToken, settings))

82
ICSharpCode.Decompiler/CSharp/OutputVisitor/CSharpOutputVisitor.cs

@ -1557,6 +1557,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1557,6 +1557,11 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
}
WriteIdentifier(typeDeclaration.NameToken);
WriteTypeParameters(typeDeclaration.TypeParameters);
if (typeDeclaration.PrimaryConstructorParameters.Count > 0)
{
Space(policy.SpaceBeforeMethodDeclarationParentheses);
WriteCommaSeparatedListInParenthesis(typeDeclaration.PrimaryConstructorParameters, policy.SpaceWithinMethodDeclarationParentheses);
}
if (typeDeclaration.BaseTypes.Any())
{
Space();
@ -1568,46 +1573,53 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -1568,46 +1573,53 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
{
constraint.AcceptVisitor(this);
}
OpenBrace(braceStyle);
if (typeDeclaration.ClassType == ClassType.Enum)
if (typeDeclaration.ClassType == ClassType.RecordClass && typeDeclaration.Members.Count == 0)
{
bool first = true;
AstNode last = null;
foreach (var member in typeDeclaration.Members)
{
if (first)
{
first = false;
}
else
{
Comma(member, noSpaceAfterComma: true);
NewLine();
}
last = member;
member.AcceptVisitor(this);
}
if (last != null)
OptionalComma(last.NextSibling);
NewLine();
Semicolon();
}
else
{
bool first = true;
foreach (var member in typeDeclaration.Members)
OpenBrace(braceStyle);
if (typeDeclaration.ClassType == ClassType.Enum)
{
if (!first)
bool first = true;
AstNode last = null;
foreach (var member in typeDeclaration.Members)
{
for (int i = 0; i < policy.MinimumBlankLinesBetweenMembers; i++)
if (first)
{
first = false;
}
else
{
Comma(member, noSpaceAfterComma: true);
NewLine();
}
last = member;
member.AcceptVisitor(this);
}
if (last != null)
OptionalComma(last.NextSibling);
NewLine();
}
else
{
bool first = true;
foreach (var member in typeDeclaration.Members)
{
if (!first)
{
for (int i = 0; i < policy.MinimumBlankLinesBetweenMembers; i++)
NewLine();
}
first = false;
member.AcceptVisitor(this);
}
first = false;
member.AcceptVisitor(this);
}
CloseBrace(braceStyle);
OptionalSemicolon(typeDeclaration.LastChild);
NewLine();
}
CloseBrace(braceStyle);
OptionalSemicolon(typeDeclaration.LastChild);
NewLine();
EndNode(typeDeclaration);
}
@ -2705,6 +2717,16 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor @@ -2705,6 +2717,16 @@ namespace ICSharpCode.Decompiler.CSharp.OutputVisitor
EndNode(functionPointerType);
}
public virtual void VisitInvocationType(InvocationAstType invocationType)
{
StartNode(invocationType);
invocationType.BaseType.AcceptVisitor(this);
WriteToken(Roles.LPar);
WriteCommaSeparatedList(invocationType.Arguments);
WriteToken(Roles.RPar);
EndNode(invocationType);
}
public virtual void VisitComposedType(ComposedType composedType)
{
StartNode(composedType);

174
ICSharpCode.Decompiler/CSharp/RecordDecompiler.cs

@ -33,22 +33,28 @@ namespace ICSharpCode.Decompiler.CSharp @@ -33,22 +33,28 @@ namespace ICSharpCode.Decompiler.CSharp
{
readonly IDecompilerTypeSystem typeSystem;
readonly ITypeDefinition recordTypeDef;
readonly DecompilerSettings settings;
readonly CancellationToken cancellationToken;
readonly List<IMember> orderedMembers;
readonly bool isInheritedRecord;
readonly IMethod primaryCtor;
readonly IType baseClass;
readonly Dictionary<IField, IProperty> backingFieldToAutoProperty = new Dictionary<IField, IProperty>();
readonly Dictionary<IProperty, IField> autoPropertyToBackingField = new Dictionary<IProperty, IField>();
readonly Dictionary<IParameter, IProperty> primaryCtorParameterToAutoProperty = new Dictionary<IParameter, IProperty>();
readonly Dictionary<IProperty, IParameter> autoPropertyToPrimaryCtorParameter = new Dictionary<IProperty, IParameter>();
public RecordDecompiler(IDecompilerTypeSystem dts, ITypeDefinition recordTypeDef, CancellationToken cancellationToken)
public RecordDecompiler(IDecompilerTypeSystem dts, ITypeDefinition recordTypeDef, DecompilerSettings settings, CancellationToken cancellationToken)
{
this.typeSystem = dts;
this.recordTypeDef = recordTypeDef;
this.settings = settings;
this.cancellationToken = cancellationToken;
this.baseClass = recordTypeDef.DirectBaseTypes.FirstOrDefault(b => b.Kind == TypeKind.Class);
this.isInheritedRecord = !baseClass.IsKnownType(KnownTypeCode.Object);
DetectAutomaticProperties();
this.orderedMembers = DetectMemberOrder(recordTypeDef, backingFieldToAutoProperty);
this.primaryCtor = DetectPrimaryConstructor();
}
void DetectAutomaticProperties()
@ -146,6 +152,62 @@ namespace ICSharpCode.Decompiler.CSharp @@ -146,6 +152,62 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
IMethod DetectPrimaryConstructor()
{
if (!settings.UsePrimaryConstructorSyntax)
return null;
var subst = recordTypeDef.AsParameterizedType().GetSubstitution();
foreach (var method in recordTypeDef.Methods)
{
cancellationToken.ThrowIfCancellationRequested();
if (method.IsStatic || !method.IsConstructor)
continue;
var m = method.Specialize(subst);
if (IsPrimaryConstructor(m))
return method;
}
return null;
bool IsPrimaryConstructor(IMethod method)
{
Debug.Assert(method.IsConstructor);
var body = DecompileBody(method);
if (body == null)
return false;
if (method.Parameters.Count == 0)
return false;
if (body.Instructions.Count != method.Parameters.Count + 2)
return false;
for (int i = 0; i < body.Instructions.Count - 2; i++)
{
if (!body.Instructions[i].MatchStFld(out var target, out var field, out var valueInst))
return false;
if (!target.MatchLdThis())
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]);
}
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();
}
}
static List<IMember> DetectMemberOrder(ITypeDefinition recordTypeDef, Dictionary<IField, IProperty> backingFieldToAutoProperty)
{
// For records, the order of members is important:
@ -166,6 +228,11 @@ namespace ICSharpCode.Decompiler.CSharp @@ -166,6 +228,11 @@ namespace ICSharpCode.Decompiler.CSharp
/// </summary>
public IEnumerable<IMember> FieldsAndProperties => orderedMembers;
/// <summary>
/// Gets the detected primary constructor. Returns null, if there was no primary constructor detected.
/// </summary>
public IMethod PrimaryConstructor => primaryCtor;
bool IsRecordType(IType type)
{
return type.GetDefinition() == recordTypeDef
@ -177,11 +244,15 @@ namespace ICSharpCode.Decompiler.CSharp @@ -177,11 +244,15 @@ namespace ICSharpCode.Decompiler.CSharp
/// </summary>
public bool MethodIsGenerated(IMethod method)
{
if (method.IsConstructor && method.Parameters.Count == 1
&& IsRecordType(method.Parameters[0].Type))
if (method.IsConstructor)
{
return IsGeneratedCopyConstructor(method);
if (method.Parameters.Count == 1
&& IsRecordType(method.Parameters[0].Type))
{
return IsGeneratedCopyConstructor(method);
}
}
switch (method.Name)
{
// Some members in records are always compiler-generated and lead to a
@ -227,6 +298,8 @@ namespace ICSharpCode.Decompiler.CSharp @@ -227,6 +298,8 @@ namespace ICSharpCode.Decompiler.CSharp
return IsGeneratedPrintMembers(method);
case "ToString" when method.Parameters.Count == 0:
return IsGeneratedToString(method);
case "Deconstruct" when primaryCtor != null && method.Parameters.Count == primaryCtor.Parameters.Count:
return IsGeneratedDeconstruct(method);
default:
return false;
}
@ -239,10 +312,17 @@ namespace ICSharpCode.Decompiler.CSharp @@ -239,10 +312,17 @@ namespace ICSharpCode.Decompiler.CSharp
case "EqualityContract":
return IsGeneratedEqualityContract(property);
default:
return false;
return IsPropertyDeclaredByPrimaryConstructor(property);
}
}
public bool IsPropertyDeclaredByPrimaryConstructor(IProperty property)
{
var subst = recordTypeDef.AsParameterizedType().GetSubstitution();
return primaryCtor != null
&& autoPropertyToPrimaryCtorParameter.ContainsKey((IProperty)property.Specialize(subst));
}
private bool IsGeneratedCopyConstructor(IMethod method)
{
/*
@ -779,18 +859,18 @@ namespace ICSharpCode.Decompiler.CSharp @@ -779,18 +859,18 @@ namespace ICSharpCode.Decompiler.CSharp
bool Visit(ILInstruction inst)
{
if (inst is BinaryNumericInstruction
{
Operator: BinaryNumericOperator.Add,
CheckForOverflow: false,
Left: BinaryNumericInstruction
{
Operator: BinaryNumericOperator.Mul,
Operator: BinaryNumericOperator.Add,
CheckForOverflow: false,
Left: var left,
Right: LdcI4 { Value: -1521134295 }
},
Right: var right
})
Left: BinaryNumericInstruction
{
Operator: BinaryNumericOperator.Mul,
CheckForOverflow: false,
Left: var left,
Right: LdcI4 { Value: -1521134295 }
},
Right: var right
})
{
if (!Visit(left))
return false;
@ -834,18 +914,70 @@ namespace ICSharpCode.Decompiler.CSharp @@ -834,18 +914,70 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
bool IsGeneratedDeconstruct(IMethod method)
{
Debug.Assert(method.Name == "Deconstruct" && method.Parameters.Count == primaryCtor.Parameters.Count);
if (!method.ReturnType.IsKnownType(KnownTypeCode.Void))
return false;
for (int i = 0; i < method.Parameters.Count; i++)
{
var deconstruct = method.Parameters[i];
var ctor = primaryCtor.Parameters[i];
if (!deconstruct.IsOut)
return false;
if (!ctor.Type.Equals(((ByReferenceType)deconstruct.Type).ElementType))
return false;
if (ctor.Name != deconstruct.Name)
return false;
}
var body = DecompileBody(method);
if (body == null || body.Instructions.Count != method.Parameters.Count + 1)
return false;
for (int i = 0; i < body.Instructions.Count - 1; i++)
{
// stobj T(ldloc parameter, call getter(ldloc this))
if (!body.Instructions[i].MatchStObj(out var targetInst, out var getter, out _))
return false;
if (!targetInst.MatchLdLoc(out var target))
return false;
if (!(target.Kind == VariableKind.Parameter && target.Index == i))
return false;
if (getter is not Call call || call.Arguments.Count != 1)
return false;
if (!call.Arguments[0].MatchLdThis())
return false;
if (!call.Method.IsAccessor)
return false;
var autoProperty = (IProperty)call.Method.AccessorOwner;
if (!autoPropertyToBackingField.ContainsKey(autoProperty))
return false;
}
var returnInst = body.Instructions.LastOrDefault();
return returnInst != null && returnInst.MatchReturn(out var retVal) && retVal.MatchNop();
}
bool MatchMemberAccess(ILInstruction inst, out ILInstruction target, out IMember member)
{
target = null;
member = null;
if (inst is CallVirt
{
Method:
{
AccessorKind: System.Reflection.MethodSemanticsAttributes.Getter,
AccessorOwner: IProperty property
}
} call)
Method:
{
AccessorKind: System.Reflection.MethodSemanticsAttributes.Getter,
AccessorOwner: IProperty property
}
} call)
{
if (call.Arguments.Count != 1)
return false;

15
ICSharpCode.Decompiler/CSharp/Syntax/DepthFirstAstVisitor.cs

@ -117,6 +117,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -117,6 +117,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
VisitChildren(functionPointerType);
}
public virtual void VisitInvocationType(InvocationAstType invocationType)
{
VisitChildren(invocationType);
}
public virtual void VisitAttribute(Attribute attribute)
{
VisitChildren(attribute);
@ -790,6 +795,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -790,6 +795,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return VisitChildren(functionPointerType);
}
public virtual T VisitInvocationType(InvocationAstType invocationType)
{
return VisitChildren(invocationType);
}
public virtual T VisitAttribute(Attribute attribute)
{
return VisitChildren(attribute);
@ -1463,6 +1473,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -1463,6 +1473,11 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return VisitChildren(functionPointerType, data);
}
public virtual S VisitInvocationType(InvocationAstType invocationType, T data)
{
return VisitChildren(invocationType, data);
}
public virtual S VisitAttribute(Attribute attribute, T data)
{
return VisitChildren(attribute, data);

5
ICSharpCode.Decompiler/CSharp/Syntax/GeneralScope/TypeDeclaration.cs

@ -107,6 +107,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -107,6 +107,10 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
get { return GetChildrenByRole(Roles.BaseType); }
}
public AstNodeCollection<ParameterDeclaration> PrimaryConstructorParameters {
get { return GetChildrenByRole(Roles.Parameter); }
}
public AstNodeCollection<Constraint> Constraints {
get { return GetChildrenByRole(Roles.Constraint); }
}
@ -144,6 +148,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -144,6 +148,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
return o != null && this.ClassType == o.ClassType && MatchString(this.Name, o.Name)
&& this.MatchAttributesAndModifiers(o, match) && this.TypeParameters.DoMatch(o.TypeParameters, match)
&& this.BaseTypes.DoMatch(o.BaseTypes, match) && this.Constraints.DoMatch(o.Constraints, match)
&& this.PrimaryConstructorParameters.DoMatch(o.PrimaryConstructorParameters, match)
&& this.Members.DoMatch(o.Members, match);
}
}

3
ICSharpCode.Decompiler/CSharp/Syntax/IAstVisitor.cs

@ -142,6 +142,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -142,6 +142,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
void VisitTupleType(TupleAstType tupleType);
void VisitTupleTypeElement(TupleTypeElement tupleTypeElement);
void VisitFunctionPointerType(FunctionPointerAstType functionPointerType);
void VisitInvocationType(InvocationAstType invocationType);
void VisitComposedType(ComposedType composedType);
void VisitArraySpecifier(ArraySpecifier arraySpecifier);
void VisitPrimitiveType(PrimitiveType primitiveType);
@ -289,6 +290,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -289,6 +290,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitTupleType(TupleAstType tupleType);
S VisitTupleTypeElement(TupleTypeElement tupleTypeElement);
S VisitFunctionPointerType(FunctionPointerAstType functionPointerType);
S VisitInvocationType(InvocationAstType invocationType);
S VisitComposedType(ComposedType composedType);
S VisitArraySpecifier(ArraySpecifier arraySpecifier);
S VisitPrimitiveType(PrimitiveType primitiveType);
@ -436,6 +438,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax @@ -436,6 +438,7 @@ namespace ICSharpCode.Decompiler.CSharp.Syntax
S VisitTupleType(TupleAstType tupleType, T data);
S VisitTupleTypeElement(TupleTypeElement tupleTypeElement, T data);
S VisitFunctionPointerType(FunctionPointerAstType functionPointerType, T data);
S VisitInvocationType(InvocationAstType invocationType, T data);
S VisitComposedType(ComposedType composedType, T data);
S VisitArraySpecifier(ArraySpecifier arraySpecifier, T data);
S VisitPrimitiveType(PrimitiveType primitiveType, T data);

70
ICSharpCode.Decompiler/CSharp/Syntax/InvocationAstType.cs

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
// Copyright (c) 2021 Siegfried Pammer
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Text;
using ICSharpCode.Decompiler.CSharp.Resolver;
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.CSharp.Syntax
{
/// <summary>
/// BaseType "(" Argument { "," Argument } ")"
/// </summary>
public class InvocationAstType : AstType
{
public AstNodeCollection<Expression> Arguments {
get { return GetChildrenByRole(Roles.Expression); }
}
public AstType BaseType {
get { return GetChildByRole(Roles.Type); }
set { SetChildByRole(Roles.Type, value); }
}
public override void AcceptVisitor(IAstVisitor visitor)
{
visitor.VisitInvocationType(this);
}
public override T AcceptVisitor<T>(IAstVisitor<T> visitor)
{
return visitor.VisitInvocationType(this);
}
public override S AcceptVisitor<T, S>(IAstVisitor<T, S> visitor, T data)
{
return visitor.VisitInvocationType(this, data);
}
protected internal override bool DoMatch(AstNode other, Match match)
{
return other is InvocationAstType o
&& this.BaseType.DoMatch(o.BaseType, match)
&& this.Arguments.DoMatch(o.Arguments, match);
}
public override ITypeReference ToTypeReference(NameLookupMode lookupMode, InterningProvider interningProvider = null)
{
throw new NotImplementedException();
}
}
}

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

@ -108,6 +108,22 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -108,6 +108,22 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
// 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)
{
if (constructorDeclaration.Parent is TypeDeclaration { BaseTypes: { Count: >= 1 } } typeDecl)
{
var baseType = typeDecl.BaseTypes.First();
var newBaseType = new InvocationAstType();
baseType.ReplaceWith(newBaseType);
newBaseType.BaseType = baseType;
ci.Arguments.MoveTo(newBaseType.Arguments);
}
constructorDeclaration.Remove();
}
}
@ -154,6 +170,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -154,6 +170,9 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
bool ctorIsUnsafe = instanceCtorsNotChainingWithThis.All(c => c.HasModifier(Modifiers.Unsafe));
if (!context.DecompileRun.RecordDecompilers.TryGetValue(ctorMethodDef.DeclaringTypeDefinition, out var record))
record = null;
// Recognize field or property initializers:
// Translate first statement in all ctors (if all ctors have the same statement) into an initializer.
bool allSame;
@ -166,14 +185,31 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -166,14 +185,31 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
if (!(fieldOrPropertyOrEvent is IField) && !(fieldOrPropertyOrEvent is IProperty) && !(fieldOrPropertyOrEvent is IEvent))
break;
var fieldOrPropertyOrEventDecl = members.FirstOrDefault(f => f.GetSymbol() == fieldOrPropertyOrEvent) as EntityDeclaration;
// Cannot transform if member is not found or if it is a custom event.
if (fieldOrPropertyOrEventDecl == null || fieldOrPropertyOrEventDecl is CustomEventDeclaration)
// Cannot transform if it is a custom event.
if (fieldOrPropertyOrEventDecl is CustomEventDeclaration)
break;
Expression initializer = m.Get<Expression>("initializer").Single();
// 'this'/'base' cannot be used in initializers
if (initializer.DescendantsAndSelf.Any(n => n is ThisReferenceExpression || n is BaseReferenceExpression))
break;
if (initializer.Annotation<ILVariableResolveResult>()?.Variable.Kind == IL.VariableKind.Parameter)
{
// remove record ctor parameter assignments
if (IsPropertyDeclaredByPrimaryCtor(fieldOrPropertyOrEvent as IProperty, record))
initializer.Remove();
else
break;
}
else
{
// cannot transform if member is not found
if (fieldOrPropertyOrEventDecl == null)
break;
}
allSame = true;
for (int i = 1; i < instanceCtorsNotChainingWithThis.Length; i++)
{
@ -193,6 +229,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -193,6 +229,8 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{
foreach (var ctor in instanceCtorsNotChainingWithThis)
ctor.Body.First().Remove();
if (fieldOrPropertyOrEventDecl == null)
continue;
if (ctorIsUnsafe && IntroduceUnsafeModifier.IsUnsafe(initializer))
{
fieldOrPropertyOrEventDecl.Modifiers |= Modifiers.Unsafe;
@ -210,6 +248,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -210,6 +248,13 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
}
}
bool IsPropertyDeclaredByPrimaryCtor(IProperty p, RecordDecompiler record)
{
if (p == null || record == null)
return false;
return record.IsPropertyDeclaredByPrimaryConstructor(p);
}
void RemoveSingleEmptyConstructor(IEnumerable<AstNode> members, ITypeDefinition contextTypeDefinition)
{
// if we're outside of a type definition skip this altogether

2
ICSharpCode.Decompiler/DecompileRun.cs

@ -3,6 +3,7 @@ using System.Collections.Generic; @@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Text;
using System.Threading;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.TypeSystem;
@ -16,6 +17,7 @@ namespace ICSharpCode.Decompiler @@ -16,6 +17,7 @@ namespace ICSharpCode.Decompiler
public CancellationToken CancellationToken { get; set; }
public DecompilerSettings Settings { get; }
public IDocumentationProvider DocumentationProvider { get; set; }
public Dictionary<ITypeDefinition, RecordDecompiler> RecordDecompilers { get; } = new Dictionary<ITypeDefinition, RecordDecompiler>();
Lazy<CSharp.TypeSystem.UsingScope> usingScope =>
new Lazy<CSharp.TypeSystem.UsingScope>(() => CreateUsingScope(Namespaces));

19
ICSharpCode.Decompiler/DecompilerSettings.cs

@ -137,6 +137,7 @@ namespace ICSharpCode.Decompiler @@ -137,6 +137,7 @@ namespace ICSharpCode.Decompiler
forEachWithGetEnumeratorExtension = false;
recordClasses = false;
withExpressions = false;
usePrimaryConstructorSyntax = false;
}
}
@ -244,6 +245,24 @@ namespace ICSharpCode.Decompiler @@ -244,6 +245,24 @@ namespace ICSharpCode.Decompiler
}
}
bool usePrimaryConstructorSyntax = true;
/// <summary>
/// Use primary constructor syntax with records.
/// </summary>
[Category("C# 9.0 / VS 2019.8")]
[Description("DecompilerSettings.UsePrimaryConstructorSyntax")]
public bool UsePrimaryConstructorSyntax {
get { return usePrimaryConstructorSyntax; }
set {
if (usePrimaryConstructorSyntax != value)
{
usePrimaryConstructorSyntax = value;
OnPropertyChanged();
}
}
}
bool functionPointers = true;
/// <summary>

1
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -75,6 +75,7 @@ @@ -75,6 +75,7 @@
<Compile Include="CSharp\Syntax\Expressions\SwitchExpression.cs" />
<Compile Include="CSharp\Syntax\Expressions\WithInitializerExpression.cs" />
<Compile Include="CSharp\Syntax\FunctionPointerAstType.cs" />
<Compile Include="CSharp\Syntax\InvocationAstType.cs" />
<Compile Include="CSharp\Syntax\VariableDesignation.cs" />
<Compile Include="Humanizer\Vocabularies.cs" />
<Compile Include="Humanizer\Vocabulary.cs" />

9
ILSpy/Properties/Resources.Designer.cs generated

@ -1262,6 +1262,15 @@ namespace ICSharpCode.ILSpy.Properties { @@ -1262,6 +1262,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Use primary constructor syntax with records.
/// </summary>
public static string DecompilerSettings_UsePrimaryConstructorSyntax {
get {
return ResourceManager.GetString("DecompilerSettings.UsePrimaryConstructorSyntax", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Use ref locals to accurately represent order of evaluation.
/// </summary>

3
ILSpy/Properties/Resources.resx

@ -450,6 +450,9 @@ Are you sure you want to continue?</value> @@ -450,6 +450,9 @@ Are you sure you want to continue?</value>
<data name="DecompilerSettings.UsePatternBasedFixedStatement" xml:space="preserve">
<value>Use pattern-based fixed statement</value>
</data>
<data name="DecompilerSettings.UsePrimaryConstructorSyntax" xml:space="preserve">
<value>Use primary constructor syntax with records</value>
</data>
<data name="DecompilerSettings.UseRefLocalsForAccurateOrderOfEvaluation" xml:space="preserve">
<value>Use ref locals to accurately represent order of evaluation</value>
</data>

Loading…
Cancel
Save