summaryrefslogtreecommitdiff
path: root/src/plugins/cppeditor/cpppointerdeclarationformatter.cpp
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@qt.io>2021-08-30 10:58:08 +0200
committerChristian Kandeler <christian.kandeler@qt.io>2021-09-01 14:53:58 +0000
commit284817fae6514701902ccdb834c2faa46462f2e8 (patch)
tree44a8c7d9813dc110b61c4639036366c7696bd7e9 /src/plugins/cppeditor/cpppointerdeclarationformatter.cpp
parent3e1fa0f170d523971d2c3c12da15a6e291f56511 (diff)
downloadqt-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.cpp474
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