Browse Source

Fix #1990: Empty default ctor with XML comment discarded from decompilation

pull/1996/head
Siegfried Pammer 6 years ago
parent
commit
6382f8c41d
  1. 2
      ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs
  2. 97
      ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs
  3. 2
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

2
ICSharpCode.Decompiler/CSharp/CSharpDecompiler.cs

@ -180,7 +180,7 @@ namespace ICSharpCode.Decompiler.CSharp
new IntroduceUnsafeModifier(), new IntroduceUnsafeModifier(),
new AddCheckedBlocks(), new AddCheckedBlocks(),
new DeclareVariables(), // should run after most transforms that modify statements new DeclareVariables(), // should run after most transforms that modify statements
new ConvertConstructorCallIntoInitializer(), // must run after DeclareVariables new TransformFieldAndConstructorInitializers(), // must run after DeclareVariables
new DecimalConstantTransform(), new DecimalConstantTransform(),
new PrettifyAssignments(), // must run after DeclareVariables new PrettifyAssignments(), // must run after DeclareVariables
new IntroduceUsingDeclarations(), new IntroduceUsingDeclarations(),

97
ICSharpCode.Decompiler/CSharp/Transforms/ConvertConstructorCallIntoInitializer.cs → ICSharpCode.Decompiler/CSharp/Transforms/TransformFieldAndConstructorInitializers.cs

@ -17,7 +17,6 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching; using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
@ -27,35 +26,31 @@ using SRM = System.Reflection.Metadata;
namespace ICSharpCode.Decompiler.CSharp.Transforms namespace ICSharpCode.Decompiler.CSharp.Transforms
{ {
/// <summary> /// <summary>
/// If the first element of a constructor is a chained constructor call, convert it into a constructor initializer. /// This transform moves field initializers at the start of constructors to their respective field declarations
/// and transforms this-/base-ctor calls in constructors to constructor initializers.
/// </summary> /// </summary>
public class ConvertConstructorCallIntoInitializer : IAstTransform public class TransformFieldAndConstructorInitializers : DepthFirstAstVisitor, IAstTransform
{ {
public void Run(AstNode node, TransformContext context) TransformContext context;
{
var visitor = new ConvertConstructorCallIntoInitializerVisitor(context);
// If we're viewing some set of members (fields are direct children of SyntaxTree),
// we also need to handle those:
visitor.HandleInstanceFieldInitializers(node.Children);
visitor.HandleStaticFieldInitializers(node.Children);
node.AcceptVisitor(visitor);
visitor.RemoveSingleEmptyConstructor(node.Children, context.CurrentTypeDefinition); public void Run(AstNode node, TransformContext context)
}
}
sealed class ConvertConstructorCallIntoInitializerVisitor : DepthFirstAstVisitor
{
readonly TransformContext context;
public ConvertConstructorCallIntoInitializerVisitor(TransformContext context)
{ {
Debug.Assert(context != null);
this.context = context; this.context = context;
try {
// If we're viewing some set of members (fields are direct children of SyntaxTree),
// we also need to handle those:
HandleInstanceFieldInitializers(node.Children);
HandleStaticFieldInitializers(node.Children);
node.AcceptVisitor(this);
RemoveSingleEmptyConstructor(node.Children, context.CurrentTypeDefinition);
} finally {
this.context = null;
}
} }
public override void VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration) public override void VisitConstructorDeclaration(ConstructorDeclaration constructorDeclaration)
{ {
if (!(constructorDeclaration.Body.Statements.FirstOrDefault() is ExpressionStatement stmt)) if (!(constructorDeclaration.Body.Statements.FirstOrDefault() is ExpressionStatement stmt))
@ -109,7 +104,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
break; break;
} }
} }
static readonly ExpressionStatement fieldInitializerPattern = new ExpressionStatement { static readonly ExpressionStatement fieldInitializerPattern = new ExpressionStatement {
Expression = new AssignmentExpression { Expression = new AssignmentExpression {
Left = new Choice { Left = new Choice {
@ -123,25 +118,25 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
Right = new AnyNode("initializer") Right = new AnyNode("initializer")
} }
}; };
static readonly AstNode thisCallPattern = new ExpressionStatement(new InvocationExpression(new MemberReferenceExpression(new ThisReferenceExpression(), ".ctor"), new Repeat(new AnyNode()))); static readonly AstNode thisCallPattern = new ExpressionStatement(new InvocationExpression(new MemberReferenceExpression(new ThisReferenceExpression(), ".ctor"), new Repeat(new AnyNode())));
public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration) public override void VisitTypeDeclaration(TypeDeclaration typeDeclaration)
{ {
// Handle initializers on instance fields // Handle initializers on instance fields
HandleInstanceFieldInitializers(typeDeclaration.Members); HandleInstanceFieldInitializers(typeDeclaration.Members);
// Now convert base constructor calls to initializers: // Now convert base constructor calls to initializers:
base.VisitTypeDeclaration(typeDeclaration); base.VisitTypeDeclaration(typeDeclaration);
// Remove single empty constructor: // Remove single empty constructor:
RemoveSingleEmptyConstructor(typeDeclaration.Members, (ITypeDefinition)typeDeclaration.GetSymbol()); RemoveSingleEmptyConstructor(typeDeclaration.Members, (ITypeDefinition)typeDeclaration.GetSymbol());
// Handle initializers on static fields: // Handle initializers on static fields:
HandleStaticFieldInitializers(typeDeclaration.Members); HandleStaticFieldInitializers(typeDeclaration.Members);
} }
internal void HandleInstanceFieldInitializers(IEnumerable<AstNode> members) void HandleInstanceFieldInitializers(IEnumerable<AstNode> members)
{ {
var instanceCtors = members.OfType<ConstructorDeclaration>().Where(c => (c.Modifiers & Modifiers.Static) == 0).ToArray(); var instanceCtors = members.OfType<ConstructorDeclaration>().Where(c => (c.Modifiers & Modifiers.Static) == 0).ToArray();
var instanceCtorsNotChainingWithThis = instanceCtors.Where(ctor => !thisCallPattern.IsMatch(ctor.Body.Statements.FirstOrDefault())).ToArray(); var instanceCtorsNotChainingWithThis = instanceCtors.Where(ctor => !thisCallPattern.IsMatch(ctor.Body.Statements.FirstOrDefault())).ToArray();
@ -151,7 +146,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
return; return;
bool ctorIsUnsafe = instanceCtorsNotChainingWithThis.All(c => c.HasModifier(Modifiers.Unsafe)); bool ctorIsUnsafe = instanceCtorsNotChainingWithThis.All(c => c.HasModifier(Modifiers.Unsafe));
// Recognize field or property initializers: // Recognize field or property initializers:
// Translate first statement in all ctors (if all ctors have the same statement) into an initializer. // Translate first statement in all ctors (if all ctors have the same statement) into an initializer.
bool allSame; bool allSame;
@ -170,7 +165,7 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
// '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;
allSame = true; allSame = true;
for (int i = 1; i < instanceCtorsNotChainingWithThis.Length; i++) { for (int i = 1; i < instanceCtorsNotChainingWithThis.Length; i++) {
var otherMatch = fieldInitializerPattern.Match(instanceCtorsNotChainingWithThis[i].Body.FirstOrDefault()); var otherMatch = fieldInitializerPattern.Match(instanceCtorsNotChainingWithThis[i].Body.FirstOrDefault());
@ -200,22 +195,34 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
} }
} }
internal void RemoveSingleEmptyConstructor(IEnumerable<AstNode> members, ITypeDefinition contextTypeDefinition) void RemoveSingleEmptyConstructor(IEnumerable<AstNode> members, ITypeDefinition contextTypeDefinition)
{ {
// if we're outside of a type definition skip this altogether
if (contextTypeDefinition == null) return; if (contextTypeDefinition == null) return;
// first get non-static constructor declarations from the AST
var instanceCtors = members.OfType<ConstructorDeclaration>().Where(c => (c.Modifiers & Modifiers.Static) == 0).ToArray(); var instanceCtors = members.OfType<ConstructorDeclaration>().Where(c => (c.Modifiers & Modifiers.Static) == 0).ToArray();
if (instanceCtors.Length == 1 && (members.Skip(1).Any() || instanceCtors[0].Parent is TypeDeclaration)) { // if there's exactly one ctor and it's part of a type declaration or there's more than one member in the current selection
ConstructorDeclaration emptyCtor = new ConstructorDeclaration(); // we can remove the constructor. (We do not want to hide the constructor if the user explicitly selected it in the tree view.)
emptyCtor.Modifiers = contextTypeDefinition.IsAbstract ? Modifiers.Protected : Modifiers.Public; if (instanceCtors.Length == 1 && (instanceCtors[0].Parent is TypeDeclaration || members.Skip(1).Any())) {
if (instanceCtors[0].HasModifier(Modifiers.Unsafe)) var ctor = instanceCtors[0];
emptyCtor.Modifiers |= Modifiers.Unsafe; // dynamically create a pattern of an empty ctor
emptyCtor.Body = new BlockStatement(); ConstructorDeclaration emptyCtorPattern = new ConstructorDeclaration();
if (emptyCtor.IsMatch(instanceCtors[0])) emptyCtorPattern.Modifiers = contextTypeDefinition.IsAbstract ? Modifiers.Protected : Modifiers.Public;
instanceCtors[0].Remove(); if (ctor.HasModifier(Modifiers.Unsafe))
emptyCtorPattern.Modifiers |= Modifiers.Unsafe;
emptyCtorPattern.Body = new BlockStatement();
if (emptyCtorPattern.IsMatch(ctor)) {
bool retainBecauseOfDocumentation = ctor.GetSymbol() is IMethod ctorMethod
&& context.Settings.ShowXmlDocumentation
&& context.DecompileRun.DocumentationProvider?.GetDocumentation(ctorMethod) != null;
if (!retainBecauseOfDocumentation)
ctor.Remove();
}
} }
} }
internal void HandleStaticFieldInitializers(IEnumerable<AstNode> members) void HandleStaticFieldInitializers(IEnumerable<AstNode> members)
{ {
// Translate static constructor into field initializers if the class is BeforeFieldInit // Translate static constructor into field initializers if the class is BeforeFieldInit
var staticCtor = members.OfType<ConstructorDeclaration>().FirstOrDefault(c => (c.Modifiers & Modifiers.Static) == Modifiers.Static); var staticCtor = members.OfType<ConstructorDeclaration>().FirstOrDefault(c => (c.Modifiers & Modifiers.Static) == Modifiers.Static);

2
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -271,7 +271,7 @@
<Compile Include="CSharp\ExpressionBuilder.cs" /> <Compile Include="CSharp\ExpressionBuilder.cs" />
<Compile Include="CSharp\StatementBuilder.cs" /> <Compile Include="CSharp\StatementBuilder.cs" />
<Compile Include="CSharp\Transforms\AddCheckedBlocks.cs" /> <Compile Include="CSharp\Transforms\AddCheckedBlocks.cs" />
<Compile Include="CSharp\Transforms\ConvertConstructorCallIntoInitializer.cs" /> <Compile Include="CSharp\Transforms\TransformFieldAndConstructorInitializers.cs" />
<Compile Include="CSharp\Transforms\CustomPatterns.cs" /> <Compile Include="CSharp\Transforms\CustomPatterns.cs" />
<Compile Include="CSharp\Transforms\IAstTransform.cs" /> <Compile Include="CSharp\Transforms\IAstTransform.cs" />
<Compile Include="CSharp\Transforms\IntroduceUnsafeModifier.cs" /> <Compile Include="CSharp\Transforms\IntroduceUnsafeModifier.cs" />

Loading…
Cancel
Save