diff options
| author | Christian Kandeler <christian.kandeler@qt.io> | 2021-08-30 10:58:08 +0200 |
|---|---|---|
| committer | Christian Kandeler <christian.kandeler@qt.io> | 2021-09-01 14:53:58 +0000 |
| commit | 284817fae6514701902ccdb834c2faa46462f2e8 (patch) | |
| tree | 44a8c7d9813dc110b61c4639036366c7696bd7e9 /src/plugins/cppeditor/cpppointerdeclarationformatter.cpp | |
| parent | 3e1fa0f170d523971d2c3c12da15a6e291f56511 (diff) | |
| download | qt-creator-284817fae6514701902ccdb834c2faa46462f2e8.tar.gz | |
Merge CppTools into CppEditor
There was no proper separation of responsibilities between these
plugins. In particular, CppTools had lots of editor-related
functionality, so it's not clear why it was separated out in the first
place.
In fact, for a lot of code, it seemed quite arbitrary where it was put
(just one example: switchHeaderSource() was in CppTools, wheras
switchDeclarationDefinition() was in CppEditor).
Merging the plugins will enable us to get rid of various convoluted
pseudo-abstractions that were only introduced to keep up the artificial
separation.
Change-Id: Iafc3bce625b4794f6d4aa03df6cddc7f2d26716a
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
Diffstat (limited to 'src/plugins/cppeditor/cpppointerdeclarationformatter.cpp')
| -rw-r--r-- | src/plugins/cppeditor/cpppointerdeclarationformatter.cpp | 474 |
1 files changed, 474 insertions, 0 deletions
diff --git a/src/plugins/cppeditor/cpppointerdeclarationformatter.cpp b/src/plugins/cppeditor/cpppointerdeclarationformatter.cpp new file mode 100644 index 0000000000..1397c935d1 --- /dev/null +++ b/src/plugins/cppeditor/cpppointerdeclarationformatter.cpp @@ -0,0 +1,474 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** 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 The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "cpppointerdeclarationformatter.h" + +#include <cplusplus/Overview.h> + +#include <QDebug> +#include <QTextCursor> + +#define DEBUG_OUTPUT 0 + +#if DEBUG_OUTPUT +# include <typeinfo> +# ifdef __GNUC__ +# include <cxxabi.h> +# endif +#endif + +#define CHECK_RV(cond, err, r) \ + if (!(cond)) { if (DEBUG_OUTPUT) qDebug() << "Discarded:" << (err); return r; } +#define CHECK_R(cond, err) \ + if (!(cond)) { if (DEBUG_OUTPUT) qDebug() << "Discarded:" << (err); return; } +#define CHECK_C(cond, err) \ + if (!(cond)) { if (DEBUG_OUTPUT) qDebug() << "Discarded:" << (err); continue; } + +namespace CppEditor { + +/*! + Skips specifiers that are not type relevant and returns 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: + + \list + \li \c {static char *s = 0;} + \li \c {typedef char *s cp;} + \li \c {__attribute__((visibility("default"))) char *f();} + \endlist + + For all these cases we want to skip all the specifiers that are not type + relevant + (since these are not part of the type and thus are not rewritten). + + \a list is the specifier list to iterate and \a translationUnit is the + translation unit. + \a endToken is the last token to check. + \a found is an output parameter that 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__: + case T___DECLSPEC: + continue; + default: + // Check if attributes follow + for (unsigned i = index; i <= endToken; ++i) { + const int tokenKind = translationUnit->tokenKind(i); + if (tokenKind == T___ATTRIBUTE__ || tokenKind == T___DECLSPEC) + return 0; + } + *found = true; + return index; + } + } + + return 0; +} + +PointerDeclarationFormatter::PointerDeclarationFormatter( + const CppRefactoringFilePtr &refactoringFile, + 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); + printCandidate(ast); + + const unsigned tokenKind = tokenAt(ast->firstToken()).kind(); + const bool astIsOk = tokenKind != T_CLASS && tokenKind != T_STRUCT && tokenKind != T_ENUM; + CHECK_RV(astIsOk, "Nothing to do for class/struct/enum", 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; + TokenRange 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 = 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 = firstActivationToken; + } else { + range.start = declarator->firstToken(); + } + lastActivationToken = declarator->equal_token + ? declarator->equal_token - 1 + : declarator->lastToken() - 1; + } + + range.end = lastActivationToken; + + checkAndRewrite(declarator, symbol, range, charactersToRemove); + } + return true; +} + +/*! Handle return types of function definitions */ +bool PointerDeclarationFormatter::visit(FunctionDefinitionAST *ast) +{ + CHECK_RV(ast, "Invalid AST", true); + printCandidate(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); + TokenRange range(firstActivationToken, lastActivationToken); + + checkAndRewrite(declarator, symbol, range); + return true; +} + +/*! Handle parameters in function declarations and definitions */ +bool PointerDeclarationFormatter::visit(ParameterDeclarationAST *ast) +{ + CHECK_RV(ast, "Invalid AST", true); + printCandidate(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; + TokenRange range(ast->firstToken(), lastActivationToken); + + checkAndRewrite(declarator, symbol, range); + return true; +} + +/*! Handle declaration in foreach statement */ +bool PointerDeclarationFormatter::visit(ForeachStatementAST *ast) +{ + CHECK_RV(ast, "Invalid AST", true); + printCandidate(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; + TokenRange range(firstSpecifier->firstToken(), lastActivationToken); + + checkAndRewrite(declarator, symbol, range); + return true; +} + +bool PointerDeclarationFormatter::visit(IfStatementAST *ast) +{ + CHECK_RV(ast, "Invalid AST", true); + printCandidate(ast); + processIfWhileForStatement(ast->condition, ast->symbol); + return true; +} + +bool PointerDeclarationFormatter::visit(WhileStatementAST *ast) +{ + CHECK_RV(ast, "Invalid AST", true); + printCandidate(ast); + processIfWhileForStatement(ast->condition, ast->symbol); + return true; +} + +bool PointerDeclarationFormatter::visit(ForStatementAST *ast) +{ + CHECK_RV(ast, "Invalid AST", true); + printCandidate(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->memberEnd() - 1; + Symbol *symbol = *it; + if (symbol && symbol->asScope()) { // True if there is a "{ ... }" following. + --it; + symbol = *it; + } + + // Specify activation range + TokenRange range(condition->firstToken(), declarator->equal_token - 1); + + checkAndRewrite(declarator, symbol, range); +} + +/*! + Performs some further checks and rewrites the type and name of \a symbol + into the substitution range in the file specified by \a tokenRange. + */ +void PointerDeclarationFormatter::checkAndRewrite(DeclaratorAST *declarator, + Symbol *symbol, + TokenRange tokenRange, + unsigned charactersToRemove) +{ + CHECK_R(tokenRange.end > 0, "TokenRange invalid1"); + CHECK_R(tokenRange.start < tokenRange.end, "TokenRange invalid2"); + CHECK_R(symbol, "No symbol"); + + // Check for expanded tokens + for (int token = tokenRange.start; token <= tokenRange.end; ++token) + CHECK_R(!tokenAt(token).expanded(), "Token is expanded"); + + Utils::ChangeSet::Range range(m_cppRefactoringFile->startOf(tokenRange.start), + m_cppRefactoringFile->endOf(tokenRange.end)); + + CHECK_R(range.start >= 0 && range.end > 0, "ChangeRange invalid1"); + CHECK_R(range.start < range.end, "ChangeRange invalid2"); + + // 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; + const Name *name = symbol->name(); + if (name) { + if (name->isOperatorNameId() + || (name->isQualifiedNameId() + && name->asQualifiedNameId()->name()->isOperatorNameId())) { + const QString operatorText = m_cppRefactoringFile->textOf(declarator->core_declarator); + m_overview.includeWhiteSpaceInOperatorName = operatorText.contains(QLatin1Char(' ')); + } + } + rewrittenDeclaration = m_overview.prettyType(type, name); + rewrittenDeclaration.remove(0, charactersToRemove); + + CHECK_R(originalDeclaration != rewrittenDeclaration, "Rewritten is same as original"); + CHECK_R(rewrittenDeclaration.contains(QLatin1Char('&')) + || rewrittenDeclaration.contains(QLatin1Char('*')), + "No pointer or references in rewritten declaration"); + + if (DEBUG_OUTPUT) { + qDebug("==> Rewritten: \"%s\" --> \"%s\"", originalDeclaration.toUtf8().constData(), + rewrittenDeclaration.toUtf8().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). + Utils::ChangeSet change(m_changeSet); + if (change.replace(range, rewrittenDeclaration)) + m_changeSet = change; + else if (DEBUG_OUTPUT) + qDebug() << "Replacement operation failed"; +} + +void PointerDeclarationFormatter::printCandidate(AST *ast) +{ +#if DEBUG_OUTPUT + QString tokens; + for (int token = ast->firstToken(); token < ast->lastToken(); token++) + tokens += QString::fromLatin1(tokenAt(token).spell()) + QLatin1Char(' '); + +# ifdef __GNUC__ + QByteArray name = abi::__cxa_demangle(typeid(*ast).name(), 0, 0, 0) + 11; + name.truncate(name.length() - 3); +# else + QByteArray name = typeid(*ast).name(); +# endif + qDebug("--> Candidate: %s: %s", name.constData(), qPrintable(tokens)); +#else + Q_UNUSED(ast) +#endif // DEBUG_OUTPUT +} + +} // namespace CppEditor |
