// Copyright (c) 2011 AlphaSierraPapa for the SharpDevelop Team
//
// 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.Diagnostics;
using System.Linq;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Syntax.PatternMatching;
using ICSharpCode.Decompiler.CSharp.TypeSystem;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.CSharp.Analysis;
using Mono.Cecil;
using ICSharpCode.Decompiler.Semantics;
namespace ICSharpCode.Decompiler.CSharp.Transforms
{
///
/// Finds the expanded form of using statements using pattern matching and replaces it with a UsingStatement.
///
public sealed class PatternStatementTransform : ContextTrackingVisitor, IAstTransform
{
TransformContext context;
public void Run(AstNode rootNode, TransformContext context)
{
this.context = context;
base.Initialize(context);
rootNode.AcceptVisitor(this);
}
#region Visitor Overrides
protected override AstNode VisitChildren(AstNode node)
{
// Go through the children, and keep visiting a node as long as it changes.
// Because some transforms delete/replace nodes before and after the node being transformed, we rely
// on the transform's return value to know where we need to keep iterating.
for (AstNode child = node.FirstChild; child != null; child = child.NextSibling) {
AstNode oldChild;
do {
oldChild = child;
child = child.AcceptVisitor(this);
Debug.Assert(child != null && child.Parent == node);
} while (child != oldChild);
}
return node;
}
public override AstNode VisitExpressionStatement(ExpressionStatement expressionStatement)
{
AstNode result;
if (context.Settings.UsingStatement)
{
result = TransformNonGenericForEach(expressionStatement);
if (result != null)
return result;
result = TransformUsings(expressionStatement);
if (result != null)
return result;
}
result = TransformFor(expressionStatement);
if (result != null)
return result;
if (context.Settings.LockStatement) {
result = TransformLock(expressionStatement);
if (result != null)
return result;
}
if (context.Settings.AutomaticProperties) {
result = ReplaceBackingFieldUsage(expressionStatement);
if (result != null)
return result;
}
if (context.Settings.AutomaticEvents) {
result = ReplaceEventFieldAnnotation(expressionStatement);
if (result != null)
return result;
}
return base.VisitExpressionStatement(expressionStatement);
}
public override AstNode VisitUsingStatement(UsingStatement usingStatement)
{
if (context.Settings.ForEachStatement) {
AstNode result = TransformForeach(usingStatement);
if (result != null)
return result;
}
return base.VisitUsingStatement(usingStatement);
}
public override AstNode VisitWhileStatement(WhileStatement whileStatement)
{
return TransformDoWhile(whileStatement) ?? base.VisitWhileStatement(whileStatement);
}
public override AstNode VisitIfElseStatement(IfElseStatement ifElseStatement)
{
if (context.Settings.SwitchStatementOnString) {
AstNode result = TransformSwitchOnString(ifElseStatement);
if (result != null)
return result;
}
AstNode simplifiedIfElse = SimplifyCascadingIfElseStatements(ifElseStatement);
if (simplifiedIfElse != null)
return simplifiedIfElse;
return base.VisitIfElseStatement(ifElseStatement);
}
public override AstNode VisitPropertyDeclaration(PropertyDeclaration propertyDeclaration)
{
if (context.Settings.AutomaticProperties) {
AstNode result = TransformAutomaticProperties(propertyDeclaration);
if (result != null)
return result;
}
return base.VisitPropertyDeclaration(propertyDeclaration);
}
public override AstNode VisitCustomEventDeclaration(CustomEventDeclaration eventDeclaration)
{
// first apply transforms to the accessor bodies
base.VisitCustomEventDeclaration(eventDeclaration);
if (context.Settings.AutomaticEvents) {
AstNode result = TransformAutomaticEvents(eventDeclaration);
if (result != null)
return result;
}
return eventDeclaration;
}
public override AstNode VisitMethodDeclaration(MethodDeclaration methodDeclaration)
{
return TransformDestructor(methodDeclaration) ?? base.VisitMethodDeclaration(methodDeclaration);
}
public override AstNode VisitTryCatchStatement(TryCatchStatement tryCatchStatement)
{
return TransformTryCatchFinally(tryCatchStatement) ?? base.VisitTryCatchStatement(tryCatchStatement);
}
#endregion
///
/// $variable = $initializer;
///
static readonly AstNode variableAssignPattern = new ExpressionStatement(
new AssignmentExpression(
new NamedNode("variable", new IdentifierExpression(Pattern.AnyString)),
new AnyNode("initializer")
));
#region using
static Expression InvokeDispose(Expression identifier)
{
return new Choice {
new InvocationExpression(new MemberReferenceExpression(identifier, "Dispose")),
new InvocationExpression(new MemberReferenceExpression(new CastExpression(new TypePattern(typeof(IDisposable)), identifier.Clone()), "Dispose"))
};
}
static readonly AstNode usingTryCatchPattern = new Choice {
{ "c#/vb",
new TryCatchStatement {
TryBlock = new AnyNode(),
FinallyBlock = new BlockStatement {
new Choice {
{ "valueType",
new ExpressionStatement(InvokeDispose(new NamedNode("ident", new IdentifierExpression(Pattern.AnyString))))
},
{ "referenceType",
new IfElseStatement {
Condition = new BinaryOperatorExpression(
new NamedNode("ident", new IdentifierExpression(Pattern.AnyString)),
BinaryOperatorType.InEquality,
new NullReferenceExpression()
),
TrueStatement = new BlockStatement {
new ExpressionStatement(InvokeDispose(new Backreference("ident")))
}
}
}
}.ToStatement()
}
}
},
{ "f#",
new TryCatchStatement {
TryBlock = new AnyNode(),
FinallyBlock =
new BlockStatement {
new ExpressionStatement(
new AssignmentExpression(left: new NamedNode("disposable", new IdentifierExpression(Pattern.AnyString)),
right: new AsExpression(expression: new NamedNode("ident", new IdentifierExpression(Pattern.AnyString)),
type: new TypePattern(typeof(IDisposable))
)
)
),
new IfElseStatement {
Condition = new BinaryOperatorExpression(
new Backreference("disposable"),
BinaryOperatorType.InEquality,
new NullReferenceExpression()
),
TrueStatement = new BlockStatement {
new ExpressionStatement(InvokeDispose(new Backreference("disposable")))
}
}
}
}
}
};
public UsingStatement TransformUsings(ExpressionStatement node)
{
// Conditions:
// 1. CaptureScope of the resource-variable must be either null or the BlockContainer of the using block.
// 2. The variable must not be used outside of the block.
// 3. The variable is only assigned once, right before the try-block.
Match m1 = variableAssignPattern.Match(node);
if (!m1.Success) return null;
TryCatchStatement tryCatch = node.NextSibling as TryCatchStatement;
Match m2 = usingTryCatchPattern.Match(tryCatch);
if (!m2.Success) return null;
IL.ILVariable variable = m1.Get("variable").Single().GetILVariable();
string variableName = m1.Get("variable").Single().Identifier;
if (variable == null || variableName != m2.Get("ident").Single().Identifier)
return null;
if (m2.Has("valueType")) {
// if there's no if(x!=null), then it must be a value type
if (variable.Type.IsReferenceType != false)
return null;
}
if (variable.StoreCount > 1 || !variable.Type.GetAllBaseTypes().Any(t => t.IsKnownType(KnownTypeCode.IDisposable)))
return null;
//if (m2.Has("f#")) {
// string variableNameDisposable = m2.Get("disposable").Single().Identifier;
// VariableDeclarationStatement varDeclDisposable = FindVariableDeclaration(node, variableNameDisposable);
// if (varDeclDisposable == null || !(varDeclDisposable.Parent is BlockStatement))
// return null;
// // Validate that the variable is not used after the using statement:
// if (!IsVariableValueUnused(varDeclDisposable, tryCatch))
// return null;
//}
node.Remove();
UsingStatement usingStatement = new UsingStatement();
usingStatement.EmbeddedStatement = tryCatch.TryBlock.Detach();
tryCatch.ReplaceWith(usingStatement);
// If possible, we'll eliminate the variable completely:
if (usingStatement.EmbeddedStatement.Descendants.OfType().Any(ident => ident.Identifier == variableName)) {
// variable is used, so we'll create a variable declaration
variable.Kind = IL.VariableKind.UsingLocal;
usingStatement.ResourceAcquisition = new VariableDeclarationStatement {
Type = variable.Type.ContainsAnonymousType() ? new SimpleType("var") : context.TypeSystemAstBuilder.ConvertType(variable.Type),
Variables = {
new VariableInitializer {
Name = variableName,
Initializer = m1.Get("initializer").Single().Detach()
}.CopyAnnotationsFrom(node.Expression)
.WithILVariable(variable)
}
}.CopyAnnotationsFrom(node);
} else {
// the variable is never used; eliminate it:
usingStatement.ResourceAcquisition = m1.Get("initializer").Single().Detach();
}
return usingStatement;
}
internal static VariableDeclarationStatement FindVariableDeclaration(AstNode node, string identifier)
{
while (node != null) {
while (node.PrevSibling != null) {
node = node.PrevSibling;
VariableDeclarationStatement varDecl = node as VariableDeclarationStatement;
if (varDecl != null && varDecl.Variables.Count == 1 && varDecl.Variables.Single().Name == identifier) {
return varDecl;
}
}
node = node.Parent;
}
return null;
}
///
/// Gets whether the old variable value (assigned inside 'targetStatement' or earlier)
/// is read anywhere in the remaining scope of the variable declaration.
///
bool IsVariableValueUnused(VariableDeclarationStatement varDecl, Statement targetStatement)
{
Debug.Assert(targetStatement.Ancestors.Contains(varDecl.Parent));
BlockStatement block = (BlockStatement)varDecl.Parent;
DefiniteAssignmentAnalysis daa = CreateDAA(block);
daa.SetAnalyzedRange(targetStatement, block, startInclusive: false);
daa.Analyze(varDecl.Variables.Single().Name);
return daa.UnassignedVariableUses.Count == 0;
}
// I used this in the first implementation of the using-statement transform, but now no longer
// because there were problems when multiple using statements were using the same variable
// - no single using statement could be transformed without making the C# code invalid,
// but transforming both would work.
// We now use 'IsVariableValueUnused' which will perform the transform
// even if it results in two variables with the same name and overlapping scopes.
// (this issue could be fixed later by renaming one of the variables)
private DefiniteAssignmentAnalysis CreateDAA(BlockStatement block)
{
var typeResolveContext = new CSharpTypeResolveContext(context.TypeSystem.MainAssembly);
return new DefiniteAssignmentAnalysis(block, (node, ct) => node.GetResolveResult(), typeResolveContext, context.CancellationToken);
}
///
/// Gets whether there is an assignment to 'variableName' anywhere within the given node.
///
bool HasAssignment(AstNode root, string variableName)
{
foreach (AstNode node in root.DescendantsAndSelf) {
IdentifierExpression ident = node as IdentifierExpression;
if (ident != null && ident.Identifier == variableName) {
if (ident.Parent is AssignmentExpression && ident.Role == AssignmentExpression.LeftRole
|| ident.Parent is DirectionExpression)
{
return true;
}
}
}
return false;
}
#endregion
#region foreach (generic)
static readonly UsingStatement genericForeachPattern = new UsingStatement {
ResourceAcquisition = new VariableDeclarationStatement {
Type = new AnyNode("enumeratorType"),
Variables = {
new NamedNode(
"enumeratorVariable",
new VariableInitializer {
Name = Pattern.AnyString,
Initializer = new InvocationExpression(new MemberReferenceExpression(new AnyNode("collection").ToExpression(), "GetEnumerator"))
}
)
}
},
EmbeddedStatement = new BlockStatement {
new Repeat(
new VariableDeclarationStatement { Type = new AnyNode(), Variables = { new VariableInitializer(Pattern.AnyString) } }.WithName("variablesOutsideLoop")
).ToStatement(),
new WhileStatement {
Condition = new InvocationExpression(new MemberReferenceExpression(new IdentifierExpressionBackreference("enumeratorVariable").ToExpression(), "MoveNext")),
EmbeddedStatement = new BlockStatement {
new Repeat(
new VariableDeclarationStatement {
Type = new AnyNode(),
Variables = { new VariableInitializer(Pattern.AnyString) }
}.WithName("variablesInsideLoop")
).ToStatement(),
new AssignmentExpression {
Left = new IdentifierExpression(Pattern.AnyString).WithName("itemVariable"),
Operator = AssignmentOperatorType.Assign,
Right = new MemberReferenceExpression(new IdentifierExpressionBackreference("enumeratorVariable").ToExpression(), "Current")
},
new Repeat(new AnyNode("statement")).ToStatement()
}
}.WithName("loop")
}};
public ForeachStatement TransformForeach(UsingStatement node)
{
Match m = genericForeachPattern.Match(node);
if (!m.Success)
return null;
if (!(node.Parent is BlockStatement) && m.Has("variablesOutsideLoop")) {
// if there are variables outside the loop, we need to put those into the parent block, and that won't work if the direct parent isn't a block
return null;
}
VariableInitializer enumeratorVar = m.Get("enumeratorVariable").Single();
var itemVar = m.Get("itemVariable").Single().GetILVariable();
WhileStatement loop = m.Get("loop").Single();
if (!VariableCanBeDeclaredInLoop(itemVar, loop)) {
return null;
}
// Make sure that the enumerator variable is not used inside the body
var enumeratorId = Identifier.Create(enumeratorVar.Name);
foreach (Statement stmt in m.Get("statement")) {
if (stmt.Descendants.OfType().Any(id => enumeratorId.IsMatch(id)))
return null;
}
BlockStatement newBody = new BlockStatement();
foreach (Statement stmt in m.Get("variablesInsideLoop"))
newBody.Add(stmt.Detach());
foreach (Statement stmt in m.Get("statement"))
newBody.Add(stmt.Detach());
itemVar.Kind = IL.VariableKind.ForeachLocal;
ForeachStatement foreachStatement = new ForeachStatement {
VariableType = itemVar.Type.ContainsAnonymousType() ? new SimpleType("var") : context.TypeSystemAstBuilder.ConvertType(itemVar.Type),
VariableName = itemVar.Name,
InExpression = m.Get("collection").Single().Detach(),
EmbeddedStatement = newBody
}.WithILVariable(itemVar);
foreachStatement.CopyAnnotationsFrom(loop);
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());
}
return foreachStatement;
}
static bool VariableCanBeDeclaredInLoop(IL.ILVariable itemVar, WhileStatement loop)
{
if (itemVar == null || !(itemVar.Kind == IL.VariableKind.Local || itemVar.Kind == IL.VariableKind.StackSlot)) {
// only locals/temporaries can be converted into foreach loop variable
return false;
}
var blockContainer = loop.Annotation();
if (!itemVar.IsSingleDefinition) {
// foreach variable cannot be assigned to.
// As a special case, we accept taking the address for a method call,
// but only if the call is the only use, so that any mutation by the call
// cannot be observed.
if (!AddressUsedForSingleCall(itemVar, blockContainer)) {
return false;
}
}
if (itemVar.CaptureScope != null && itemVar.CaptureScope != blockContainer) {
// captured variables cannot be declared in the loop unless the loop is their capture scope
return false;
}
return true;
}
static bool AddressUsedForSingleCall(IL.ILVariable v, IL.BlockContainer loop)
{
if (v.StoreCount == 1 && v.AddressCount == 1 && v.LoadCount == 0 && v.Type.IsReferenceType == false) {
if (v.AddressInstructions[0].Parent is IL.Call call
&& v.AddressInstructions[0].ChildIndex == 0
&& !call.Method.IsStatic) {
// used as this pointer for a method call
// this is OK iff the call is not within a nested loop
for (var node = call.Parent; node != null; node = node.Parent) {
if (node == loop)
return true;
else if (node is IL.BlockContainer)
break;
}
}
}
return false;
}
#endregion
#region foreach (non-generic)
ExpressionStatement getEnumeratorPattern = new ExpressionStatement(
new AssignmentExpression(
new NamedNode("left", new IdentifierExpression(Pattern.AnyString)),
new InvocationExpression(new MemberReferenceExpression(new AnyNode("collection").ToExpression(), "GetEnumerator"))
));
TryCatchStatement nonGenericForeachPattern = new TryCatchStatement {
TryBlock = new BlockStatement {
new WhileStatement {
Condition = new InvocationExpression(new MemberReferenceExpression(new IdentifierExpression(Pattern.AnyString).WithName("enumerator"), "MoveNext")),
EmbeddedStatement = new BlockStatement {
new AssignmentExpression(
new IdentifierExpression(Pattern.AnyString).WithName("itemVar"),
new Choice {
new MemberReferenceExpression(new Backreference("enumerator").ToExpression(), "Current"),
new CastExpression {
Type = new AnyNode("castType"),
Expression = new MemberReferenceExpression(new Backreference("enumerator").ToExpression(), "Current")
}
}
),
new Repeat(new AnyNode("stmt")).ToStatement()
}
}.WithName("loop")
},
FinallyBlock = new BlockStatement {
new AssignmentExpression(
new IdentifierExpression(Pattern.AnyString).WithName("disposable"),
new AsExpression(new Backreference("enumerator").ToExpression(), new TypePattern(typeof(IDisposable)))
),
new IfElseStatement {
Condition = new BinaryOperatorExpression {
Left = new Backreference("disposable"),
Operator = BinaryOperatorType.InEquality,
Right = new NullReferenceExpression()
},
TrueStatement = new BlockStatement {
new InvocationExpression(new MemberReferenceExpression(new Backreference("disposable").ToExpression(), "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();
var itemVar = m2.Get("itemVar").Single().GetILVariable();
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;
if (!VariableCanBeDeclaredInLoop(itemVar, loop))
return null;
itemVar.Kind = IL.VariableKind.ForeachLocal;
ForeachStatement foreachStatement = new ForeachStatement
{
VariableType = itemVar.Type.ContainsAnonymousType() ? new SimpleType("var") : context.TypeSystemAstBuilder.ConvertType(itemVar.Type),
VariableName = itemVar.Name,
}.WithILVariable(itemVar);
BlockStatement body = new BlockStatement();
foreachStatement.EmbeddedStatement = body;
foreachStatement.CopyAnnotationsFrom(loop);
((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.Detach());
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 {
Left = new NamedNode("ident", new IdentifierExpression(Pattern.AnyString)),
Operator = BinaryOperatorType.Any,
Right = new AnyNode("endExpr")
},
EmbeddedStatement = new BlockStatement {
Statements = {
new Repeat(new AnyNode("statement")),
new NamedNode(
"increment",
new ExpressionStatement(
new AssignmentExpression {
Left = new Backreference("ident"),
Operator = AssignmentOperatorType.Any,
Right = new AnyNode()
}))
}
}};
public ForStatement TransformFor(ExpressionStatement node)
{
Match m1 = variableAssignPattern.Match(node);
if (!m1.Success) return null;
var variableName = m1.Get("variable").Single().Identifier;
AstNode next = node.NextSibling;
if (next is ForStatement forStatement) {
if ((forStatement.Iterators.FirstOrDefault() is ExpressionStatement stmt
&& stmt.Expression is AssignmentExpression assign
&& variableName == assign.Left.ToString())
|| (forStatement.Condition is BinaryOperatorExpression cond
&& variableName == cond.Left.ToString()))
{
node.Remove();
forStatement.InsertChildAfter(null, node, ForStatement.InitializerRole);
return forStatement;
}
}
Match m3 = forPattern.Match(next);
if (!m3.Success) return null;
// ensure the variable in the for pattern is the same as in the declaration
if (variableName != m3.Get("ident").Single().Identifier)
return null;
WhileStatement loop = (WhileStatement)next;
node.Remove();
BlockStatement newBody = new BlockStatement();
foreach (Statement stmt in m3.Get("statement"))
newBody.Add(stmt.Detach());
forStatement = new ForStatement();
forStatement.CopyAnnotationsFrom(loop);
forStatement.Initializers.Add(node);
forStatement.Condition = loop.Condition.Detach();
forStatement.Iterators.Add(m3.Get("increment").Single().Detach());
forStatement.EmbeddedStatement = newBody;
loop.ReplaceWith(forStatement);
return forStatement;
}
#endregion
#region doWhile
static readonly WhileStatement doWhilePattern = new WhileStatement {
Condition = new PrimitiveExpression(true),
EmbeddedStatement = new BlockStatement {
Statements = {
new Repeat(new AnyNode("statement")),
new IfElseStatement {
Condition = new AnyNode("condition"),
TrueStatement = new BlockStatement { new BreakStatement() }
}
}
}};
public DoWhileStatement TransformDoWhile(WhileStatement whileLoop)
{
Match m = doWhilePattern.Match(whileLoop);
if (m.Success) {
DoWhileStatement doLoop = new DoWhileStatement();
doLoop.Condition = new UnaryOperatorExpression(UnaryOperatorType.Not, m.Get("condition").Single().Detach());
//doLoop.Condition.AcceptVisitor(new PushNegation(), null);
BlockStatement block = (BlockStatement)whileLoop.EmbeddedStatement;
block.Statements.Last().Remove(); // remove if statement
doLoop.EmbeddedStatement = block.Detach();
doLoop.CopyAnnotationsFrom(whileLoop);
whileLoop.ReplaceWith(doLoop);
// we may have to extract variable definitions out of the loop if they were used in the condition:
foreach (var varDecl in block.Statements.OfType()) {
VariableInitializer v = varDecl.Variables.Single();
if (doLoop.Condition.DescendantsAndSelf.OfType().Any(i => i.Identifier == v.Name)) {
AssignmentExpression assign = new AssignmentExpression(new IdentifierExpression(v.Name), v.Initializer.Detach());
// move annotations from v to assign:
assign.CopyAnnotationsFrom(v);
v.RemoveAnnotations