diff --git a/ICSharpCode.Decompiler/Ast/DeclareVariableInSmallestScope.cs b/ICSharpCode.Decompiler/Ast/DeclareVariableInSmallestScope.cs index bd4150537..ba60db563 100644 --- a/ICSharpCode.Decompiler/Ast/DeclareVariableInSmallestScope.cs +++ b/ICSharpCode.Decompiler/Ast/DeclareVariableInSmallestScope.cs @@ -26,17 +26,21 @@ namespace Decompiler /// The type of the new variable /// The name of the new variable /// Whether the variable is allowed to be placed inside a loop - public static void DeclareVariable(AstNode node, AstType type, string name, bool allowPassIntoLoops = true) + public static VariableDeclarationStatement DeclareVariable(AstNode node, AstType type, string name, bool allowPassIntoLoops = true) { + VariableDeclarationStatement result = null; AstNode pos = FindInsertPos(node, name, allowPassIntoLoops); if (pos != null) { Match m = assignmentPattern.Match(pos); if (m != null && m.Get("ident").Single().Identifier == name) { - pos.ReplaceWith(new VariableDeclarationStatement(type, name, m.Get("init").Single().Detach())); + result = new VariableDeclarationStatement(type, name, m.Get("init").Single().Detach()); + pos.ReplaceWith(result); } else { - pos.Parent.InsertChildBefore(pos, new VariableDeclarationStatement(type, name), BlockStatement.StatementRole); + result = new VariableDeclarationStatement(type, name); + pos.Parent.InsertChildBefore(pos, result, BlockStatement.StatementRole); } } + return result; } static AstNode FindInsertPos(AstNode node, string name, bool allowPassIntoLoops) diff --git a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs index 1909350b1..d5bf7d8b2 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/DelegateConstruction.cs @@ -32,6 +32,10 @@ namespace Decompiler.Transforms } } + internal sealed class CapturedVariableAnnotation + { + } + public DelegateConstruction(DecompilerContext context) : base(context) { } @@ -240,7 +244,9 @@ namespace Decompiler.Transforms } // Now insert the variable declarations (we can do this after the replacements only so that the scope detection works): foreach (var tuple in variablesToDeclare) { - DeclareVariableInSmallestScope.DeclareVariable(blockStatement, tuple.Item1, tuple.Item2, allowPassIntoLoops: false); + var newVarDecl = DeclareVariableInSmallestScope.DeclareVariable(blockStatement, tuple.Item1, tuple.Item2, allowPassIntoLoops: false); + if (newVarDecl != null) + newVarDecl.Variables.Single().AddAnnotation(new CapturedVariableAnnotation()); } } return null; diff --git a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs index ebce4a4db..0207b60a7 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs @@ -103,25 +103,23 @@ namespace Decompiler.Transforms ).ToVariable() } }, - EmbeddedStatement = new BlockStatement { - new ForStatement { - EmbeddedStatement = new BlockStatement { - new IfElseStatement { - Condition = new UnaryOperatorExpression( - UnaryOperatorType.Not, - new NamedNode("enumeratorIdent", new IdentifierExpression()).ToExpression().Invoke("MoveNext") - ), - TrueStatement = new BlockStatement { - new BreakStatement() - }, - FalseStatement = new BlockStatement { + EmbeddedStatement = new Choice { + // There are two forms of the foreach statement: + // one where the item variable is declared inside the loop, + // and one where it is declared outside of the loop. + // In the former case, we can apply the foreach pattern only if the variable wasn't captured. + { "itemVariableInsideLoop", + new BlockStatement { + new WhileStatement { + Condition = new IdentifierExpressionBackreference("enumeratorVariable").ToExpression().Invoke("MoveNext"), + EmbeddedStatement = new BlockStatement { new VariableDeclarationStatement { Type = new AnyNode("itemType").ToType(), Variables = { new NamedNode( "itemVariable", new VariableInitializer { - Initializer = new Backreference("enumeratorIdent").ToExpression().Member("Current") + Initializer = new IdentifierExpressionBackreference("enumeratorVariable").ToExpression().Member("Current") } ).ToVariable() } @@ -130,8 +128,29 @@ namespace Decompiler.Transforms } } } + }, + { "itemVariableOutsideLoop", + new BlockStatement { + new VariableDeclarationStatement { + Type = new AnyNode("itemType").ToType(), + Variables = { + new NamedNode("itemVariable", new VariableInitializer()).ToVariable() + } + }, + new WhileStatement { + Condition = new IdentifierExpressionBackreference("enumeratorVariable").ToExpression().Invoke("MoveNext"), + EmbeddedStatement = new BlockStatement { + new AssignmentExpression { + Left = new IdentifierExpressionBackreference("itemVariable").ToExpression(), + Operator = AssignmentOperatorType.Assign, + Right = new IdentifierExpressionBackreference("enumeratorVariable").ToExpression().Member("Current") + }, + new Repeat(new AnyNode("statement")).ToStatement() + } + } + } } - } + }.ToStatement() }; public void TransformForeach(AstNode compilationUnit) @@ -141,16 +160,18 @@ namespace Decompiler.Transforms if (m == null) continue; VariableInitializer enumeratorVar = m.Get("enumeratorVariable").Single(); - if (enumeratorVar.Name != m.Get("enumeratorIdent").Single().Identifier) - continue; VariableInitializer itemVar = m.Get("itemVariable").Single(); + if (m.Has("itemVariableInsideLoop") && itemVar.Annotation() != null) { + // cannot move captured variables out of loops + continue; + } BlockStatement newBody = new BlockStatement(); foreach (Statement stmt in m.Get("statement")) newBody.Add(stmt.Detach()); node.ReplaceWith( new ForeachStatement { VariableType = m.Get("itemType").Single().Detach(), - VariableName = enumeratorVar.Name, + VariableName = itemVar.Name, InExpression = m.Get("collection").Single().Detach(), EmbeddedStatement = newBody });