.NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform!
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.
 
 
 
 

193 lines
6.9 KiB

//
// ConvertIfToSwitchAction.cs
//
// Author:
// Mansheng Yang <lightyang0@gmail.com>
//
// Copyright (c) 2012 Mansheng Yang <lightyang0@gmail.com>
//
// 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.Linq;
using System.Collections.Generic;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.PatternMatching;
namespace ICSharpCode.NRefactory.CSharp.Refactoring
{
[ContextAction ("Convert 'if' to 'switch'", Description = "Convert 'if' statement to 'switch' statement")]
public class ConvertIfToSwitchAction : SpecializedCodeAction<IfElseStatement>
{
protected override CodeAction GetAction (RefactoringContext context, IfElseStatement node)
{
if (!node.IfToken.Contains (context.Location))
return null;
var switchExpr = GetSwitchExpression (context, node.Condition);
if (switchExpr == null)
return null;
var switchSections = new List<SwitchSection> ();
if (!CollectSwitchSections (switchSections, context, node, switchExpr))
return null;
return new CodeAction (context.TranslateString ("Convert 'if' to 'switch'"),
script =>
{
var switchStatement = new SwitchStatement { Expression = switchExpr.Clone () };
switchStatement.SwitchSections.AddRange (switchSections);
script.Replace (node, switchStatement);
});
}
static Expression GetSwitchExpression (RefactoringContext context, Expression expr)
{
var binaryOp = expr as BinaryOperatorExpression;
if (binaryOp == null)
return null;
if (binaryOp.Operator == BinaryOperatorType.ConditionalOr)
return GetSwitchExpression (context, binaryOp.Left);
if (binaryOp.Operator == BinaryOperatorType.Equality) {
Expression switchExpr = null;
if (IsConstantExpression (context, binaryOp.Right))
switchExpr = binaryOp.Left;
if (IsConstantExpression (context, binaryOp.Left))
switchExpr = binaryOp.Right;
if (switchExpr != null && IsValidSwitchType (context.Resolve (switchExpr).Type))
return switchExpr;
}
return null;
}
static bool IsConstantExpression (RefactoringContext context, Expression expr)
{
if (expr is PrimitiveExpression || expr is NullReferenceExpression)
return true;
return context.Resolve (expr).IsCompileTimeConstant;
}
static readonly KnownTypeCode [] validTypes =
{
KnownTypeCode.String, KnownTypeCode.Boolean, KnownTypeCode.Char,
KnownTypeCode.Byte, KnownTypeCode.SByte,
KnownTypeCode.Int16, KnownTypeCode.Int32, KnownTypeCode.Int64,
KnownTypeCode.UInt16, KnownTypeCode.UInt32, KnownTypeCode.UInt64
};
static bool IsValidSwitchType (IType type)
{
if (type.Kind == TypeKind.Enum)
return true;
var typeDefinition = type.GetDefinition ();
if (typeDefinition == null)
return false;
if (typeDefinition.KnownTypeCode == KnownTypeCode.NullableOfT) {
var nullableType = (ParameterizedType)type;
typeDefinition = nullableType.TypeArguments [0].GetDefinition ();
if (typeDefinition == null)
return false;
}
return Array.IndexOf (validTypes, typeDefinition.KnownTypeCode) != -1;
}
static bool CollectSwitchSections (ICollection<SwitchSection> result, RefactoringContext context,
IfElseStatement ifStatement, Expression switchExpr)
{
// if
var section = new SwitchSection ();
if (!CollectCaseLabels (section.CaseLabels, context, ifStatement.Condition, switchExpr))
return false;
CollectSwitchSectionStatements (section.Statements, context, ifStatement.TrueStatement);
result.Add (section);
if (ifStatement.FalseStatement.IsNull)
return true;
// else if
var falseStatement = ifStatement.FalseStatement as IfElseStatement;
if (falseStatement != null)
return CollectSwitchSections (result, context, falseStatement, switchExpr);
// else (default label)
var defaultSection = new SwitchSection ();
defaultSection.CaseLabels.Add (new CaseLabel ());
CollectSwitchSectionStatements (defaultSection.Statements, context, ifStatement.FalseStatement);
result.Add (defaultSection);
return true;
}
static bool CollectCaseLabels (AstNodeCollection<CaseLabel> result, RefactoringContext context,
Expression condition, Expression switchExpr)
{
if (condition is ParenthesizedExpression)
return CollectCaseLabels (result, context, ((ParenthesizedExpression)condition).Expression, switchExpr);
var binaryOp = condition as BinaryOperatorExpression;
if (binaryOp == null)
return false;
if (binaryOp.Operator == BinaryOperatorType.ConditionalOr)
return CollectCaseLabels (result, context, binaryOp.Left, switchExpr) &&
CollectCaseLabels (result, context, binaryOp.Right, switchExpr);
if (binaryOp.Operator == BinaryOperatorType.Equality) {
if (switchExpr.Match (binaryOp.Left).Success) {
if (IsConstantExpression (context, binaryOp.Right)) {
result.Add (new CaseLabel (binaryOp.Right.Clone ()));
return true;
}
} else if (switchExpr.Match (binaryOp.Right).Success) {
if (IsConstantExpression (context, binaryOp.Left)) {
result.Add (new CaseLabel (binaryOp.Left.Clone ()));
return true;
}
}
}
return false;
}
static void CollectSwitchSectionStatements (AstNodeCollection<Statement> result, RefactoringContext context,
Statement statement)
{
BlockStatement blockStatement;
if (statement is BlockStatement)
blockStatement = (BlockStatement)statement.Clone ();
else
blockStatement = new BlockStatement { statement.Clone () };
var breackStatement = new BreakStatement ();
blockStatement.Add (breackStatement);
// check if break is needed
var reachabilityAnalysis = context.CreateReachabilityAnalysis (blockStatement);
if (!reachabilityAnalysis.IsReachable (breackStatement))
blockStatement.Statements.Remove (breackStatement);
var statements = blockStatement.Statements.ToArray ();
blockStatement.Statements.Clear ();
result.AddRange (statements);
}
}
}