forked from qt-creator/qt-creator
C++: Introduce PointerDeclarationFormatter
For a given AST, CppRefactoringFile and Overview this will create a ChangeSet for rewriting the pointer or reference declaration according to the Overview. Task-number: QTCREATORBUG-6169 Change-Id: If6f824c1ea5e9f53a11a58ec8b6d696d01f0723e Reviewed-by: Erik Verbruggen <erik.verbruggen@digia.com>
This commit is contained in:
417
src/plugins/cpptools/cpppointerdeclarationformatter.cpp
Normal file
417
src/plugins/cpptools/cpppointerdeclarationformatter.cpp
Normal file
@@ -0,0 +1,417 @@
|
||||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
||||
** Contact: http://www.qt-project.org/legal
|
||||
**
|
||||
** This file is part of Qt Creator.
|
||||
**
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and Digia. For licensing terms and
|
||||
** conditions see http://qt.digia.com/licensing. For further information
|
||||
** use the contact form at http://qt.digia.com/contact-us.
|
||||
**
|
||||
** GNU Lesser General Public License Usage
|
||||
** Alternatively, this file may be used under the terms of the GNU Lesser
|
||||
** General Public License version 2.1 as published by the Free Software
|
||||
** Foundation and appearing in the file LICENSE.LGPL included in the
|
||||
** packaging of this file. Please review the following information to
|
||||
** ensure the GNU Lesser General Public License version 2.1 requirements
|
||||
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
||||
**
|
||||
** In addition, as a special exception, Digia gives you certain additional
|
||||
** rights. These rights are described in the Digia Qt LGPL Exception
|
||||
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
|
||||
#include "cpppointerdeclarationformatter.h"
|
||||
|
||||
#include <AST.h>
|
||||
|
||||
#include <QTextCursor>
|
||||
|
||||
#define DEBUG_OUTPUT 0
|
||||
#define CHECK_RV(cond, err, r) if (!(cond)) { if (DEBUG_OUTPUT) qDebug() << (err); return r; }
|
||||
#define CHECK_R(cond, err) if (!(cond)) { if (DEBUG_OUTPUT) qDebug() << (err); return; }
|
||||
#define CHECK_C(cond, err) if (!(cond)) { if (DEBUG_OUTPUT) qDebug() << (err); continue; }
|
||||
|
||||
using namespace CppTools;
|
||||
|
||||
/*!
|
||||
\brief Skip not type relevant specifiers and return the index of the
|
||||
first specifier token which is not followed by __attribute__
|
||||
((T___ATTRIBUTE__)).
|
||||
|
||||
This is used to get 'correct' start of the activation range in
|
||||
simple declarations.
|
||||
|
||||
Consider these cases:
|
||||
|
||||
static char *s = 0;
|
||||
typedef char *s cp;
|
||||
__attribute__((visibility("default"))) char *f();
|
||||
|
||||
For all cases we want to skip all the not type relevant specifer
|
||||
(since these are not part of the type and thus are not rewritten).
|
||||
|
||||
\param list The specifier list to iterate
|
||||
\param translationUnit The TranslationUnit
|
||||
\param endToken Do not check further than this token
|
||||
\param found Output parameter, must not be 0.
|
||||
*/
|
||||
static unsigned firstTypeSpecifierWithoutFollowingAttribute(
|
||||
SpecifierListAST *list, TranslationUnit *translationUnit, unsigned endToken, bool *found)
|
||||
{
|
||||
*found = false;
|
||||
if (! list || ! translationUnit || ! endToken)
|
||||
return 0;
|
||||
|
||||
for (SpecifierListAST *it = list; it; it = it->next) {
|
||||
SpecifierAST *specifier = it->value;
|
||||
CHECK_RV(specifier, "No specifier", 0);
|
||||
const unsigned index = specifier->firstToken();
|
||||
CHECK_RV(index < endToken, "EndToken reached", 0);
|
||||
|
||||
const int tokenKind = translationUnit->tokenKind(index);
|
||||
switch (tokenKind) {
|
||||
case T_VIRTUAL:
|
||||
case T_INLINE:
|
||||
case T_FRIEND:
|
||||
case T_REGISTER:
|
||||
case T_STATIC:
|
||||
case T_EXTERN:
|
||||
case T_MUTABLE:
|
||||
case T_TYPEDEF:
|
||||
case T_CONSTEXPR:
|
||||
case T___ATTRIBUTE__:
|
||||
continue;
|
||||
default:
|
||||
// Check if attributes follow
|
||||
for (unsigned i = index; i <= endToken; ++i) {
|
||||
const int tokenKind = translationUnit->tokenKind(i);
|
||||
if (tokenKind == T___ATTRIBUTE__)
|
||||
return 0;
|
||||
}
|
||||
*found = true;
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
PointerDeclarationFormatter::PointerDeclarationFormatter(
|
||||
const CppRefactoringFilePtr refactoringFile,
|
||||
const Overview &overview,
|
||||
CursorHandling cursorHandling)
|
||||
: ASTVisitor(refactoringFile->cppDocument()->translationUnit())
|
||||
, m_cppRefactoringFile(refactoringFile)
|
||||
, m_overview(overview)
|
||||
, m_cursorHandling(cursorHandling)
|
||||
{}
|
||||
|
||||
/*!
|
||||
Handle
|
||||
(1) Simple declarations like in "char *s, *t, *int foo();"
|
||||
(2) Return types of function declarations.
|
||||
*/
|
||||
bool PointerDeclarationFormatter::visit(SimpleDeclarationAST *ast)
|
||||
{
|
||||
CHECK_RV(ast, "Invalid AST", true);
|
||||
|
||||
DeclaratorListAST *declaratorList = ast->declarator_list;
|
||||
CHECK_RV(declaratorList, "No declarator list", true);
|
||||
DeclaratorAST *firstDeclarator = declaratorList->value;
|
||||
CHECK_RV(firstDeclarator, "No declarator", true);
|
||||
CHECK_RV(ast->symbols, "No Symbols", true);
|
||||
CHECK_RV(ast->symbols->value, "No Symbol", true);
|
||||
|
||||
List<Symbol *> *sit = ast->symbols;
|
||||
DeclaratorListAST *dit = declaratorList;
|
||||
for (; sit && dit; sit = sit->next, dit = dit->next) {
|
||||
DeclaratorAST *declarator = dit->value;
|
||||
Symbol *symbol = sit->value;
|
||||
|
||||
const bool isFirstDeclarator = declarator == firstDeclarator;
|
||||
|
||||
// If were not handling the first declarator, we need to remove
|
||||
// characters from the beginning since our rewritten declaration
|
||||
// will contain all type specifiers.
|
||||
int charactersToRemove = 0;
|
||||
if (! isFirstDeclarator) {
|
||||
const int startAST = m_cppRefactoringFile->startOf(ast);
|
||||
const int startFirstDeclarator = m_cppRefactoringFile->startOf(firstDeclarator);
|
||||
CHECK_RV(startAST < startFirstDeclarator, "No specifier", true);
|
||||
charactersToRemove = startFirstDeclarator - startAST;
|
||||
}
|
||||
|
||||
// Specify activation range
|
||||
int lastActivationToken = 0;
|
||||
Range range;
|
||||
// (2) Handle function declaration's return type
|
||||
if (symbol->type()->asFunctionType()) {
|
||||
PostfixDeclaratorListAST *pfDeclaratorList = declarator->postfix_declarator_list;
|
||||
CHECK_RV(pfDeclaratorList, "No postfix declarator list", true);
|
||||
PostfixDeclaratorAST *pfDeclarator = pfDeclaratorList->value;
|
||||
CHECK_RV(pfDeclarator, "No postfix declarator", true);
|
||||
FunctionDeclaratorAST *functionDeclarator = pfDeclarator->asFunctionDeclarator();
|
||||
CHECK_RV(functionDeclarator, "No function declarator", true);
|
||||
// End the activation range before the '(' token.
|
||||
lastActivationToken = functionDeclarator->lparen_token - 1;
|
||||
|
||||
SpecifierListAST *specifierList = isFirstDeclarator
|
||||
? ast->decl_specifier_list
|
||||
: declarator->attribute_list;
|
||||
|
||||
unsigned firstActivationToken = 0;
|
||||
bool foundBegin = false;
|
||||
firstActivationToken = firstTypeSpecifierWithoutFollowingAttribute(
|
||||
specifierList,
|
||||
m_cppRefactoringFile->cppDocument()->translationUnit(),
|
||||
lastActivationToken,
|
||||
&foundBegin);
|
||||
if (! foundBegin) {
|
||||
CHECK_RV(! isFirstDeclarator, "Declaration without attributes not supported", true);
|
||||
firstActivationToken = declarator->firstToken();
|
||||
}
|
||||
|
||||
range.start = m_cppRefactoringFile->startOf(firstActivationToken);
|
||||
|
||||
// (1) Handle 'normal' declarations.
|
||||
} else {
|
||||
if (isFirstDeclarator) {
|
||||
bool foundBegin = false;
|
||||
unsigned firstActivationToken = firstTypeSpecifierWithoutFollowingAttribute(
|
||||
ast->decl_specifier_list,
|
||||
m_cppRefactoringFile->cppDocument()->translationUnit(),
|
||||
declarator->firstToken(),
|
||||
&foundBegin);
|
||||
CHECK_RV(foundBegin, "Declaration without attributes not supported", true);
|
||||
range.start = m_cppRefactoringFile->startOf(firstActivationToken);
|
||||
} else {
|
||||
range.start = m_cppRefactoringFile->startOf(declarator);
|
||||
}
|
||||
lastActivationToken = declarator->equal_token
|
||||
? declarator->equal_token - 1
|
||||
: declarator->lastToken() - 1;
|
||||
}
|
||||
range.end = m_cppRefactoringFile->endOf(lastActivationToken);
|
||||
|
||||
checkAndRewrite(symbol, range, charactersToRemove);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*! Handle return types of function definitions */
|
||||
bool PointerDeclarationFormatter::visit(FunctionDefinitionAST *ast)
|
||||
{
|
||||
DeclaratorAST *declarator = ast->declarator;
|
||||
CHECK_RV(declarator, "No declarator", true);
|
||||
CHECK_RV(declarator->ptr_operator_list, "No Pointer or references", true);
|
||||
Symbol *symbol = ast->symbol;
|
||||
|
||||
PostfixDeclaratorListAST *pfDeclaratorList = declarator->postfix_declarator_list;
|
||||
CHECK_RV(pfDeclaratorList, "No postfix declarator list", true);
|
||||
PostfixDeclaratorAST *pfDeclarator = pfDeclaratorList->value;
|
||||
CHECK_RV(pfDeclarator, "No postfix declarator", true);
|
||||
FunctionDeclaratorAST *functionDeclarator = pfDeclarator->asFunctionDeclarator();
|
||||
CHECK_RV(functionDeclarator, "No function declarator", true);
|
||||
|
||||
// Specify activation range
|
||||
bool foundBegin = false;
|
||||
const unsigned lastActivationToken = functionDeclarator->lparen_token - 1;
|
||||
const unsigned firstActivationToken = firstTypeSpecifierWithoutFollowingAttribute(
|
||||
ast->decl_specifier_list,
|
||||
m_cppRefactoringFile->cppDocument()->translationUnit(),
|
||||
lastActivationToken,
|
||||
&foundBegin);
|
||||
CHECK_RV(foundBegin, "Declaration without attributes not supported", true);
|
||||
Range range(m_cppRefactoringFile->startOf(firstActivationToken),
|
||||
m_cppRefactoringFile->endOf(lastActivationToken));
|
||||
|
||||
checkAndRewrite(symbol, range);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*! Handle parameters in function declarations and definitions */
|
||||
bool PointerDeclarationFormatter::visit(ParameterDeclarationAST *ast)
|
||||
{
|
||||
DeclaratorAST *declarator = ast->declarator;
|
||||
CHECK_RV(declarator, "No declarator", true);
|
||||
CHECK_RV(declarator->ptr_operator_list, "No Pointer or references", true);
|
||||
Symbol *symbol = ast->symbol;
|
||||
|
||||
// Specify activation range
|
||||
const int lastActivationToken = ast->equal_token
|
||||
? ast->equal_token - 1
|
||||
: ast->lastToken() - 1;
|
||||
Range range(m_cppRefactoringFile->startOf(ast),
|
||||
m_cppRefactoringFile->endOf(lastActivationToken));
|
||||
|
||||
checkAndRewrite(symbol, range);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*! Handle declaration in foreach statement */
|
||||
bool PointerDeclarationFormatter::visit(ForeachStatementAST *ast)
|
||||
{
|
||||
DeclaratorAST *declarator = ast->declarator;
|
||||
CHECK_RV(declarator, "No declarator", true);
|
||||
CHECK_RV(declarator->ptr_operator_list, "No Pointer or references", true);
|
||||
CHECK_RV(ast->type_specifier_list, "No type specifier", true);
|
||||
SpecifierAST *firstSpecifier = ast->type_specifier_list->value;
|
||||
CHECK_RV(firstSpecifier, "No first type specifier", true);
|
||||
CHECK_RV(ast->symbol, "No symbol", true);
|
||||
Symbol *symbol = ast->symbol->memberAt(0);
|
||||
|
||||
// Specify activation range
|
||||
const int lastActivationToken = declarator->equal_token
|
||||
? declarator->equal_token - 1
|
||||
: declarator->lastToken() - 1;
|
||||
Range range(m_cppRefactoringFile->startOf(firstSpecifier),
|
||||
m_cppRefactoringFile->endOf(lastActivationToken));
|
||||
|
||||
checkAndRewrite(symbol, range);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PointerDeclarationFormatter::visit(IfStatementAST *ast)
|
||||
{
|
||||
processIfWhileForStatement(ast->condition, ast->symbol);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PointerDeclarationFormatter::visit(WhileStatementAST *ast)
|
||||
{
|
||||
processIfWhileForStatement(ast->condition, ast->symbol);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PointerDeclarationFormatter::visit(ForStatementAST *ast)
|
||||
{
|
||||
processIfWhileForStatement(ast->condition, ast->symbol);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*! Handle declaration in if, while and for statements */
|
||||
void PointerDeclarationFormatter::processIfWhileForStatement(ExpressionAST *expression,
|
||||
Symbol *statementSymbol)
|
||||
{
|
||||
CHECK_R(expression, "No expression");
|
||||
CHECK_R(statementSymbol, "No symbol");
|
||||
|
||||
ConditionAST *condition = expression->asCondition();
|
||||
CHECK_R(condition, "No condition");
|
||||
DeclaratorAST *declarator = condition->declarator;
|
||||
CHECK_R(declarator, "No declarator");
|
||||
CHECK_R(declarator->ptr_operator_list, "No Pointer or references");
|
||||
CHECK_R(declarator->equal_token, "No equal token");
|
||||
Block *block = statementSymbol->asBlock();
|
||||
CHECK_R(block, "No block");
|
||||
CHECK_R(block->memberCount() > 0, "No block members");
|
||||
|
||||
// Get the right symbol
|
||||
//
|
||||
// This is especially important for e.g.
|
||||
//
|
||||
// for (char *s = 0; char *t = 0;) {}
|
||||
//
|
||||
// The declaration for 's' will be handled in visit(SimpleDeclarationAST *ast),
|
||||
// so handle declaration for 't' here.
|
||||
Scope::iterator it = block->lastMember() - 1;
|
||||
Symbol *symbol = *it;
|
||||
if (symbol && symbol->asScope()) { // True if there is a "{ ... }" following.
|
||||
--it;
|
||||
symbol = *it;
|
||||
}
|
||||
|
||||
// Specify activation range
|
||||
Range range(m_cppRefactoringFile->startOf(condition),
|
||||
m_cppRefactoringFile->endOf(declarator->equal_token - 1));
|
||||
|
||||
checkAndRewrite(symbol, range);
|
||||
}
|
||||
|
||||
/*!
|
||||
\brief Do some further checks and rewrite the symbol's type and
|
||||
name into the given range
|
||||
|
||||
\param symbol the symbol to be rewritten
|
||||
\param range the substitution range in the file
|
||||
*/
|
||||
void PointerDeclarationFormatter::checkAndRewrite(Symbol *symbol, Range range,
|
||||
unsigned charactersToRemove)
|
||||
{
|
||||
CHECK_R(range.start >= 0 && range.end > 0, "Range invalid");
|
||||
CHECK_R(range.start < range.end, "Range invalid");
|
||||
CHECK_R(symbol, "No symbol");
|
||||
|
||||
// Check range with respect to cursor position / selection
|
||||
if (m_cursorHandling == RespectCursor) {
|
||||
const QTextCursor cursor = m_cppRefactoringFile->cursor();
|
||||
if (cursor.hasSelection()) {
|
||||
CHECK_R(cursor.selectionStart() <= range.start, "Change not in selection range");
|
||||
CHECK_R(range.end <= cursor.selectionEnd(), "Change not in selection range");
|
||||
} else {
|
||||
CHECK_R(range.start <= cursor.selectionStart(), "Cursor before activation range");
|
||||
CHECK_R(cursor.selectionEnd() <= range.end, "Cursor after activation range");
|
||||
}
|
||||
}
|
||||
|
||||
FullySpecifiedType type = symbol->type();
|
||||
if (Function *function = type->asFunctionType())
|
||||
type = function->returnType();
|
||||
|
||||
// Check if pointers or references are involved
|
||||
const QString originalDeclaration = m_cppRefactoringFile->textOf(range);
|
||||
CHECK_R(originalDeclaration.contains(QLatin1Char('&'))
|
||||
|| originalDeclaration.contains(QLatin1Char('*')), "No pointer or references");
|
||||
|
||||
// Does the rewritten declaration (part) differs from the original source (part)?
|
||||
QString rewrittenDeclaration = rewriteDeclaration(type, symbol->name());
|
||||
rewrittenDeclaration.remove(0, charactersToRemove);
|
||||
CHECK_R(originalDeclaration != rewrittenDeclaration, "Rewritten is same as original");
|
||||
|
||||
if (DEBUG_OUTPUT) {
|
||||
qDebug("Rewritten: \"%s\" --> \"%s\"", originalDeclaration.toLatin1().constData(),
|
||||
rewrittenDeclaration.toLatin1().constData());
|
||||
}
|
||||
|
||||
// Creating the replacement in the changeset may fail due to operations
|
||||
// in the changeset that overlap with the current range.
|
||||
//
|
||||
// Consider this case:
|
||||
//
|
||||
// void (*foo)(char * s) = 0;
|
||||
//
|
||||
// First visit(SimpleDeclarationAST *ast) will be called. It creates a
|
||||
// replacement that also includes the parameter.
|
||||
// Next visit(ParameterDeclarationAST *ast) is called with the
|
||||
// original source. It tries to create an replacement operation
|
||||
// at this position and fails due to overlapping ranges (the
|
||||
// simple declaration range includes parameter declaration range).
|
||||
ChangeSet change(m_changeSet);
|
||||
if (change.replace(range, rewrittenDeclaration))
|
||||
m_changeSet = change;
|
||||
else if (DEBUG_OUTPUT)
|
||||
qDebug() << "Replacement operation failed";
|
||||
}
|
||||
|
||||
/*! Rewrite/format the given type and name. */
|
||||
QString PointerDeclarationFormatter::rewriteDeclaration(FullySpecifiedType type, const Name *name)
|
||||
const
|
||||
{
|
||||
CHECK_RV(type.isValid(), "Invalid type", QString());
|
||||
|
||||
const char *identifier = 0;
|
||||
if (const Name *declarationName = name) {
|
||||
if (const Identifier *id = declarationName->identifier())
|
||||
identifier = id->chars();
|
||||
}
|
||||
|
||||
return m_overview.prettyType(type, QLatin1String(identifier));
|
||||
}
|
||||
Reference in New Issue
Block a user