#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

432 lines
15 KiB

//
// StatementsToInitializerConverter.cs
//
// Author:
// Simon Lindgren <simon.n.lindgren@gmail.com>
//
// Copyright (c) 2012 Simon Lindgren
//
// 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 ICSharpCode.NRefactory.CSharp.Refactoring;
using System.Collections.Generic;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.Semantics;
using System.Linq;
using ICSharpCode.NRefactory.CSharp.Resolver;
using System.Threading;
using System.Diagnostics;
namespace ICSharpCode.NRefactory.CSharp.Refactoring
{
public class StatementsToInitializerConverter
{
IDictionary<AccessPath, Expression> accessPaths = new Dictionary<AccessPath, Expression>();
IList<Comment> comments = new List<Comment>();
AccessPath mainAccessPath;
RefactoringContext context;
public StatementsToInitializerConverter(RefactoringContext context)
{
this.context = context;
}
bool Initialize(AstNode targetNode)
{
var target = context.Resolve(targetNode);
var targetInitializerPath = AccessPath.FromResolveResult(target);
if (targetInitializerPath == null)
return false;
mainAccessPath = targetInitializerPath;
return true;
}
public VariableInitializer ConvertToInitializer(VariableInitializer variableInitializer, ref IList<AstNode> statements)
{
if (variableInitializer == null)
throw new ArgumentNullException("variableInitializer");
if (statements == null)
throw new ArgumentNullException("statements");
if (!Initialize(variableInitializer))
return null;
accessPaths [mainAccessPath] = variableInitializer.Initializer.Clone();
Convert(statements);
statements = ReplacementNodeHelper.GetReplacedNodes(accessPaths [mainAccessPath]);
return new VariableInitializer(mainAccessPath.RootName, accessPaths [mainAccessPath]);
}
public AssignmentExpression ConvertToInitializer(AssignmentExpression assignmentExpression, ref IList<AstNode> statements)
{
if (assignmentExpression == null)
throw new ArgumentNullException("assignmentExpression");
if (statements == null)
throw new ArgumentNullException("statements");
if (!(assignmentExpression.Right is ObjectCreateExpression))
throw new ArgumentException("assignmentExpression.Right must be an ObjectCreateExpression", "assignmentExpression");
if (!Initialize(assignmentExpression.Left))
return null;
accessPaths [mainAccessPath] = assignmentExpression.Right.Clone();
Convert(statements);
statements = ReplacementNodeHelper.GetReplacedNodes(accessPaths [mainAccessPath]);
return new AssignmentExpression(new IdentifierExpression(mainAccessPath.RootName), accessPaths [mainAccessPath]);
}
void Convert(IList<AstNode> originalStatements)
{
foreach (var node in originalStatements) {
var comment = node as Comment;
if (comment != null) {
comments.Add((Comment)ReplacementNodeHelper.CloneWithReplacementAnnotation(comment, node));
continue;
}
var success = TryHandleInitializer(node);
if (success) {
continue;
}
var expressionStatement = node as ExpressionStatement;
if (expressionStatement == null)
break;
success = TryHandleAssignmentExpression(expressionStatement);
if (success) {
continue;
}
success = TryHandleAddCall(expressionStatement);
if (success) {
continue;
}
break;
}
}
bool TryHandleInitializer(AstNode node)
{
VariableInitializer variableInitializer;
var variableDeclarationStatement = node as VariableDeclarationStatement;
if (variableDeclarationStatement == null) {
return false;
}
variableInitializer = variableDeclarationStatement.Variables.FirstOrNullObject();
if (variableInitializer.IsNull || variableInitializer.Initializer.IsNull)
return false;
var sourceResolveResult = context.Resolve(variableInitializer.Initializer) as LocalResolveResult;
if (HasDependency(variableInitializer.Initializer) && !CanReplaceDependent(sourceResolveResult))
return false;
var targetResolveResult = context.Resolve(variableInitializer) as LocalResolveResult;
if (targetResolveResult == null)
return false;
AddNewVariable(targetResolveResult.Variable, variableInitializer.Initializer, node);
return true;
}
bool TryHandleAddCall(ExpressionStatement expressionStatement)
{
var invocationExpression = expressionStatement.Expression as InvocationExpression;
if (invocationExpression == null)
return false;
var invocationResolveResult = context.Resolve(invocationExpression) as InvocationResolveResult;
if (invocationResolveResult == null)
return false;
if (invocationResolveResult.Member.Name != "Add")
return false;
var targetResult = invocationResolveResult.TargetResult;
if (targetResult is MemberResolveResult)
return false;
var tuple = new ArrayInitializerExpression();
foreach (var argument in invocationExpression.Arguments) {
var argumentLocalResolveResult = context.Resolve(argument) as LocalResolveResult;
if (argumentLocalResolveResult != null) {
var initializerPath = AccessPath.FromResolveResult(argumentLocalResolveResult);
if (initializerPath == null || !accessPaths.ContainsKey(initializerPath))
return false;
// Add a clone, since we do not yet know if this is where the initializer will be used
var initializerClone = accessPaths[initializerPath].Clone();
tuple.Elements.Add(initializerClone);
} else {
tuple.Elements.Add(argument.Clone());
}
}
ReplacementNodeHelper.AddReplacementAnnotation(tuple, expressionStatement);
var targetPath = AccessPath.FromResolveResult(targetResult);
if (targetPath == null || !accessPaths.ContainsKey(targetPath))
return false;
InsertImplicitInitializersForPath(targetPath);
var targetInitializer = accessPaths [targetPath];
AddToInitializer(targetInitializer, tuple);
return true;
}
bool TryHandleAssignmentExpression(ExpressionStatement expressionStatement)
{
var assignmentExpression = expressionStatement.Expression as AssignmentExpression;
if (assignmentExpression == null || assignmentExpression.Operator != AssignmentOperatorType.Assign)
return false;
var resolveResult = context.Resolve(assignmentExpression.Right);
if (HasDependency(assignmentExpression.Right) && !CanReplaceDependent(resolveResult))
return false;
var success = PushAssignment(assignmentExpression.Left, assignmentExpression.Right, expressionStatement);
return success;
}
bool CanReplaceDependent(ResolveResult resolveResult)
{
return resolveResult is LocalResolveResult;
}
void AddNewVariable(IVariable variable, Expression initializer, AstNode node)
{
var variablePath = new AccessPath(variable);
var rightResolveResult = context.Resolve(initializer) as LocalResolveResult;
if (rightResolveResult != null) {
var rightPath = AccessPath.FromResolveResult(rightResolveResult);
if (rightPath != null && accessPaths.ContainsKey(rightPath)) {
var rightInitializer = accessPaths [rightPath];
ReplacementNodeHelper.AddReplacementAnnotation(rightInitializer, node);
accessPaths.Remove(rightPath);
accessPaths [variablePath] = rightInitializer;
if (rightPath == mainAccessPath) {
mainAccessPath = variablePath;
}
}
} else {
accessPaths [variablePath] = ReplacementNodeHelper.CloneWithReplacementAnnotation(initializer, node);
}
}
void AddOldAnnotationsToInitializer(AccessPath targetPath, IAnnotatable initializer)
{
if (targetPath != null) {
if (accessPaths.ContainsKey(targetPath)) {
foreach (var astNode in ReplacementNodeHelper.GetAllReplacementAnnotations(accessPaths[targetPath])) {
initializer.AddAnnotation(astNode);
}
}
}
}
bool PushAssignment(Expression left, Expression right, AstNode node)
{
var rightResolveResult = context.Resolve(right) as LocalResolveResult;
var leftResolveResult = context.Resolve(left);
Expression initializer;
if (rightResolveResult != null) {
var rightPath = AccessPath.FromResolveResult(rightResolveResult);
if (accessPaths.ContainsKey(rightPath)) {
initializer = accessPaths [rightPath];
} else {
initializer = right.Clone();
}
} else {
initializer = right.Clone();
}
var leftPath = AccessPath.FromResolveResult(leftResolveResult);
if (leftPath == null) {
return false;
}
// Move replacement annotations over, in case this is the second assignment
// to the same variable/member.
AddOldAnnotationsToInitializer(leftPath, initializer);
if (leftPath.PartCount == 1) {
ReplacementNodeHelper.AddReplacementAnnotation(initializer, node);
accessPaths [leftPath] = initializer;
return true;
}
if (!(leftResolveResult is MemberResolveResult))
return false;
Debug.Assert(leftPath.PartCount > 1, "No top level assignment should get here.");
var parentKey = leftPath.GetParentPath();
var member = leftPath.MemberPath.Last();
var success = InsertImplicitInitializersForPath(parentKey);
if (!success)
return false;
var parentInitializer = accessPaths [parentKey];
AddToInitializer(parentInitializer, comments.ToArray());
comments.Clear();
AddToInitializer(parentInitializer, new NamedExpression(member.Name, initializer));
ReplacementNodeHelper.AddReplacementAnnotation(initializer, node);
accessPaths [leftPath] = initializer;
return true;
}
static void AddNodesToInitializer(Expression initializer, params AstNode[] nodes)
{
foreach (var node in nodes) {
if (node is Expression) {
initializer.AddChild((Expression)node, Roles.Expression);
} else if (node is Comment) {
initializer.AddChild((Comment)node, Roles.Comment);
}
}
}
void AddToInitializer(Expression initializer, params AstNode[] nodes)
{
if (initializer is ArrayInitializerExpression) {
var arrayInitializerExpression = (ArrayInitializerExpression)initializer;
AddNodesToInitializer(arrayInitializerExpression, nodes);
} else if (initializer is ObjectCreateExpression) {
var objectCreateExpression = (ObjectCreateExpression)initializer;
if (objectCreateExpression.Initializer.IsNull)
objectCreateExpression.Initializer = new ArrayInitializerExpression();
AddNodesToInitializer(objectCreateExpression.Initializer, nodes);
}
}
bool HasDependency(Expression expression)
{
var referenceFinder = new FindReferences();
return HasDependency(referenceFinder, expression);
}
bool HasDependency(FindReferences referenceFinder, Expression expression)
{
if (HasDependencyCheck(referenceFinder, expression))
return true;
var queue = new Queue<ResolveResult>();
queue.Enqueue(context.Resolve(expression));
do {
var result = queue.Dequeue();
if (result is LocalResolveResult && HasDependencyCheck(referenceFinder, (LocalResolveResult)result)) {
return true;
}
foreach (var childResult in result.GetChildResults()) {
queue.Enqueue(childResult);
}
} while (queue.Count > 0);
return false;
}
bool HasDependencyCheck(FindReferences referenceFinder, LocalResolveResult localResolveResult)
{
bool result = false;
referenceFinder.FindLocalReferences(localResolveResult.Variable, context.UnresolvedFile,
(SyntaxTree)context.RootNode, context.Compilation,
(node, resolveResult) => {
result |= VariableHasBeenConverted(localResolveResult.Variable);
}, CancellationToken.None);
return result;
}
bool HasDependencyCheck(FindReferences referenceFinder, Expression expression)
{
var memberReferences = from exp in expression.DescendantsAndSelf
let memberReference = exp as MemberReferenceExpression
where memberReference != null
select memberReference;
foreach (var memberReference in memberReferences) {
var resolveResult = context.Resolve(memberReference) as MemberResolveResult;
if (resolveResult == null)
continue;
var initializerPath = AccessPath.FromResolveResult(resolveResult);
if (initializerPath != null && accessPaths.ContainsKey(initializerPath))
return true;
}
return false;
}
bool VariableHasBeenConverted(IVariable variable)
{
if (variable == null)
throw new ArgumentNullException("variable");
return accessPaths.Any(item => item.Key != null && item.Key.VariableRoot != null && item.Key.VariableRoot.Equals(variable));
}
bool InsertImplicitInitializersForPath(AccessPath path)
{
if (accessPaths.ContainsKey(path))
return true;
if (path.MemberPath.Count == 0)
return false;
var parentPath = path.GetParentPath();
var success = InsertImplicitInitializersForPath(parentPath);
if (!success)
return false;
var parentInitializer = accessPaths [parentPath];
var initializer = new ArrayInitializerExpression();
var namedExpression = new NamedExpression(path.MemberPath [path.MemberPath.Count - 1].Name, initializer);
AddToInitializer(parentInitializer, namedExpression);
accessPaths [path] = initializer;
return true;
}
}
class ReplacementNodeAnnotation
{
public AstNode ReplacedNode { get; set; }
}
class ReplacementNodeHelper
{
public static void AddReplacementAnnotation(AstNode node, AstNode replacedNode)
{
node.AddAnnotation(new ReplacementNodeAnnotation {
ReplacedNode = replacedNode
});
}
public static AstNode CloneWithReplacementAnnotation(AstNode node, AstNode replacedNode)
{
var newNode = node.Clone();
AddReplacementAnnotation(newNode, replacedNode);
return newNode;
}
public static Expression CloneWithReplacementAnnotation(Expression expression, AstNode replacedNode)
{
var newExpression = expression.Clone();
AddReplacementAnnotation(newExpression, replacedNode);
return newExpression;
}
public static IEnumerable<ReplacementNodeAnnotation> GetAllReplacementAnnotations(AstNode node)
{
return
from n in node.DescendantsAndSelf
from annotation in n.Annotations
let replacementAnnotation = annotation as ReplacementNodeAnnotation
where replacementAnnotation != null
select replacementAnnotation;
}
public static IList<AstNode> GetReplacedNodes(AstNode expression)
{
return GetAllReplacementAnnotations(expression)
.Select(a => a.ReplacedNode)
.ToList();
}
}
}