Commit 02825b58 authored by Lorenz Haas's avatar Lorenz Haas Committed by Nikolai Kosjar

CppEditor: New quick fix "Optimize For Loop"

Rewrites post increment/decrement operators (++ and --) as pre
increment/decrement operators and moves non string/numeric literals and
non id expressions from loops condition to loops initializer.

Change-Id: Id95334b6df6fcaa9af436cc1d2d0982d38bf8fe2
Reviewed-by: default avatarLeena Miettinen <riitta-leena.miettinen@digia.com>
Reviewed-by: default avatarNikolai Kosjar <nikolai.kosjar@digia.com>
parent 7811cad1
......@@ -1978,6 +1978,21 @@
\image qtcreator-refactoring-virtual-function-dialog.png
\li Class or base class name
\row
\li Optimize for-Loop
\li Rewrites post increment operators as pre increment operators and post decrement
operators as pre decrement operators. It also moves other than string or numeric
literals and id expressions from the condition of a for loop to its initializer.
For example, rewrites:
\code
for (int i = 0; i < 3 * 2; i++)
\endcode
as
\code
for (int i = 0, total = 3 * 2; i < total; ++i)
\endcode
\li for
\endtable
......
......@@ -252,6 +252,16 @@ private slots:
void test_quickfix_InsertVirtualMethods_notrigger_allImplemented();
void test_quickfix_InsertVirtualMethods_BaseClassInNamespace();
void test_quickfix_OptimizeForLoop_postcrement();
void test_quickfix_OptimizeForLoop_condition();
void test_quickfix_OptimizeForLoop_flipedCondition();
void test_quickfix_OptimizeForLoop_alterVariableName();
void test_quickfix_OptimizeForLoop_optimizeBoth();
void test_quickfix_OptimizeForLoop_emptyInitializer();
void test_quickfix_OptimizeForLoop_wrongInitializer();
void test_quickfix_OptimizeForLoop_noTriggerNumeric1();
void test_quickfix_OptimizeForLoop_noTriggerNumeric2();
// The following tests depend on the projects that are loaded on startup
// and will be skipped in case no projects are loaded.
void test_openEachFile();
......
......@@ -3768,3 +3768,93 @@ void CppEditorPlugin::test_quickfix_InsertVirtualMethods_BaseClassInNamespace()
TestCase data(testFiles);
data.run(&factory);
}
/// Check: optimize postcrement
void CppEditorPlugin::test_quickfix_OptimizeForLoop_postcrement()
{
const QByteArray original = "void foo() {f@or (int i = 0; i < 3; i++) {}}\n";
const QByteArray expected = "void foo() {for (int i = 0; i < 3; ++i) {}}\n\n";
OptimizeForLoop factory;
TestCase data(original, expected);
data.run(&factory);
}
/// Check: optimize condition
void CppEditorPlugin::test_quickfix_OptimizeForLoop_condition()
{
const QByteArray original = "void foo() {f@or (int i = 0; i < 3 + 5; ++i) {}}\n";
const QByteArray expected = "void foo() {for (int i = 0, total = 3 + 5; i < total; ++i) {}}\n\n";
OptimizeForLoop factory;
TestCase data(original, expected);
data.run(&factory);
}
/// Check: optimize fliped condition
void CppEditorPlugin::test_quickfix_OptimizeForLoop_flipedCondition()
{
const QByteArray original = "void foo() {f@or (int i = 0; 3 + 5 > i; ++i) {}}\n";
const QByteArray expected = "void foo() {for (int i = 0, total = 3 + 5; total > i; ++i) {}}\n\n";
OptimizeForLoop factory;
TestCase data(original, expected);
data.run(&factory);
}
/// Check: if "total" used, create other name.
void CppEditorPlugin::test_quickfix_OptimizeForLoop_alterVariableName()
{
const QByteArray original = "void foo() {f@or (int i = 0, total = 0; i < 3 + 5; ++i) {}}\n";
const QByteArray expected = "void foo() {for (int i = 0, total = 0, totalX = 3 + 5; i < totalX; ++i) {}}\n\n";
OptimizeForLoop factory;
TestCase data(original, expected);
data.run(&factory);
}
/// Check: optimize postcrement and condition
void CppEditorPlugin::test_quickfix_OptimizeForLoop_optimizeBoth()
{
const QByteArray original = "void foo() {f@or (int i = 0; i < 3 + 5; i++) {}}\n";
const QByteArray expected = "void foo() {for (int i = 0, total = 3 + 5; i < total; ++i) {}}\n\n";
OptimizeForLoop factory;
TestCase data(original, expected);
data.run(&factory);
}
/// Check: empty initializier
void CppEditorPlugin::test_quickfix_OptimizeForLoop_emptyInitializer()
{
const QByteArray original = "int i; void foo() {f@or (; i < 3 + 5; ++i) {}}\n";
const QByteArray expected = "int i; void foo() {for (int total = 3 + 5; i < total; ++i) {}}\n\n";
OptimizeForLoop factory;
TestCase data(original, expected);
data.run(&factory);
}
/// Check: wrong initializier type -> no trigger
void CppEditorPlugin::test_quickfix_OptimizeForLoop_wrongInitializer()
{
const QByteArray original = "int i; void foo() {f@or (double a = 0; i < 3 + 5; ++i) {}}\n";
const QByteArray expected = "int i; void foo() {f@or (double a = 0; i < 3 + 5; ++i) {}}\n\n";
OptimizeForLoop factory;
TestCase data(original, expected);
data.run(&factory);
}
/// Check: No trigger when numeric
void CppEditorPlugin::test_quickfix_OptimizeForLoop_noTriggerNumeric1()
{
const QByteArray original = "void foo() {fo@r (int i = 0; i < 3; ++i) {}}\n";
const QByteArray expected = original + "\n";
OptimizeForLoop factory;
TestCase data(original, expected);
data.run(&factory);
}
/// Check: No trigger when numeric
void CppEditorPlugin::test_quickfix_OptimizeForLoop_noTriggerNumeric2()
{
const QByteArray original = "void foo() {fo@r (int i = 0; i < -3; ++i) {}}\n";
const QByteArray expected = original + "\n";
OptimizeForLoop factory;
TestCase data(original, expected);
data.run(&factory);
}
......@@ -131,6 +131,8 @@ void CppEditor::Internal::registerQuickFixes(ExtensionSystem::IPlugin *plugIn)
plugIn->addAutoReleasedObject(new AssignToLocalVariable);
plugIn->addAutoReleasedObject(new InsertVirtualMethods);
plugIn->addAutoReleasedObject(new OptimizeForLoop);
}
// In the following anonymous namespace all functions are collected, which could be of interest for
......@@ -5051,4 +5053,187 @@ void InsertVirtualMethods::match(const CppQuickFixInterface &interface, QuickFix
delete op;
}
namespace {
class OptimizeForLoopOperation: public CppQuickFixOperation
{
public:
OptimizeForLoopOperation(const CppQuickFixInterface &interface, const ForStatementAST *forAst,
const bool optimizePostcrement, const ExpressionAST *expression,
const FullySpecifiedType type)
: CppQuickFixOperation(interface)
, m_forAst(forAst)
, m_optimizePostcrement(optimizePostcrement)
, m_expression(expression)
, m_type(type)
{
setDescription(QApplication::translate("CppTools::QuickFix", "Optimize for-Loop"));
}
void perform()
{
QTC_ASSERT(m_forAst, return);
const QString filename = assistInterface()->currentFile()->fileName();
const CppRefactoringChanges refactoring(assistInterface()->snapshot());
const CppRefactoringFilePtr file = refactoring.file(filename);
ChangeSet change;
// Optimize post (in|de)crement operator to pre (in|de)crement operator
if (m_optimizePostcrement && m_forAst->expression) {
PostIncrDecrAST *incrdecr = m_forAst->expression->asPostIncrDecr();
if (incrdecr && incrdecr->base_expression && incrdecr->incr_decr_token) {
change.flip(file->range(incrdecr->base_expression),
file->range(incrdecr->incr_decr_token));
}
}
// Optimize Condition
int renamePos = -1;
if (m_expression) {
QString varName = QLatin1String("total");
if (file->textOf(m_forAst->initializer).length() == 1) {
Overview oo = CppCodeStyleSettings::currentProjectCodeStyleOverview();
const QString typeAndName = oo.prettyType(m_type, varName);
renamePos = file->endOf(m_forAst->initializer) - 1 + typeAndName.length();
change.insert(file->endOf(m_forAst->initializer) - 1, // "-1" because of ";"
typeAndName + QLatin1String(" = ") + file->textOf(m_expression));
} else {
// Check if varName is already used
if (DeclarationStatementAST *ds = m_forAst->initializer->asDeclarationStatement()) {
if (DeclarationAST *decl = ds->declaration) {
if (SimpleDeclarationAST *sdecl = decl->asSimpleDeclaration()) {
for (;;) {
bool match = false;
for (DeclaratorListAST *it = sdecl->declarator_list; it;
it = it->next) {
if (file->textOf(it->value->core_declarator) == varName) {
varName += QLatin1Char('X');
match = true;
break;
}
}
if (!match)
break;
}
}
}
}
renamePos = file->endOf(m_forAst->initializer) + 1 + varName.length();
change.insert(file->endOf(m_forAst->initializer) - 1, // "-1" because of ";"
QLatin1String(", ") + varName + QLatin1String(" = ")
+ file->textOf(m_expression));
}
ChangeSet::Range exprRange(file->startOf(m_expression), file->endOf(m_expression));
change.replace(exprRange, varName);
}
file->setChangeSet(change);
file->apply();
// Select variable name and trigger symbol rename
if (renamePos != -1) {
QTextCursor c = file->cursor();
c.setPosition(renamePos);
assistInterface()->editor()->setTextCursor(c);
assistInterface()->editor()->renameSymbolUnderCursor();
c.select(QTextCursor::WordUnderCursor);
assistInterface()->editor()->setTextCursor(c);
}
}
private:
const ForStatementAST *m_forAst;
const bool m_optimizePostcrement;
const ExpressionAST *m_expression;
const FullySpecifiedType m_type;
};
} // anonymous namespace
void OptimizeForLoop::match(const CppQuickFixInterface &interface, QuickFixOperations &result)
{
const QList<AST *> path = interface->path();
ForStatementAST *forAst = 0;
if (!path.isEmpty())
forAst = path.last()->asForStatement();
if (!forAst || !interface->isCursorOn(forAst))
return;
// Check for optimizing a postcrement
const CppRefactoringFilePtr file = interface->currentFile();
bool optimizePostcrement = false;
if (forAst->expression) {
if (PostIncrDecrAST *incrdecr = forAst->expression->asPostIncrDecr()) {
const Token t = file->tokenAt(incrdecr->incr_decr_token);
if (t.is(T_PLUS_PLUS) || t.is(T_MINUS_MINUS))
optimizePostcrement = true;
}
}
// Check for optimizing condition
bool optimizeCondition = false;
FullySpecifiedType conditionType;
ExpressionAST *conditionExpression = 0;
if (forAst->initializer && forAst->condition) {
if (BinaryExpressionAST *binary = forAst->condition->asBinaryExpression()) {
// Get the expression against which we should evaluate
IdExpressionAST *conditionId = binary->left_expression->asIdExpression();
if (conditionId) {
conditionExpression = binary->right_expression;
} else {
conditionId = binary->right_expression->asIdExpression();
conditionExpression = binary->left_expression;
}
if (conditionId && conditionExpression
&& !(conditionExpression->asNumericLiteral()
|| conditionExpression->asStringLiteral()
|| conditionExpression->asIdExpression()
|| conditionExpression->asUnaryExpression())) {
// Determine type of for initializer
FullySpecifiedType initializerType;
if (DeclarationStatementAST *stmt = forAst->initializer->asDeclarationStatement()) {
if (stmt->declaration) {
if (SimpleDeclarationAST *decl = stmt->declaration->asSimpleDeclaration()) {
if (decl->symbols) {
if (Symbol *symbol = decl->symbols->value)
initializerType = symbol->type();
}
}
}
}
// Determine type of for condition
TypeOfExpression typeOfExpression;
typeOfExpression.init(interface->semanticInfo().doc, interface->snapshot(),
interface->context().bindings());
typeOfExpression.setExpandTemplates(true);
Scope *scope = file->scopeAt(conditionId->firstToken());
const QList<LookupItem> conditionItems = typeOfExpression(
conditionId, interface->semanticInfo().doc, scope);
if (!conditionItems.isEmpty())
conditionType = conditionItems.first().type();
if (conditionType.isValid()
&& (file->textOf(forAst->initializer) == QLatin1String(";")
|| initializerType == conditionType)) {
optimizeCondition = true;
}
}
}
}
if (optimizePostcrement || optimizeCondition) {
OptimizeForLoopOperation *op
= new OptimizeForLoopOperation(interface, forAst, optimizePostcrement,
(optimizeCondition) ? conditionExpression : 0,
conditionType);
result.append(QuickFixOperation::Ptr(op));
}
}
#include "cppquickfixes.moc"
......@@ -584,6 +584,16 @@ private:
InsertVirtualMethodsDialog *m_dialog;
};
/*!
Optimizes a for loop to avoid permanent condition check and forces to use preincrement
or predecrement operators in the expression of the for loop.
*/
class OptimizeForLoop : public CppQuickFixFactory
{
public:
void match(const CppQuickFixInterface &interface, TextEditor::QuickFixOperations &result);
};
} // namespace Internal
} // namespace CppEditor
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment