diff --git a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs index e8c619143..cd5a7eded 100644 --- a/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs +++ b/ICSharpCode.Decompiler/Ast/Transforms/PatternStatementTransform.cs @@ -47,6 +47,9 @@ namespace ICSharpCode.Decompiler.Ast.Transforms result = TransformUsings(expressionStatement); if (result != null) return result; + result = TransformNonGenericForEach(expressionStatement); + if (result != null) + return result; } result = TransformFor(expressionStatement); if (result != null) @@ -118,7 +121,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms #endregion /// - /// $type $variable = $initializer; + /// $variable = $initializer; /// static readonly AstNode variableAssignPattern = new ExpressionStatement( new AssignmentExpression( @@ -235,8 +238,8 @@ namespace ICSharpCode.Decompiler.Ast.Transforms } #endregion - #region foreach - static readonly UsingStatement foreachPattern = new UsingStatement { + #region foreach (generic) + static readonly UsingStatement genericForeachPattern = new UsingStatement { ResourceAcquisition = new VariableDeclarationStatement { Type = new AnyNode("enumeratorType"), Variables = { @@ -270,7 +273,7 @@ namespace ICSharpCode.Decompiler.Ast.Transforms public ForeachStatement TransformForeach(UsingStatement node) { - Match m = foreachPattern.Match(node); + Match m = genericForeachPattern.Match(node); if (!m.Success) return null; if (!(node.Parent is BlockStatement) && m.Has("variablesOutsideLoop")) { @@ -308,8 +311,9 @@ namespace ICSharpCode.Decompiler.Ast.Transforms InExpression = m.Get("collection").Single().Detach(), EmbeddedStatement = newBody }; - if (foreachStatement.InExpression is BaseReferenceExpression) - foreachStatement.InExpression = new ThisReferenceExpression(); + if (foreachStatement.InExpression is BaseReferenceExpression) { + foreachStatement.InExpression = new ThisReferenceExpression().CopyAnnotationsFrom(foreachStatement.InExpression); + } node.ReplaceWith(foreachStatement); foreach (Statement stmt in m.Get("variablesOutsideLoop")) { ((BlockStatement)foreachStatement.Parent).Statements.InsertAfter(null, stmt.Detach()); @@ -318,6 +322,117 @@ namespace ICSharpCode.Decompiler.Ast.Transforms } #endregion + #region foreach (non-generic) + ExpressionStatement getEnumeratorPattern = new ExpressionStatement( + new AssignmentExpression( + new NamedNode("left", new IdentifierExpression()), + new AnyNode("collection").ToExpression().Invoke("GetEnumerator") + )); + + TryCatchStatement nonGenericForeachPattern = new TryCatchStatement { + TryBlock = new BlockStatement { + new WhileStatement { + Condition = new IdentifierExpression().WithName("enumerator").Invoke("MoveNext"), + EmbeddedStatement = new BlockStatement { + new AssignmentExpression( + new IdentifierExpression().WithName("itemVar"), + new Choice { + new Backreference("enumerator").ToExpression().Member("Current"), + new CastExpression { + Type = new AnyNode("castType"), + Expression = new Backreference("enumerator").ToExpression().Member("Current") + } + } + ), + new Repeat(new AnyNode("stmt")).ToStatement() + } + }.WithName("loop") + }, + FinallyBlock = new BlockStatement { + new AssignmentExpression( + new IdentifierExpression().WithName("disposable"), + new Backreference("enumerator").ToExpression().CastAs(new TypePattern(typeof(IDisposable))) + ), + new IfElseStatement { + Condition = new BinaryOperatorExpression { + Left = new Backreference("disposable"), + Operator = BinaryOperatorType.InEquality, + Right = new NullReferenceExpression() + }, + TrueStatement = new BlockStatement { + new Backreference("disposable").ToExpression().Invoke("Dispose") + } + } + }}; + + public ForeachStatement TransformNonGenericForEach(ExpressionStatement node) + { + Match m1 = getEnumeratorPattern.Match(node); + if (!m1.Success) return null; + AstNode tryCatch = node.NextSibling; + Match m2 = nonGenericForeachPattern.Match(tryCatch); + if (!m2.Success) return null; + + IdentifierExpression enumeratorVar = m2.Get("enumerator").Single(); + IdentifierExpression itemVar = m2.Get("itemVar").Single(); + WhileStatement loop = m2.Get("loop").Single(); + + // verify that the getEnumeratorPattern assigns to the same variable as the nonGenericForeachPattern is reading from + if (!enumeratorVar.IsMatch(m1.Get("left").Single())) + return null; + + VariableDeclarationStatement enumeratorVarDecl = FindVariableDeclaration(loop, enumeratorVar.Identifier); + if (enumeratorVarDecl == null || !(enumeratorVarDecl.Parent is BlockStatement)) + return null; + + // Find the declaration of the item variable: + // Because we look only outside the loop, we won't make the mistake of moving a captured variable across the loop boundary + VariableDeclarationStatement itemVarDecl = FindVariableDeclaration(loop, itemVar.Identifier); + if (itemVarDecl == null || !(itemVarDecl.Parent is BlockStatement)) + return null; + + // Now verify that we can move the variable declaration in front of the loop: + Statement declarationPoint; + CanMoveVariableDeclarationIntoStatement(itemVarDecl, loop, out declarationPoint); + // We ignore the return value because we don't care whether we can move the variable into the loop + // (that is possible only with non-captured variables). + // We just care that we can move it in front of the loop: + if (declarationPoint != loop) + return null; + + ForeachStatement foreachStatement = new ForeachStatement(); + foreachStatement.VariableType = itemVarDecl.Type.Clone(); + foreachStatement.VariableName = itemVar.Identifier; + BlockStatement body = new BlockStatement(); + foreachStatement.EmbeddedStatement = body; + ((BlockStatement)node.Parent).Statements.InsertBefore(node, foreachStatement); + + body.Add(node.Detach()); + body.Add((Statement)tryCatch.Detach()); + + // Now that we moved the whole try-catch into the foreach loop; verify that we can + // move the enumerator into the foreach loop: + CanMoveVariableDeclarationIntoStatement(enumeratorVarDecl, foreachStatement, out declarationPoint); + if (declarationPoint != foreachStatement) { + // oops, the enumerator variable can't be moved into the foreach loop + // Undo our AST changes: + ((BlockStatement)foreachStatement.Parent).Statements.InsertBefore(foreachStatement, node); + foreachStatement.ReplaceWith(tryCatch); + return null; + } + + // Now create the correct body for the foreach statement: + foreachStatement.InExpression = m1.Get("collection").Single().Detach(); + if (foreachStatement.InExpression is BaseReferenceExpression) { + foreachStatement.InExpression = new ThisReferenceExpression().CopyAnnotationsFrom(foreachStatement.InExpression); + } + body.Statements.Clear(); + body.Statements.AddRange(m2.Get("stmt").Select(stmt => stmt.Detach())); + + return foreachStatement; + } + #endregion + #region for static readonly WhileStatement forPattern = new WhileStatement { Condition = new BinaryOperatorExpression { diff --git a/ICSharpCode.Decompiler/Tests/Loops.cs b/ICSharpCode.Decompiler/Tests/Loops.cs index fa96e28f4..6a88b4f81 100644 --- a/ICSharpCode.Decompiler/Tests/Loops.cs +++ b/ICSharpCode.Decompiler/Tests/Loops.cs @@ -2,6 +2,7 @@ // This code is distributed under MIT X11 license (for details please see \doc\license.txt) using System; +using System.Collections; using System.Collections.Generic; public class Loops @@ -23,14 +24,30 @@ public class Loops } } - public void ForEachOverArray(string[] array) + public void ForEachOverNonGenericEnumerable(IEnumerable enumerable) { - foreach (string text in array) + foreach (object current in enumerable) { - text.ToLower(); + current.ToString(); } } + public void ForEachOverNonGenericEnumerableWithAutomaticCast(IEnumerable enumerable) + { + foreach (int num in enumerable) + { + num.ToString(); + } + } + +// public void ForEachOverArray(string[] array) +// { +// foreach (string text in array) +// { +// text.ToLower(); +// } +// } + public void ForOverArray(string[] array) { for (int i = 0; i < array.Length; i++) diff --git a/ICSharpCode.Decompiler/Tests/TestRunner.cs b/ICSharpCode.Decompiler/Tests/TestRunner.cs index 23a7651d4..eb6261f7e 100644 --- a/ICSharpCode.Decompiler/Tests/TestRunner.cs +++ b/ICSharpCode.Decompiler/Tests/TestRunner.cs @@ -61,7 +61,7 @@ namespace ICSharpCode.Decompiler.Tests TestFile(@"..\..\Tests\InitializerTests.cs"); } - [Test, Ignore("ForEachOverArray not supported")] + [Test] public void Loops() { TestFile(@"..\..\Tests\Loops.cs");