diff options
Diffstat (limited to 'src/plugins/cppeditor/cppquickfixes.cpp')
-rw-r--r-- | src/plugins/cppeditor/cppquickfixes.cpp | 261 |
1 files changed, 214 insertions, 47 deletions
diff --git a/src/plugins/cppeditor/cppquickfixes.cpp b/src/plugins/cppeditor/cppquickfixes.cpp index b9aac32df2..25592086ae 100644 --- a/src/plugins/cppeditor/cppquickfixes.cpp +++ b/src/plugins/cppeditor/cppquickfixes.cpp @@ -55,7 +55,12 @@ #include <extensionsystem/pluginmanager.h> +#include <projectexplorer/projectnodes.h> +#include <projectexplorer/projecttree.h> + +#include <utils/algorithm.h> #include <utils/fancylineedit.h> +#include <utils/fileutils.h> #include <utils/qtcassert.h> #include <QApplication> @@ -74,6 +79,7 @@ #include <bitset> #include <cctype> +#include <functional> #include <limits> using namespace CPlusPlus; @@ -1752,6 +1758,138 @@ void AddIncludeForUndefinedIdentifierOp::perform() insertNewIncludeDirective(m_include, file, semanticInfo().doc); } +AddForwardDeclForUndefinedIdentifierOp::AddForwardDeclForUndefinedIdentifierOp( + const CppQuickFixInterface &interface, + int priority, + const QString &fqClassName, + int symbolPos) + : CppQuickFixOperation(interface, priority), m_className(fqClassName), m_symbolPos(symbolPos) +{ + setDescription(QApplication::translate("CppTools::QuickFix", + "Add forward declaration for %1").arg(m_className)); +} + +class NSVisitor : public ASTVisitor +{ +public: + NSVisitor(const CppRefactoringFile *file, const QStringList &namespaces, int symbolPos) + : ASTVisitor(file->cppDocument()->translationUnit()), + m_file(file), + m_remainingNamespaces(namespaces), + m_symbolPos(symbolPos) + {} + + const QStringList remainingNamespaces() const { return m_remainingNamespaces; } + const NamespaceAST *firstNamespace() const { return m_firstNamespace; } + const AST *firstToken() const { return m_firstToken; } + const NamespaceAST *enclosingNamespace() const { return m_enclosingNamespace; } + +private: + bool preVisit(AST *ast) override + { + if (!m_firstToken) + m_firstToken = ast; + if (m_file->startOf(ast) >= m_symbolPos) + m_done = true; + return !m_done; + } + + bool visit(NamespaceAST *ns) override + { + if (!m_firstNamespace) + m_firstNamespace = ns; + if (m_remainingNamespaces.isEmpty()) { + m_done = true; + return false; + } + + QString name; + const Identifier * const id = translationUnit()->identifier(ns->identifier_token); + if (id) + name = QString::fromUtf8(id->chars(), id->size()); + if (name != m_remainingNamespaces.first()) + return name.isEmpty(); + + if (!ns->linkage_body) { + m_done = true; + return false; + } + + m_enclosingNamespace = ns; + m_remainingNamespaces.removeFirst(); + return !m_remainingNamespaces.isEmpty(); + } + + void postVisit(AST *ast) override + { + if (ast == m_enclosingNamespace) + m_done = true; + } + + const CppRefactoringFile * const m_file; + const NamespaceAST *m_enclosingNamespace = nullptr; + const NamespaceAST *m_firstNamespace = nullptr; + const AST *m_firstToken = nullptr; + QStringList m_remainingNamespaces; + const int m_symbolPos; + bool m_done = false; +}; + +void AddForwardDeclForUndefinedIdentifierOp::perform() +{ + const QStringList parts = m_className.split("::"); + QTC_ASSERT(!parts.isEmpty(), return); + const QStringList namespaces = parts.mid(0, parts.length() - 1); + + CppRefactoringChanges refactoring(snapshot()); + CppRefactoringFilePtr file = refactoring.file(fileName()); + + NSVisitor visitor(file.data(), namespaces, m_symbolPos); + visitor.accept(file->cppDocument()->translationUnit()->ast()); + const auto stringToInsert = [&visitor, symbol = parts.last()] { + QString s = "\n"; + for (const QString &ns : visitor.remainingNamespaces()) + s += "namespace " + ns + " { "; + s += "class " + symbol + ';'; + for (int i = 0; i < visitor.remainingNamespaces().size(); ++i) + s += " }"; + return s; + }; + + int insertPos = 0; + + // Find the position to insert: + // If we have a matching namespace, we do the insertion there. + // If we don't have a matching namespace, but there is another namespace in the file, + // we assume that to be a good position for our insertion. + // Otherwise, do the insertion after the last include that comes before the use of the symbol. + // If there is no such include, do the insertion before the first token. + if (visitor.enclosingNamespace()) { + insertPos = file->startOf(visitor.enclosingNamespace()->linkage_body) + 1; + } else if (visitor.firstNamespace()) { + insertPos = file->startOf(visitor.firstNamespace()); + } else { + const QTextCursor tc = file->document()->find( + QRegularExpression("^\\s*#include .*$"), + m_symbolPos, + QTextDocument::FindBackward | QTextDocument::FindCaseSensitively); + if (!tc.isNull()) + insertPos = tc.position() + 1; + else if (visitor.firstToken()) + insertPos = file->startOf(visitor.firstToken()); + } + + QString insertion = stringToInsert(); + if (file->charAt(insertPos - 1) != QChar::ParagraphSeparator) + insertion.prepend('\n'); + if (file->charAt(insertPos) != QChar::ParagraphSeparator) + insertion.append('\n'); + ChangeSet s; + s.insert(insertPos, insertion); + file->setChangeSet(s); + file->apply(); +} + namespace { QString findShortestInclude(const QString currentDocumentFilePath, @@ -1779,25 +1917,20 @@ QString findShortestInclude(const QString currentDocumentFilePath, return result; } -QString findQtIncludeWithSameName(const QString &className, - const ProjectExplorer::HeaderPaths &headerPaths) +QString findMatchingInclude(const QString &className, + const ProjectExplorer::HeaderPaths &headerPaths) { - QString result; - - // Check for a header file with the same name in the Qt include paths - foreach (const ProjectExplorer::HeaderPath &headerPath, headerPaths) { - if (!headerPath.path.contains(QLatin1String("/Qt"))) // "QtCore", "QtGui" etc... - continue; - - const QString headerPathCandidate = headerPath.path + QLatin1Char('/') + className; - const QFileInfo fileInfo(headerPathCandidate); - if (fileInfo.exists() && fileInfo.isFile()) { - result = QLatin1Char('<') + className + QLatin1Char('>'); - break; + const QStringList candidateFileNames{className, className + ".h", className + ".hpp", + className.toLower(), className.toLower() + ".h", className.toLower() + ".hpp"}; + for (const QString &fileName : candidateFileNames) { + for (const ProjectExplorer::HeaderPath &headerPath : headerPaths) { + const QString headerPathCandidate = headerPath.path + QLatin1Char('/') + fileName; + const QFileInfo fileInfo(headerPathCandidate); + if (fileInfo.exists() && fileInfo.isFile()) + return '<' + fileName + '>'; } } - - return result; + return {}; } ProjectExplorer::HeaderPaths relevantHeaderPaths(const QString &filePath) @@ -1839,17 +1972,18 @@ NameAST *nameUnderCursor(const QList<AST *> &path) return nameAst; } -bool canLookupDefinition(const CppQuickFixInterface &interface, const NameAST *nameAst) +enum class LookupResult { Definition, Declaration, None }; +LookupResult lookUpDefinition(const CppQuickFixInterface &interface, const NameAST *nameAst) { - QTC_ASSERT(nameAst && nameAst->name, return false); + QTC_ASSERT(nameAst && nameAst->name, return LookupResult::None); // Find the enclosing scope int line, column; - const Document::Ptr &doc = interface.semanticInfo().doc; + const Document::Ptr doc = interface.semanticInfo().doc; doc->translationUnit()->getTokenStartPosition(nameAst->firstToken(), &line, &column); Scope *scope = doc->scopeAt(line, column); if (!scope) - return false; + return LookupResult::None; // Try to find the class/template definition const Name *name = nameAst->name; @@ -1857,16 +1991,21 @@ bool canLookupDefinition(const CppQuickFixInterface &interface, const NameAST *n foreach (const LookupItem &item, results) { if (Symbol *declaration = item.declaration()) { if (declaration->isClass()) - return true; + return LookupResult::Definition; + if (declaration->isForwardClassDeclaration()) + return LookupResult::Declaration; if (Template *templ = declaration->asTemplate()) { - Symbol *declaration = templ->declaration(); - if (declaration && declaration->isClass()) - return true; + if (Symbol *declaration = templ->declaration()) { + if (declaration->isClass()) + return LookupResult::Definition; + if (declaration->isForwardClassDeclaration()) + return LookupResult::Declaration; + } } } } - return false; + return LookupResult::None; } QString templateNameAsString(const TemplateNameId *templateName) @@ -1887,17 +2026,11 @@ Snapshot forwardingHeaders(const CppQuickFixInterface &interface) return result; } -bool looksLikeAQtClass(const QString &identifier) -{ - return identifier.size() > 2 - && identifier.at(0) == QLatin1Char('Q') - && identifier.at(1).isUpper(); -} - bool matchName(const Name *name, QList<Core::LocatorFilterEntry> *matches, QString *className) { if (!name) return false; + QString simpleName; if (Core::ILocatorFilter *classesFilter = CppTools::CppModelManager::instance()->classesFilter()) { QFutureInterface<Core::LocatorFilterEntry> dummy; @@ -1908,7 +2041,8 @@ bool matchName(const Name *name, QList<Core::LocatorFilterEntry> *matches, QStri if (const TemplateNameId *templateName = name->asTemplateNameId()) { *className = templateNameAsString(templateName); } else { - *className = oo.prettyName(name); + simpleName = oo.prettyName(name); + *className = simpleName; *matches = classesFilter->matchesFor(dummy, *className); if (matches->empty()) { if (const Name *name = qualifiedName->base()) { @@ -1927,6 +2061,8 @@ bool matchName(const Name *name, QList<Core::LocatorFilterEntry> *matches, QStri if (matches->empty()) *matches = classesFilter->matchesFor(dummy, *className); + if (matches->empty() && !simpleName.isEmpty()) + *className = simpleName; } return !matches->empty(); @@ -1941,22 +2077,25 @@ void AddIncludeForUndefinedIdentifier::match(const CppQuickFixInterface &interfa if (!nameAst || !nameAst->name) return; - if (canLookupDefinition(interface, nameAst)) + const LookupResult lookupResult = lookUpDefinition(interface, nameAst); + if (lookupResult == LookupResult::Definition) return; QString className; QList<Core::LocatorFilterEntry> matches; const QString currentDocumentFilePath = interface.semanticInfo().doc->fileName(); const ProjectExplorer::HeaderPaths headerPaths = relevantHeaderPaths(currentDocumentFilePath); - bool qtHeaderFileIncludeOffered = false; + QList<Utils::FilePath> headers; // Find an include file through the locator if (matchName(nameAst->name, &matches, &className)) { + QList<IndexItem::Ptr> indexItems; const Snapshot forwardHeaders = forwardingHeaders(interface); foreach (const Core::LocatorFilterEntry &entry, matches) { IndexItem::Ptr info = entry.internalData.value<IndexItem::Ptr>(); if (info->symbolName() != className) continue; + indexItems << info; Snapshot localForwardHeaders = forwardHeaders; localForwardHeaders.insert(interface.snapshot().document(info->fileName())); @@ -1978,11 +2117,30 @@ void AddIncludeForUndefinedIdentifier::match(const CppQuickFixInterface &interfa else if (headerFileName.at(1).isUpper()) priority = 1; - if (looksLikeAQtClass(include.mid(1, include.size() - 2))) - qtHeaderFileIncludeOffered = true; - result << new AddIncludeForUndefinedIdentifierOp(interface, priority, include); + headers << header; + } + } + } + + if (lookupResult == LookupResult::None && indexItems.size() == 1) { + QString qualifiedName = Overview().prettyName(nameAst->name); + if (qualifiedName.startsWith("::")) + qualifiedName.remove(0, 2); + if (indexItems.first()->scopedSymbolName().endsWith(qualifiedName)) { + const ProjectExplorer::Node * const node = ProjectExplorer::ProjectTree + ::nodeForFile(Utils::FilePath::fromString(interface.fileName())); + ProjectExplorer::FileType fileType = node && node->asFileNode() + ? node->asFileNode()->fileType() : ProjectExplorer::FileType::Unknown; + if (fileType == ProjectExplorer::FileType::Unknown + && ProjectFile::isHeader(ProjectFile::classify(interface.fileName()))) { + fileType = ProjectExplorer::FileType::Header; + } + if (fileType == ProjectExplorer::FileType::Header) { + result << new AddForwardDeclForUndefinedIdentifierOp( + interface, 0, indexItems.first()->scopedSymbolName(), + interface.currentFile()->startOf(nameAst)); } } } @@ -1991,12 +2149,16 @@ void AddIncludeForUndefinedIdentifier::match(const CppQuickFixInterface &interfa if (className.isEmpty()) return; - // The header file we are looking for might not be (yet) included in any file we have parsed. - // As such, it will not be findable via locator. At least for Qt classes, check also for - // headers with the same name. - if (!qtHeaderFileIncludeOffered && looksLikeAQtClass(className)) { - const QString include = findQtIncludeWithSameName(className, headerPaths); - if (!include.isEmpty()) + // Fallback: Check the include paths for files that look like candidates + // for the given name. + if (!Utils::contains(headers, + [&className](const Utils::FilePath &fp) { return fp.fileName() == className; })) { + const QString include = findMatchingInclude(className, headerPaths); + const auto matcher = [&include](const QuickFixOperation::Ptr &o) { + const auto includeOp = o.dynamicCast<AddIncludeForUndefinedIdentifierOp>(); + return includeOp && includeOp->include() == include; + }; + if (!include.isEmpty() && !Utils::contains(result, matcher)) result << new AddIncludeForUndefinedIdentifierOp(interface, 1, include); } } @@ -2680,7 +2842,7 @@ void InsertDefFromDecl::match(const CppQuickFixInterface &interface, QuickFixOpe if (Symbol *symbol = simpleDecl->symbols->value) { if (Declaration *decl = symbol->asDeclaration()) { if (Function *func = decl->type()->asFunctionType()) { - if (func->isSignal() || func->isPureVirtual()) + if (func->isSignal() || func->isPureVirtual() || func->isFriend()) return; // Check if there is already a definition @@ -3271,12 +3433,17 @@ public: // Write class qualification, if any. if (matchingClass) { - Class *current = matchingClass; + const Scope *current = matchingClass; QVector<const Name *> classes{matchingClass->name()}; while (current->enclosingScope()->asClass()) { current = current->enclosingScope()->asClass(); classes.prepend(current->name()); } + while (current->enclosingScope() && current->enclosingScope()->asNamespace()) { + current = current->enclosingScope()->asNamespace(); + if (current->name()) + classes.prepend(current->name()); + } for (const Name *n : classes) { const Name *name = rewriteName(n, &env, control); funcDef.append(printer.prettyName(name)); @@ -5822,7 +5989,7 @@ Class *senderOrReceiverClass(const CppQuickFixInterface &interface, toe.init(interface.semanticInfo().doc, interface.snapshot(), context.bindings()); const QList<LookupItem> objectPointerExpressions = toe(objectPointerExpression, objectPointerScope, TypeOfExpression::Preprocess); - QTC_ASSERT(objectPointerExpressions.size() == 1, return nullptr); + QTC_ASSERT(!objectPointerExpressions.isEmpty(), return nullptr); Type *objectPointerTypeBase = objectPointerExpressions.first().type().type(); QTC_ASSERT(objectPointerTypeBase, return nullptr); |