diff --git a/languages/clang/CMakeLists.txt b/languages/clang/CMakeLists.txt index 671c4dec0e..c19a951ef9 100644 --- a/languages/clang/CMakeLists.txt +++ b/languages/clang/CMakeLists.txt @@ -1,135 +1,137 @@ add_definitions(${LLVM_CFLAGS}) include_directories(${CLANG_INCLUDE_DIRS}) configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/version.h.cmake" "${CMAKE_CURRENT_BINARY_DIR}/version.h" @ONLY ) include_directories( ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/libs ${KDevelop_SOURCE_DIR}/languages/plugin ) add_subdirectory(tests) add_definitions( -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS -DQT_NO_URL_CAST_FROM_STRING -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII -DQT_NO_CAST_FROM_BYTEARRAY ) # TODO: Move to kdevplatform function(add_private_library target) set(options) set(oneValueArgs) set(multiValueArgs SOURCES) cmake_parse_arguments(KDEV_ADD_PRIVATE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) string(REPLACE "KDev" "" shortTargetName ${target}) if (${shortTargetName} STREQUAL ${target}) message(FATAL_ERROR "Target passed to add_private_library needs to start with \"KDev\", was \"${target}\"") endif() string(TOLOWER ${shortTargetName} shortTargetNameToLower) add_library(${target} SHARED ${KDEV_ADD_PRIVATE_SOURCES}) generate_export_header(${target} EXPORT_FILE_NAME ${shortTargetNameToLower}export.h) set_target_properties(${target} PROPERTIES VERSION ${KDEV_PLUGIN_VERSION} SOVERSION ${KDEV_PLUGIN_VERSION} ) install(TARGETS ${target} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS} LIBRARY NAMELINK_SKIP) endfunction() set(kdevclangprivate_SRCS clangsettings/clangsettingsmanager.cpp clangsettings/sessionsettings/sessionsettings.cpp codecompletion/completionhelper.cpp codecompletion/context.cpp codecompletion/includepathcompletioncontext.cpp codecompletion/model.cpp codegen/adaptsignatureaction.cpp codegen/adaptsignatureassistant.cpp codegen/codegenhelper.cpp + codegen/clangrefactoring.cpp + codegen/sourcemanipulation.cpp duchain/builder.cpp duchain/clangdiagnosticevaluator.cpp duchain/clangducontext.cpp duchain/clanghelpers.cpp duchain/clangindex.cpp duchain/clangparsingenvironment.cpp duchain/clangparsingenvironmentfile.cpp duchain/clangpch.cpp duchain/clangproblem.cpp duchain/debugvisitor.cpp duchain/documentfinderhelpers.cpp duchain/duchainutils.cpp duchain/macrodefinition.cpp duchain/macronavigationcontext.cpp duchain/missingincludepathproblem.cpp duchain/navigationwidget.cpp duchain/parsesession.cpp duchain/todoextractor.cpp duchain/types/classspecializationtype.cpp duchain/unknowndeclarationproblem.cpp duchain/unsavedfile.cpp util/clangdebug.cpp util/clangtypes.cpp util/clangutils.cpp ) include_directories( ${CMAKE_CURRENT_BINARY_DIR} ) ki18n_wrap_ui(kdevclangprivate_SRCS clangsettings/sessionsettings/sessionsettings.ui ) kconfig_add_kcfg_files(kdevclangprivate_SRCS clangsettings/sessionsettings/sessionconfig.kcfgc) add_private_library(KDevClangPrivate SOURCES ${kdevclangprivate_SRCS}) target_link_libraries(KDevClangPrivate LINK_PRIVATE Qt5::Core KF5::TextEditor KF5::ThreadWeaver KDev::Util LINK_PUBLIC KDev::Language KDev::Project KDev::Util ${CLANG_LIBCLANG_LIB} ) install(FILES duchain/gcc_compat.h DESTINATION ${DATA_INSTALL_DIR}/kdevclangsupport PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) install(DIRECTORY duchain/wrappedQtHeaders DESTINATION ${DATA_INSTALL_DIR}/kdevclangsupport DIRECTORY_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_WRITE GROUP_EXECUTE WORLD_READ WORLD_EXECUTE FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) set(kdevclangsupport_SRCS clangparsejob.cpp clangsupport.cpp clanghighlighting.cpp ) qt5_add_resources(kdevclangsupport_SRCS kdevclangsupport.qrc) kdevplatform_add_plugin(kdevclangsupport JSON kdevclangsupport.json SOURCES ${kdevclangsupport_SRCS}) target_link_libraries(kdevclangsupport KDevClangPrivate KF5::ThreadWeaver KF5::TextEditor KDev::Util KDev::Project ) diff --git a/languages/clang/clangparsejob.cpp b/languages/clang/clangparsejob.cpp index f31750b178..715d686d40 100644 --- a/languages/clang/clangparsejob.cpp +++ b/languages/clang/clangparsejob.cpp @@ -1,369 +1,379 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "clangparsejob.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "clangsettings/clangsettingsmanager.h" #include "duchain/clanghelpers.h" #include "duchain/clangpch.h" #include "duchain/duchainutils.h" #include "duchain/parsesession.h" #include "duchain/clangindex.h" #include "duchain/clangparsingenvironmentfile.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "clangsupport.h" #include "duchain/documentfinderhelpers.h" #include #include #include #include #include #include using namespace KDevelop; namespace { QString findConfigFile(const QString& forFile, const QString& configFileName) { QDir dir = QFileInfo(forFile).dir(); while (dir.exists()) { const QFileInfo customIncludePaths(dir, configFileName); if (customIncludePaths.exists()) { return customIncludePaths.absoluteFilePath(); } if (!dir.cdUp()) { break; } } return {}; } Path::List readPathListFile(const QString& filepath) { if (filepath.isEmpty()) { return {}; } QFile f(filepath); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) { return {}; } const QString text = QString::fromLocal8Bit(f.readAll()); const QStringList lines = text.split(QLatin1Char('\n'), QString::SkipEmptyParts); Path::List paths(lines.length()); std::transform(lines.begin(), lines.end(), paths.begin(), [] (const QString& line) { return Path(line); }); return paths; } /** * File should contain the header to precompile and use while parsing * @returns the first path in the file */ Path userDefinedPchIncludeForFile(const QString& sourcefile) { static const QString pchIncludeFilename = QStringLiteral(".kdev_pch_include"); const auto paths = readPathListFile(findConfigFile(sourcefile, pchIncludeFilename)); return paths.isEmpty() ? Path() : paths.first(); } ProjectFileItem* findProjectFileItem(const IndexedString& url, bool* hasBuildSystemInfo) { ProjectFileItem* file = nullptr; *hasBuildSystemInfo = false; for (auto project: ICore::self()->projectController()->projects()) { auto files = project->filesForPath(url); if (files.isEmpty()) { continue; } file = files.last(); // A file might be defined in different targets. // Prefer file items defined inside a target with non-empty includes. for (auto f: files) { if (!dynamic_cast(f->parent())) { continue; } file = f; if (!IDefinesAndIncludesManager::manager()->includes(f, IDefinesAndIncludesManager::ProjectSpecific).isEmpty()) { break; } } } if (file && file->project()) { if (auto bsm = file->project()->buildSystemManager()) { *hasBuildSystemInfo = bsm->hasIncludesOrDefines(file); } } return file; } ClangParsingEnvironmentFile* parsingEnvironmentFile(const TopDUContext* context) { return dynamic_cast(context->parsingEnvironmentFile().data()); } DocumentChangeTracker* trackerForUrl(const IndexedString& url) { return ICore::self()->languageController()->backgroundParser()->trackerForUrl(url); } } ClangParseJob::ClangParseJob(const IndexedString& url, ILanguageSupport* languageSupport) : ParseJob(url, languageSupport) { const auto tuUrl = clang()->index()->translationUnitForUrl(url); bool hasBuildSystemInfo; if (auto file = findProjectFileItem(tuUrl, &hasBuildSystemInfo)) { m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includes(file)); m_environment.addDefines(IDefinesAndIncludesManager::manager()->defines(file)); m_environment.setParserSettings(ClangSettingsManager::self()->parserSettings(file)); } else { m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includes(tuUrl.str())); m_environment.addDefines(IDefinesAndIncludesManager::manager()->defines(tuUrl.str())); m_environment.setParserSettings(ClangSettingsManager::self()->parserSettings(nullptr)); } const bool isSource = ClangHelpers::isSource(tuUrl.str()); m_environment.setQuality( isSource ? (hasBuildSystemInfo ? ClangParsingEnvironment::BuildSystem : ClangParsingEnvironment::Source) : ClangParsingEnvironment::Unknown ); m_environment.setTranslationUnitUrl(tuUrl); Path::List projectPaths; const auto& projects = ICore::self()->projectController()->projects(); projectPaths.reserve(projects.size()); foreach (auto project, projects) { projectPaths.append(project->path()); } m_environment.setProjectPaths(projectPaths); foreach(auto document, ICore::self()->documentController()->openDocuments()) { auto textDocument = document->textDocument(); if (!textDocument || !textDocument->isModified() || !textDocument->url().isLocalFile() || !DocumentFinderHelpers::mimeTypesList().contains(textDocument->mimeType())) { continue; } m_unsavedFiles << UnsavedFile(textDocument->url().toLocalFile(), textDocument->textLines(textDocument->documentRange())); const IndexedString indexedUrl(textDocument->url()); m_unsavedRevisions.insert(indexedUrl, ModificationRevision::revisionForFile(indexedUrl)); + if (indexedUrl == tuUrl) { + m_tuDocumentIsUnsaved = true; + } } if (auto tracker = trackerForUrl(url)) { tracker->reset(); } } ClangSupport* ClangParseJob::clang() const { return static_cast(languageSupport()); } void ClangParseJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread* /*thread*/) { QReadLocker parseLock(languageSupport()->parseLock()); if (abortRequested()) { return; } { const auto tuUrlStr = m_environment.translationUnitUrl().str(); + if (!m_tuDocumentIsUnsaved && !QFile::exists(tuUrlStr)) { + // maybe we requested a parse job some time ago but now the file + // does not exist anymore. return early then + clang()->index()->unpinTranslationUnitForUrl(document()); + return; + } + m_environment.addIncludes(IDefinesAndIncludesManager::manager()->includesInBackground(tuUrlStr)); m_environment.addDefines(IDefinesAndIncludesManager::manager()->definesInBackground(tuUrlStr)); m_environment.setPchInclude(userDefinedPchIncludeForFile(tuUrlStr)); } if (abortRequested()) { return; } // NOTE: we must have all declarations, contexts and uses available for files that are opened in the editor // it is very hard to check this for all included files of this TU, and previously lead to problems // when we tried to skip function bodies as an optimization for files that where not open in the editor. // now, we always build everything, which is correct but a tad bit slower. we can try to optimize later. setMinimumFeatures(static_cast(minimumFeatures() | TopDUContext::AllDeclarationsContextsAndUses)); if (minimumFeatures() & AttachASTWithoutUpdating) { // The context doesn't need to be updated, but has no AST attached (restored from disk), // so attach AST to it, without updating DUChain ParseSession session(createSessionData()); DUChainWriteLocker lock; auto ctx = DUChainUtils::standardContextForUrl(document().toUrl()); if (!ctx) { clangDebug() << "Lost context while attaching AST"; return; } ctx->setAst(IAstContainer::Ptr(session.data())); if (minimumFeatures() & UpdateHighlighting) { lock.unlock(); languageSupport()->codeHighlighting()->highlightDUChain(ctx); } return; } { UrlParseLock urlLock(document()); if (abortRequested() || !isUpdateRequired(ParseSession::languageString())) { return; } } ParseSession session(ClangIntegration::DUChainUtils::findParseSessionData(document(), m_environment.translationUnitUrl())); if (abortRequested()) { return; } if (!session.data() || !session.reparse(m_unsavedFiles, m_environment)) { session.setData(createSessionData()); } if (!session.unit()) { // failed to parse file, unpin and don't try again clang()->index()->unpinTranslationUnitForUrl(document()); return; } if (!clang_getFile(session.unit(), document().byteArray().constData())) { // this parse job's document does not exist in the pinned translation unit // so we need to unpin and re-add this document // Ideally we'd reset m_environment and session, but this is much simpler // and shouldn't be a common case clang()->index()->unpinTranslationUnitForUrl(document()); if (!(minimumFeatures() & Rescheduled)) { auto features = static_cast(minimumFeatures() | Rescheduled); ICore::self()->languageController()->backgroundParser()->addDocument(document(), features, priority()); } return; } Imports imports = ClangHelpers::tuImports(session.unit()); IncludeFileContexts includedFiles; if (auto pch = clang()->index()->pch(m_environment)) { auto pchFile = pch->mapFile(session.unit()); includedFiles = pch->mapIncludes(session.unit()); includedFiles.insert(pchFile, pch->context()); auto tuFile = clang_getFile(session.unit(), m_environment.translationUnitUrl().byteArray().constData()); imports.insert(tuFile, { pchFile, CursorInRevision(0, 0) } ); } if (abortRequested()) { return; } auto context = ClangHelpers::buildDUChain(session.mainFile(), imports, session, minimumFeatures(), includedFiles, clang()->index()); setDuChain(context); if (abortRequested()) { return; } if (context) { if (minimumFeatures() & TopDUContext::AST) { DUChainWriteLocker lock; context->setAst(IAstContainer::Ptr(session.data())); } #ifdef QT_DEBUG DUChainReadLocker lock; auto file = parsingEnvironmentFile(context); Q_ASSERT(file); // verify that features and environment where properly set in ClangHelpers::buildDUChain Q_ASSERT(file->featuresSatisfied(TopDUContext::Features(minimumFeatures() & ~TopDUContext::ForceUpdateRecursive))); if (trackerForUrl(context->url())) { Q_ASSERT(file->featuresSatisfied(TopDUContext::AllDeclarationsContextsAndUses)); } #endif } foreach(const auto& context, includedFiles) { if (!context) { continue; } { // prefer the editor modification revision, instead of the on-disk revision auto it = m_unsavedRevisions.find(context->url()); if (it != m_unsavedRevisions.end()) { DUChainWriteLocker lock; auto file = parsingEnvironmentFile(context); Q_ASSERT(file); file->setModificationRevision(it.value()); } } if (trackerForUrl(context->url())) { if (clang()->index()->translationUnitForUrl(context->url()) == m_environment.translationUnitUrl()) { // cache the parse session and the contained translation unit for this chain // this then allows us to quickly reparse the document if it is changed by // the user // otherwise no editor component is open for this document and we can dispose // the TU to save memory // share the session data with all contexts that are pinned to this TU DUChainWriteLocker lock; context->setAst(IAstContainer::Ptr(session.data())); } languageSupport()->codeHighlighting()->highlightDUChain(context); } } } ParseSessionData::Ptr ClangParseJob::createSessionData() const { return ParseSessionData::Ptr(new ParseSessionData(m_unsavedFiles, clang()->index(), m_environment, ParseSessionData::NoOption)); } const ParsingEnvironment* ClangParseJob::environment() const { return &m_environment; } diff --git a/languages/clang/clangparsejob.h b/languages/clang/clangparsejob.h index 205580e5d6..7c0ab7aac1 100644 --- a/languages/clang/clangparsejob.h +++ b/languages/clang/clangparsejob.h @@ -1,62 +1,63 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CLANGPARSEJOB_H #define CLANGPARSEJOB_H #include #include #include "duchain/clangparsingenvironment.h" #include "duchain/unsavedfile.h" class ClangSupport; class ParseSessionData; class ClangParseJob : public KDevelop::ParseJob { public: ClangParseJob(const KDevelop::IndexedString& url, KDevelop::ILanguageSupport* languageSupport); ClangSupport* clang() const; enum CustomFeatures { Rescheduled = (KDevelop::TopDUContext::LastFeature << 1), AttachASTWithoutUpdating = (Rescheduled << 1), ///< Used when context is up to date, but has no AST attached. UpdateHighlighting = (AttachASTWithoutUpdating << 1) ///< Used when we only need to update highlighting }; protected: virtual void run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread) override; virtual const KDevelop::ParsingEnvironment* environment() const override; private: QExplicitlySharedDataPointer createSessionData() const; ClangParsingEnvironment m_environment; QVector m_unsavedFiles; + bool m_tuDocumentIsUnsaved = false; QHash m_unsavedRevisions; }; #endif // CLANGPARSEJOB_H diff --git a/languages/clang/clangsupport.cpp b/languages/clang/clangsupport.cpp index 153211e833..6959407f2f 100644 --- a/languages/clang/clangsupport.cpp +++ b/languages/clang/clangsupport.cpp @@ -1,413 +1,419 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "clangsupport.h" #include "clangparsejob.h" #include "version.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "util/clangutils.h" #include "codecompletion/model.h" #include "clanghighlighting.h" #include #include #include #include #include #include +#include "codegen/clangrefactoring.h" #include "codegen/adaptsignatureassistant.h" #include "duchain/documentfinderhelpers.h" #include "duchain/clangindex.h" #include "duchain/navigationwidget.h" #include "duchain/macrodefinition.h" #include "duchain/clangparsingenvironmentfile.h" #include "duchain/duchainutils.h" #include #include #include #include -#include #include #include #include #include #include #include #include #include #include "clangsettings/sessionsettings/sessionsettings.h" #include #include #include #include #include K_PLUGIN_FACTORY_WITH_JSON(KDevClangSupportFactory, "kdevclangsupport.json", registerPlugin(); ) using namespace KDevelop; namespace { QPair lineInDocument(const QUrl &url, const KTextEditor::Cursor& position) { KDevelop::IDocument* doc = ICore::self()->documentController()->documentForUrl(url); if (!doc || !doc->textDocument() || !ICore::self()->documentController()->activeTextDocumentView()) { return {}; } const int lineNumber = position.line(); const int lineLength = doc->textDocument()->lineLength(lineNumber); KTextEditor::Range range(lineNumber, 0, lineNumber, lineLength); QString line = doc->textDocument()->text(range); return {line, range}; } QPair importedContextForPosition(const QUrl &url, const KTextEditor::Cursor& position) { auto pair = lineInDocument(url, position); const QString line = pair.first; if (line.isEmpty()) return {{}, KTextEditor::Range::invalid()}; KTextEditor::Range wordRange = ClangUtils::rangeForIncludePathSpec(line, pair.second); if (!wordRange.isValid()) { return {{}, KTextEditor::Range::invalid()}; } // Since this is called by the editor while editing, use a fast timeout so the editor stays responsive DUChainReadLocker lock(nullptr, 100); if (!lock.locked()) { clangDebug() << "Failed to lock the du-chain in time"; return {TopDUContextPointer(), KTextEditor::Range::invalid()}; } TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (line.isEmpty() || !topContext || !topContext->parsingEnvironmentFile()) { return {TopDUContextPointer(), KTextEditor::Range::invalid()}; } // It's an #include, find out which file was included at the given line foreach(const DUContext::Import &imported, topContext->importedParentContexts()) { auto context = imported.context(nullptr); if (context) { if(topContext->transformFromLocalRevision(topContext->importPosition(context)).line() == wordRange.start().line()) { if (auto importedTop = dynamic_cast(context)) return {TopDUContextPointer(importedTop), wordRange}; } } } // The last resort. Check if the file is already included (maybe recursively from another files). // This is needed as clang doesn't visit (clang_getInclusions) those inclusions. // TODO: Maybe create an assistant that'll report whether the file is already included? auto includeName = line.mid(wordRange.start().column(), wordRange.end().column() - wordRange.start().column()); if (!includeName.isEmpty()) { if (includeName.startsWith(QLatin1Char('.'))) { const Path dir = Path(url).parent(); includeName = Path(dir, includeName).toLocalFile(); } const auto recursiveImports = topContext->recursiveImportIndices(); auto iterator = recursiveImports.iterator(); while (iterator) { const auto str = (*iterator).url().str(); if (str == includeName || (str.endsWith(includeName) && str[str.size()-includeName.size()-1] == QLatin1Char('/'))) { return {TopDUContextPointer((*iterator).data()), wordRange}; } ++iterator; } } return {{}, KTextEditor::Range::invalid()}; } QPair macroExpansionForPosition(const QUrl &url, const KTextEditor::Cursor& position) { TopDUContext* topContext = DUChainUtils::standardContextForUrl(url); if (topContext) { int useAt = topContext->findUseAt(topContext->transformToLocalRevision(position)); if (useAt >= 0) { Use use = topContext->uses()[useAt]; if (dynamic_cast(use.usedDeclaration(topContext))) { return {TopDUContextPointer(topContext), use}; } } } return {{}, Use()}; } } ClangSupport::ClangSupport(QObject* parent, const QVariantList& ) : IPlugin( QStringLiteral("kdevclangsupport"), parent ) , ILanguageSupport() , m_highlighting(nullptr) , m_refactoring(nullptr) , m_index(nullptr) { KDEV_USE_EXTENSION_INTERFACE( KDevelop::ILanguageSupport ) setXMLFile( QStringLiteral("kdevclangsupport.rc") ); ClangIntegration::DUChainUtils::registerDUChainItems(); m_highlighting = new ClangHighlighting(this); - m_refactoring = new BasicRefactoring(this); + m_refactoring = new ClangRefactoring(this); m_index.reset(new ClangIndex); auto model = new KDevelop::CodeCompletion( this, new ClangCodeCompletionModel(m_index.data(), this), name() ); // TODO: use direct signal/slot connect syntax for 5.1 connect(model, SIGNAL(registeredToView(KTextEditor::View*)), this, SLOT(disableKeywordCompletion(KTextEditor::View*))); connect(model, SIGNAL(unregisteredFromView(KTextEditor::View*)), this, SLOT(enableKeywordCompletion(KTextEditor::View*))); for(const auto& type : DocumentFinderHelpers::mimeTypesList()){ KDevelop::IBuddyDocumentFinder::addFinder(type, this); } auto assistantsManager = core()->languageController()->staticAssistantsManager(); assistantsManager->registerAssistant(StaticAssistant::Ptr(new RenameAssistant(this))); assistantsManager->registerAssistant(StaticAssistant::Ptr(new AdaptSignatureAssistant(this))); connect(ICore::self()->documentController(), &IDocumentController::documentActivated, this, &ClangSupport::documentActivated); } ClangSupport::~ClangSupport() { parseLock()->lockForWrite(); // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state parseLock()->unlock(); for(const auto& type : DocumentFinderHelpers::mimeTypesList()) { KDevelop::IBuddyDocumentFinder::removeFinder(type); } ClangIntegration::DUChainUtils::unregisterDUChainItems(); } KDevelop::ConfigPage* ClangSupport::configPage(int number, QWidget* parent) { return number == 0 ? new SessionSettings(parent) : nullptr; } int ClangSupport::configPages() const { return 1; } ParseJob* ClangSupport::createParseJob(const IndexedString& url) { return new ClangParseJob(url, this); } QString ClangSupport::name() const { return QStringLiteral("clang"); } ICodeHighlighting* ClangSupport::codeHighlighting() const { return m_highlighting; } BasicRefactoring* ClangSupport::refactoring() const { return m_refactoring; } ClangIndex* ClangSupport::index() { return m_index.data(); } bool ClangSupport::areBuddies(const QUrl &url1, const QUrl& url2) { return DocumentFinderHelpers::areBuddies(url1, url2); } bool ClangSupport::buddyOrder(const QUrl &url1, const QUrl& url2) { return DocumentFinderHelpers::buddyOrder(url1, url2); } QVector< QUrl > ClangSupport::getPotentialBuddies(const QUrl &url) const { return DocumentFinderHelpers::getPotentialBuddies(url); } void ClangSupport::createActionsForMainWindow (Sublime::MainWindow* /*window*/, QString& _xmlFile, KActionCollection& actions) { _xmlFile = xmlFile(); QAction* renameDeclarationAction = actions.addAction(QStringLiteral("code_rename_declaration")); renameDeclarationAction->setText( i18n("Rename Declaration") ); renameDeclarationAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); actions.setDefaultShortcut(renameDeclarationAction, Qt::CTRL | Qt::SHIFT | Qt::Key_R); connect(renameDeclarationAction, &QAction::triggered, - m_refactoring, &BasicRefactoring::executeRenameAction); + m_refactoring, &ClangRefactoring::executeRenameAction); + + QAction* moveIntoSourceAction = actions.addAction(QStringLiteral("code_move_definition")); + moveIntoSourceAction->setText(i18n("Move into Source")); + actions.setDefaultShortcut(moveIntoSourceAction, Qt::CTRL | Qt::ALT | Qt::Key_S); + connect(moveIntoSourceAction, &QAction::triggered, + m_refactoring, &ClangRefactoring::executeMoveIntoSourceAction); } KDevelop::ContextMenuExtension ClangSupport::contextMenuExtension(KDevelop::Context* context) { ContextMenuExtension cm; EditorContext *ec = dynamic_cast(context); if (ec && ICore::self()->languageController()->languagesForUrl(ec->url()).contains(this)) { // It's a C++ file, let's add our context menu. m_refactoring->fillContextMenu(cm, context); } return cm; } KTextEditor::Range ClangSupport::specialLanguageObjectRange(const QUrl &url, const KTextEditor::Cursor& position) { DUChainReadLocker lock; const QPair macroExpansion = macroExpansionForPosition(url, position); if (macroExpansion.first) { return macroExpansion.first->transformFromLocalRevision(macroExpansion.second.m_range); } const QPair import = importedContextForPosition(url, position); if(import.first) { return import.second; } return KTextEditor::Range::invalid(); } QPair ClangSupport::specialLanguageObjectJumpCursor(const QUrl &url, const KTextEditor::Cursor& position) { const QPair import = importedContextForPosition(url, position); DUChainReadLocker lock; if (import.first) { return qMakePair(import.first->url().toUrl(), KTextEditor::Cursor(0,0)); } return {{}, KTextEditor::Cursor::invalid()}; } QWidget* ClangSupport::specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) { DUChainReadLocker lock; const QPair macroExpansion = macroExpansionForPosition(url, position); if (macroExpansion.first) { Declaration* declaration = macroExpansion.second.usedDeclaration(macroExpansion.first.data()); const MacroDefinition::Ptr macroDefinition(dynamic_cast(declaration)); Q_ASSERT(macroDefinition); auto rangeInRevision = macroExpansion.first->transformFromLocalRevision(macroExpansion.second.m_range.start); return new ClangNavigationWidget(macroDefinition, DocumentCursor(IndexedString(url), rangeInRevision)); } const QPair import = importedContextForPosition(url, position); if (import.first) { return import.first->createNavigationWidget(); } return nullptr; } TopDUContext* ClangSupport::standardContext(const QUrl &url, bool /*proxyContext*/) { ClangParsingEnvironment env; return DUChain::self()->chainForDocument(url, &env); } void ClangSupport::documentActivated(IDocument* doc) { TopDUContext::Features features; { DUChainReadLocker lock; auto ctx = DUChainUtils::standardContextForUrl(doc->url()); if (!ctx) { return; } auto file = ctx->parsingEnvironmentFile(); if (!file) { return; } if (file->type() != CppParsingEnvironment) { return; } if (file->needsUpdate()) { return; } features = ctx->features(); } const auto indexedUrl = IndexedString(doc->url()); auto sessionData = ClangIntegration::DUChainUtils::findParseSessionData(indexedUrl, index()->translationUnitForUrl(IndexedString(doc->url()))); if (sessionData) { return; } if ((features & TopDUContext::AllDeclarationsContextsAndUses) != TopDUContext::AllDeclarationsContextsAndUses) { // the file was parsed in simplified mode, we need to reparse it to get all data // now that its opened in the editor features = TopDUContext::AllDeclarationsContextsAndUses; } else { features = static_cast(ClangParseJob::AttachASTWithoutUpdating | features); if (ICore::self()->languageController()->backgroundParser()->isQueued(indexedUrl)) { // The document is already scheduled for parsing (happens when opening a project with an active document) // The background parser will optimize the previous request out, so we need to update highlighting features = static_cast(ClangParseJob::UpdateHighlighting | features); } } ICore::self()->languageController()->backgroundParser()->addDocument(indexedUrl, features); } static void setKeywordCompletion(KTextEditor::View* view, bool enabled) { if (auto config = qobject_cast(view)) { config->setConfigValue(QStringLiteral("keyword-completion"), enabled); } } void ClangSupport::disableKeywordCompletion(KTextEditor::View* view) { setKeywordCompletion(view, false); } void ClangSupport::enableKeywordCompletion(KTextEditor::View* view) { setKeywordCompletion(view, true); } #include "clangsupport.moc" diff --git a/languages/clang/clangsupport.h b/languages/clang/clangsupport.h index 9d4a89ee27..93b794fe7c 100644 --- a/languages/clang/clangsupport.h +++ b/languages/clang/clangsupport.h @@ -1,100 +1,100 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef KDEVCLANGSUPPORT_H #define KDEVCLANGSUPPORT_H #include #include #include #include #include class ClangIndex; +class ClangRefactoring; namespace KDevelop { -class BasicRefactoring; class IDocument; } namespace KTextEditor { class View; } class ClangSupport : public KDevelop::IPlugin, public KDevelop::ILanguageSupport, public KDevelop::IBuddyDocumentFinder { Q_OBJECT Q_INTERFACES(KDevelop::ILanguageSupport) public: explicit ClangSupport(QObject *parent, const QVariantList& args = QVariantList()); ~ClangSupport() override; /** Name Of the Language */ QString name() const override; /** Parsejob used by background parser to parse given url */ KDevelop::ParseJob *createParseJob(const KDevelop::IndexedString &url) override; /** the code highlighter */ KDevelop::ICodeHighlighting* codeHighlighting() const override; KDevelop::BasicRefactoring* refactoring() const override; void createActionsForMainWindow(Sublime::MainWindow* window, QString& xmlFile, KActionCollection& actions) override; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context) override; KTextEditor::Range specialLanguageObjectRange(const QUrl &url, const KTextEditor::Cursor& position) override; QPair specialLanguageObjectJumpCursor(const QUrl &url, const KTextEditor::Cursor& position) override; QWidget* specialLanguageObjectNavigationWidget(const QUrl &url, const KTextEditor::Cursor& position) override; ClangIndex* index(); KDevelop::TopDUContext* standardContext(const QUrl &url, bool proxyContext = false) override; KDevelop::ConfigPage* configPage(int number, QWidget *parent) override; int configPages() const override; //BEGIN IBuddyDocumentFinder bool areBuddies(const QUrl &url1, const QUrl& url2) override; bool buddyOrder(const QUrl &url1, const QUrl& url2) override; QVector< QUrl > getPotentialBuddies(const QUrl &url) const override; //END IBuddyDocumentFinder private slots: void documentActivated(KDevelop::IDocument* doc); void disableKeywordCompletion(KTextEditor::View* view); void enableKeywordCompletion(KTextEditor::View* view); private: KDevelop::ICodeHighlighting *m_highlighting; - KDevelop::BasicRefactoring *m_refactoring; + ClangRefactoring *m_refactoring; QScopedPointer m_index; }; #endif diff --git a/languages/clang/codegen/adaptsignatureaction.cpp b/languages/clang/codegen/adaptsignatureaction.cpp index fb9d66ed3c..7a3bb193dd 100644 --- a/languages/clang/codegen/adaptsignatureaction.cpp +++ b/languages/clang/codegen/adaptsignatureaction.cpp @@ -1,199 +1,126 @@ /* Copyright 2009 David Nolden Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "adaptsignatureaction.h" #include "codegenhelper.h" #include "../duchain/duchainutils.h" #include "../util/clangdebug.h" #include #include -#include #include #include #include #include -#include #include #include using namespace KDevelop; -QString makeSignatureString(const Declaration* functionDecl, const Signature& signature, const bool editingDefinition) -{ - if (!functionDecl || !functionDecl->internalContext()) { - return {}; - } - const auto visibilityFrom = functionDecl->internalContext()->parentContext(); - if (!visibilityFrom) { - return {}; - } - - QString ret; - - if (!editingDefinition) { - auto classMember = dynamic_cast(functionDecl); - if (classMember && classMember->isStatic()) { - ret += QLatin1String("static "); - } - } - - // constructors don't have a return type - if (signature.returnType.isValid()) { - ret += CodegenHelper::simplifiedTypeString(signature.returnType.abstractType(), - visibilityFrom); - ret += QLatin1Char(' '); - } - - ret += editingDefinition ? functionDecl->qualifiedIdentifier().toString() : functionDecl->identifier().toString(); - - ret += QLatin1Char('('); - int pos = 0; - - foreach(const ParameterItem &item, signature.parameters) - { - if (pos != 0) { - ret += QLatin1String(", "); - } - - ///TODO: merge common code with helpers.cpp::createArgumentList - AbstractType::Ptr type = item.first.abstractType(); - - QString arrayAppendix; - ArrayType::Ptr arrayType; - while ((arrayType = type.cast())) { - type = arrayType->elementType(); - //note: we have to prepend since we iterate from outside, i.e. from right to left. - if (arrayType->dimension()) { - arrayAppendix.prepend(QStringLiteral("[%1]").arg(arrayType->dimension())); - } else { - // dimensionless - arrayAppendix.prepend(QLatin1String("[]")); - } - } - ret += CodegenHelper::simplifiedTypeString(type, - visibilityFrom); - - if (!item.second.isEmpty()) { - ret += QLatin1Char(' ') + item.second; - } - ret += arrayAppendix; - if (signature.defaultParams.size() > pos && !signature.defaultParams[pos].isEmpty()) { - ret += QLatin1String(" = ") + signature.defaultParams[pos]; - } - ++pos; - } - ret += QLatin1Char(')'); - if (signature.isConst) { - ret += QLatin1String(" const"); - } - return ret; -} - AdaptSignatureAction::AdaptSignatureAction(const DeclarationId& definitionId, ReferencedTopDUContext definitionContext, const Signature& oldSignature, const Signature& newSignature, bool editingDefinition, QList renameActions ) : m_otherSideId(definitionId) , m_otherSideTopContext(definitionContext) , m_oldSignature(oldSignature) , m_newSignature(newSignature) , m_editingDefinition(editingDefinition) , m_renameActions(renameActions) { } AdaptSignatureAction::~AdaptSignatureAction() { qDeleteAll(m_renameActions); } QString AdaptSignatureAction::description() const { return m_editingDefinition ? i18n("Update declaration signature") : i18n("Update definition signature"); } QString AdaptSignatureAction::toolTip() const { DUChainReadLocker lock; auto declaration = m_otherSideId.getDeclaration(m_otherSideTopContext.data()); if (!declaration) { return {}; } return i18n("Update %1 signature\nfrom: %2\nto: %3", m_editingDefinition ? i18n("declaration") : i18n("definition"), - makeSignatureString(declaration, m_oldSignature, m_editingDefinition), - makeSignatureString(declaration, m_newSignature, !m_editingDefinition)); + CodegenHelper::makeSignatureString(declaration, m_oldSignature, m_editingDefinition), + CodegenHelper::makeSignatureString(declaration, m_newSignature, !m_editingDefinition)); } void AdaptSignatureAction::execute() { DUChainReadLocker lock; IndexedString url = m_otherSideTopContext->url(); lock.unlock(); m_otherSideTopContext = DUChain::self()->waitForUpdate(url, TopDUContext::AllDeclarationsContextsAndUses); if (!m_otherSideTopContext) { clangDebug() << "failed to update" << url.str(); return; } lock.lock(); Declaration* otherSide = m_otherSideId.getDeclaration(m_otherSideTopContext.data()); if (!otherSide) { clangDebug() << "could not find definition"; return; } DUContext* functionContext = DUChainUtils::getFunctionContext(otherSide); if (!functionContext) { clangDebug() << "no function context"; return; } if (!functionContext || functionContext->type() != DUContext::Function) { clangDebug() << "no correct function context"; return; } DocumentChangeSet changes; KTextEditor::Range parameterRange = ClangIntegration::DUChainUtils::functionSignatureRange(otherSide); - QString newText = makeSignatureString(otherSide, m_newSignature, !m_editingDefinition); + QString newText = CodegenHelper::makeSignatureString(otherSide, m_newSignature, !m_editingDefinition); if (!m_editingDefinition) { // append a newline after the method signature in case the method definition follows newText += QLatin1Char('\n'); } DocumentChange changeParameters(functionContext->url(), parameterRange, QString(), newText); changeParameters.m_ignoreOldText = true; changes.addChange(changeParameters); changes.setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange); DocumentChangeSet::ChangeResult result = changes.applyAllChanges(); if (!result) { KMessageBox::error(0, i18n("Failed to apply changes: %1", result.m_failureReason)); } emit executed(this); foreach(RenameAction * renAct, m_renameActions) { renAct->execute(); } } #include "moc_adaptsignatureaction.cpp" diff --git a/languages/clang/codegen/clangrefactoring.cpp b/languages/clang/codegen/clangrefactoring.cpp new file mode 100644 index 0000000000..4b10087660 --- /dev/null +++ b/languages/clang/codegen/clangrefactoring.cpp @@ -0,0 +1,231 @@ +/* + * This file is part of KDevelop + * + * Copyright 2015 Sergey Kalinichev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "clangrefactoring.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "duchain/clanghelpers.h" +#include "duchain/documentfinderhelpers.h" +#include "duchain/duchainutils.h" +#include "sourcemanipulation.h" +#include "util/clangdebug.h" + +using namespace KDevelop; + +ClangRefactoring::ClangRefactoring(QObject* parent) + : BasicRefactoring(parent) +{ + qRegisterMetaType(); +} + +void ClangRefactoring::fillContextMenu(ContextMenuExtension& extension, Context* context) +{ + auto declContext = dynamic_cast(context); + if (!declContext) { + return; + } + + DUChainReadLocker lock; + + auto declaration = declContext->declaration().data(); + if (!declaration) { + return; + } + + QFileInfo fileInfo(declaration->topContext()->url().str()); + if (!fileInfo.isWritable()) { + return; + } + + auto action = new QAction(i18n("Rename %1", declaration->qualifiedIdentifier().toString()), this); + action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); + action->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); + connect(action, &QAction::triggered, this, &ClangRefactoring::executeRenameAction); + + extension.addAction(ContextMenuExtension::RefactorGroup, action); + + if (!validCandidateToMoveIntoSource(declaration)) { + return; + } + + action = new QAction( + i18n("Create separate definition for %1", declaration->qualifiedIdentifier().toString()), this); + action->setData(QVariant::fromValue(IndexedDeclaration(declaration))); + connect(action, &QAction::triggered, this, &ClangRefactoring::executeMoveIntoSourceAction); + extension.addAction(ContextMenuExtension::RefactorGroup, action); +} + +bool ClangRefactoring::validCandidateToMoveIntoSource(Declaration* decl) +{ + if (!decl || !decl->isFunctionDeclaration() || !decl->type()) { + return false; + } + + if (!decl->internalContext() || decl->internalContext()->type() != DUContext::Function) { + return false; + } + + auto childCtx = decl->internalContext()->childContexts(); + if (childCtx.isEmpty()) { + return false; + } + + auto ctx = childCtx.first(); + if (!ctx || ctx->type() != DUContext::Other) { + return false; + } + + auto functionDecl = dynamic_cast(decl); + if (!functionDecl || functionDecl->isInline()) { + return false; + } + + return true; +} + +QString ClangRefactoring::moveIntoSource(const IndexedDeclaration& iDecl) +{ + DUChainReadLocker lock; + auto decl = iDecl.data(); + if (!decl) { + return i18n("No declaration under cursor"); + } + + const auto headerUrl = decl->url(); + auto targetUrl = DocumentFinderHelpers::sourceForHeader(headerUrl.str()); + + if (targetUrl.isEmpty() || targetUrl == headerUrl.str()) { + // TODO: Create source file if it doesn't exist + return i18n("No source file available for %1.", headerUrl.str()); + } + + lock.unlock(); + const IndexedString indexedTargetUrl(targetUrl); + auto top + = DUChain::self()->waitForUpdate(headerUrl, KDevelop::TopDUContext::AllDeclarationsAndContexts); + auto targetTopContext + = DUChain::self()->waitForUpdate(indexedTargetUrl, KDevelop::TopDUContext::AllDeclarationsAndContexts); + lock.lock(); + + if (!targetTopContext) { + return i18n("Failed to update DUChain for %1.", targetUrl); + } + + if (!top || !iDecl.data() || iDecl.data() != decl) { + return i18n("Declaration lost while updating."); + } + + clangDebug() << "moving" << decl->qualifiedIdentifier(); + + if (!validCandidateToMoveIntoSource(decl)) { + return i18n("Cannot create definition for this declaration."); + } + + auto otherCtx = decl->internalContext()->childContexts().first(); + + auto code = createCodeRepresentation(headerUrl); + if (!code) { + return i18n("No document for %1", headerUrl.str()); + } + + auto bodyRange = otherCtx->rangeInCurrentRevision(); + + auto prefixRange(ClangIntegration::DUChainUtils::functionSignatureRange(decl)); + const auto prefixText = code->rangeText(prefixRange); + for (int i = prefixText.length() - 1; i >= 0 && prefixText.at(i).isSpace(); --i) { + if (bodyRange.start().column() == 0) { + bodyRange.setStart(bodyRange.start() - KTextEditor::Cursor(1, 0)); + if (bodyRange.start().line() == prefixRange.start().line()) { + bodyRange.setStart(KTextEditor::Cursor(bodyRange.start().line(), prefixRange.start().column() + i)); + } else { + int lastNewline = prefixText.lastIndexOf(QLatin1Char('\n'), i - 1); + bodyRange.setStart(KTextEditor::Cursor(bodyRange.start().line(), i - lastNewline - 1)); + } + } else { + bodyRange.setStart(bodyRange.start() - KTextEditor::Cursor(0, 1)); + } + } + + const QString body = code->rangeText(bodyRange); + SourceCodeInsertion ins(targetTopContext); + auto parentId = decl->internalContext()->parentContext()->scopeIdentifier(false); + + ins.setSubScope(parentId); + + Identifier id(IndexedString(decl->qualifiedIdentifier().mid(parentId.count()).toString())); + clangDebug() << "id:" << id; + + if (!ins.insertFunctionDeclaration(decl, id, body)) { + return i18n("Insertion failed"); + } + lock.unlock(); + + auto applied = ins.changes().applyAllChanges(); + if (!applied) { + return i18n("Applying changes failed: %1", applied.m_failureReason); + } + + // replace function body with a semicolon + DocumentChangeSet changeHeader; + changeHeader.addChange(DocumentChange(headerUrl, bodyRange, body, QStringLiteral(";"))); + applied = changeHeader.applyAllChanges(); + if (!applied) { + return i18n("Applying changes failed: %1", applied.m_failureReason); + } + + ICore::self()->languageController()->backgroundParser()->addDocument(headerUrl); + ICore::self()->languageController()->backgroundParser()->addDocument(indexedTargetUrl); + + return {}; +} + +void ClangRefactoring::executeMoveIntoSourceAction() +{ + auto action = qobject_cast(sender()); + Q_ASSERT(action); + + auto iDecl = action->data().value(); + if (!iDecl.isValid()) { + iDecl = declarationUnderCursor(false); + } + + const auto error = moveIntoSource(iDecl); + if (!error.isEmpty()) { + KMessageBox::error(nullptr, error); + } +} diff --git a/languages/clang/codegen/clangrefactoring.h b/languages/clang/codegen/clangrefactoring.h new file mode 100644 index 0000000000..f39239f54c --- /dev/null +++ b/languages/clang/codegen/clangrefactoring.h @@ -0,0 +1,58 @@ +/* + * This file is part of KDevelop + * + * Copyright 2015 Sergey Kalinichev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef SIMPLEREFACTORING_H +#define SIMPLEREFACTORING_H + +#include "clangprivateexport.h" + +#include + +namespace KDevelop +{ +class Context; +class ContextMenuExtension; +class Declaration; +} + +class KDEVCLANGPRIVATE_EXPORT ClangRefactoring : public KDevelop::BasicRefactoring +{ + Q_OBJECT + +public: + explicit ClangRefactoring(QObject* parent = 0); + + void fillContextMenu(KDevelop::ContextMenuExtension& extension, KDevelop::Context* context) override; + + QString moveIntoSource(const KDevelop::IndexedDeclaration& iDecl); + + using KDevelop::BasicRefactoring::executeRenameAction; + +public slots: + void executeMoveIntoSourceAction(); + +private: + bool validCandidateToMoveIntoSource(KDevelop::Declaration* decl); +}; + +#endif diff --git a/languages/clang/codegen/codegenhelper.cpp b/languages/clang/codegen/codegenhelper.cpp index e21d86d411..ecd9effd42 100644 --- a/languages/clang/codegen/codegenhelper.cpp +++ b/languages/clang/codegen/codegenhelper.cpp @@ -1,408 +1,469 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "codegenhelper.h" +#include "adaptsignatureaction.h" + +#include +#include #include #include #include #include #include #include using namespace KDevelop; namespace { IndexedTypeIdentifier stripPrefixIdentifiers(const IndexedTypeIdentifier& id, const QualifiedIdentifier& strip); Identifier stripPrefixIdentifiers(const Identifier& id, const QualifiedIdentifier& strip) { Identifier ret(id); ret.clearTemplateIdentifiers(); for (unsigned int a = 0; a < id.templateIdentifiersCount(); ++a) { ret.appendTemplateIdentifier(stripPrefixIdentifiers(id.templateIdentifier(a), strip)); } return ret; } IndexedTypeIdentifier stripPrefixIdentifiers(const IndexedTypeIdentifier& id, const QualifiedIdentifier& strip) { QualifiedIdentifier oldId(id.identifier().identifier()); QualifiedIdentifier qid; int commonPrefix = 0; for (; commonPrefix < oldId.count() - 1 && commonPrefix < strip.count(); ++commonPrefix) { if (strip.at(commonPrefix).toString() != oldId.at(commonPrefix).toString()) { break; } } for (int a = commonPrefix; a < oldId.count(); ++a) { qid.push(stripPrefixIdentifiers(oldId.at(a), strip)); } IndexedTypeIdentifier ret(id); ret.setIdentifier(qid); return ret; } int reservedIdentifierCount(const QString &name) { QStringList l = name.split(QLatin1String("::")); int ret = 0; foreach(const QString &s, l) if (s.startsWith(QLatin1Char('_'))) { ++ret; } return ret; } uint buildIdentifierForType(const AbstractType::Ptr& type, IndexedTypeIdentifier& id, uint pointerLevel, TopDUContext* top) { if (!type) { return pointerLevel; } TypePtr refType = type.cast(); if (refType) { id.setIsReference(true); if (refType->modifiers() & AbstractType::ConstModifier) { id.setIsConstant(true); } return buildIdentifierForType(refType->baseType(), id, pointerLevel, top); } TypePtr pointerType = type.cast(); if (pointerType) { ++pointerLevel; uint maxPointerLevel = buildIdentifierForType(pointerType->baseType(), id, pointerLevel, top); if (type->modifiers() & AbstractType::ConstModifier) { id.setIsConstPointer(maxPointerLevel - pointerLevel, true); } if (static_cast(id.pointerDepth()) < pointerLevel) { id.setPointerDepth(pointerLevel); } return maxPointerLevel; } - IdentifiedType* idType = dynamic_cast(type.data()); - if (idType) { - Declaration* decl = idType->declaration(top); - if (decl) { - id.setIdentifier(decl->qualifiedIdentifier()); - } else { - id.setIdentifier(idType->qualifiedIdentifier()); - } - } else { - //Just create it as an expression - AbstractType::Ptr useTypeText = type; - if (type->modifiers() & AbstractType::ConstModifier) { - //Remove the 'const' modifier, as it will be added to the type-identifier below - useTypeText = type->indexed().abstractType(); - useTypeText->setModifiers(useTypeText->modifiers() & (~AbstractType::ConstModifier)); - } - id.setIdentifier(QualifiedIdentifier(useTypeText->toString(), true)); + AbstractType::Ptr useTypeText = type; + if (type->modifiers() & AbstractType::ConstModifier) { + //Remove the 'const' modifier, as it will be added to the type-identifier below + useTypeText = type->indexed().abstractType(); + useTypeText->setModifiers(useTypeText->modifiers() & (~AbstractType::ConstModifier)); } + id.setIdentifier(QualifiedIdentifier(useTypeText->toString(), true)); + if (type->modifiers() & AbstractType::ConstModifier) { id.setIsConstant(true); } if (type->modifiers() & AbstractType::VolatileModifier) { id.setIsVolatile(true); } return pointerLevel; } IndexedTypeIdentifier identifierForType(const AbstractType::Ptr& type, TopDUContext* top) { IndexedTypeIdentifier ret; buildIdentifierForType(type, ret, 0, top); return ret; } IndexedTypeIdentifier removeTemplateParameters(const IndexedTypeIdentifier& identifier, int behindPosition); Identifier removeTemplateParameters(const Identifier& id, int behindPosition) { Identifier ret(id); ret.clearTemplateIdentifiers(); for (unsigned int a = 0; a < id.templateIdentifiersCount(); ++a) { IndexedTypeIdentifier replacement = removeTemplateParameters(id.templateIdentifier(a), behindPosition); if (( int ) a < behindPosition) { ret.appendTemplateIdentifier(replacement); } else { ret.appendTemplateIdentifier(IndexedTypeIdentifier(QualifiedIdentifier(QStringLiteral("...")))); break; } } return ret; } IndexedTypeIdentifier removeTemplateParameters(const IndexedTypeIdentifier& identifier, int behindPosition) { IndexedTypeIdentifier ret(identifier); QualifiedIdentifier oldId(identifier.identifier().identifier()); QualifiedIdentifier qid; for (int a = 0; a < oldId.count(); ++a) { qid.push(removeTemplateParameters(oldId.at(a), behindPosition)); } ret.setIdentifier(qid); return ret; } IndexedType removeConstModifier(const IndexedType& indexedType) { AbstractType::Ptr type = indexedType.abstractType(); type->setModifiers(type->modifiers() & (~AbstractType::ConstModifier)); return type->indexed(); } AbstractType::Ptr shortenTypeForViewing(const AbstractType::Ptr& type) { struct ShortenAliasExchanger : public TypeExchanger { AbstractType::Ptr exchange(const AbstractType::Ptr& type) override { if (!type) { return type; } AbstractType::Ptr newType(type->clone()); TypeAliasType::Ptr alias = type.cast(); if (alias) { //If the aliased type has less involved template arguments, prefer it AbstractType::Ptr shortenedTarget = exchange(alias->type()); if (shortenedTarget && shortenedTarget->toString().count(QLatin1Char('<')) < alias->toString().count(QLatin1Char('<')) && reservedIdentifierCount(shortenedTarget->toString()) <= reservedIdentifierCount(alias->toString())) { shortenedTarget->setModifiers(shortenedTarget->modifiers() | alias->modifiers()); return shortenedTarget; } } newType->exchangeTypes(this); return newType; } }; ShortenAliasExchanger exchanger; return exchanger.exchange(type); } ///Returns a type that has all template types replaced with DelayedType's that have their template default parameters stripped away, ///and all scope prefixes removed that are redundant within the given context ///The returned type should not actively be used in the type-system, but rather only for displaying. AbstractType::Ptr stripType(const AbstractType::Ptr& type, DUContext* ctx) { if (!type) { return AbstractType::Ptr(); } struct ShortenTemplateDefaultParameter : public TypeExchanger { DUContext* ctx; ShortenTemplateDefaultParameter(DUContext* _ctx) : ctx(_ctx) { Q_ASSERT(ctx); } AbstractType::Ptr exchange(const AbstractType::Ptr& type) override { if (!type) { return type; } AbstractType::Ptr newType(type->clone()); if (const IdentifiedType * idType = dynamic_cast(type.data())) { Declaration* decl = idType->declaration(ctx->topContext()); if (!decl) { return type; } QualifiedIdentifier newTypeName; #if 0 // from oldcpp if (TemplateDeclaration * tempDecl = dynamic_cast(decl)) { if (decl->context()->type() == DUContext::Class && decl->context()->owner()) { //Strip template default-parameters from the parent class AbstractType::Ptr parentType = stripType(decl->context()->owner()->abstractType(), ctx); if (parentType) { newTypeName = QualifiedIdentifier(parentType->toString(), true); } } if (newTypeName.isEmpty()) { newTypeName = decl->context()->scopeIdentifier(true); } Identifier currentId; if (!idType->qualifiedIdentifier().isEmpty()) { currentId = idType->qualifiedIdentifier().last(); } currentId.clearTemplateIdentifiers(); InstantiationInformation instantiationInfo = tempDecl->instantiatedWith().information(); InstantiationInformation newInformation(instantiationInfo); newInformation.templateParametersList().clear(); for (uint neededParameters = 0; neededParameters < instantiationInfo.templateParametersSize(); ++neededParameters) { newInformation.templateParametersList().append(instantiationInfo.templateParameters()[neededParameters]); AbstractType::Ptr niceParam = stripType(instantiationInfo.templateParameters()[neededParameters].abstractType(), ctx); if (niceParam) { currentId.appendTemplateIdentifier(IndexedTypeIdentifier(niceParam->toString(), true)); // debug() << "testing param" << niceParam->toString(); } if (tempDecl->instantiate(newInformation, ctx->topContext()) == decl) { // debug() << "got full instantiation"; break; } } newTypeName.push(currentId); } else { newTypeName = decl->qualifiedIdentifier(); } #endif newTypeName = decl->qualifiedIdentifier(); //Strip unneded prefixes of the scope QualifiedIdentifier candidate = newTypeName; while (candidate.count() > 1) { candidate = candidate.mid(1); QList decls = ctx->findDeclarations(candidate); if (decls.isEmpty()) { continue; // type aliases might be available for nested sub scopes, hence we must not break early } if (decls[0]->kind() != Declaration::Type || removeConstModifier(decls[0]->indexedType()) != removeConstModifier(type->indexed())) { break; } newTypeName = candidate; } if (newTypeName == decl->qualifiedIdentifier()) { return type; } DelayedType::Ptr ret(new DelayedType); IndexedTypeIdentifier ti(newTypeName); ti.setIsConstant(type->modifiers() & AbstractType::ConstModifier); ret->setIdentifier(ti); return ret.cast(); } newType->exchangeTypes(this); return newType; } }; ShortenTemplateDefaultParameter exchanger(ctx); return exchanger.exchange(type); } } namespace CodegenHelper { AbstractType::Ptr typeForShortenedString(Declaration* decl) { AbstractType::Ptr type = decl->abstractType(); if (decl->isTypeAlias()) { if (type.cast()) { type = type.cast()->type(); } } if (decl->isFunctionDeclaration()) { FunctionType::Ptr funType = decl->type(); if (!funType) { return AbstractType::Ptr(); } type = funType->returnType(); } return type; } QString shortenedTypeString(Declaration* decl, DUContext* ctx, int desiredLength, const QualifiedIdentifier& stripPrefix) { return shortenedTypeString(typeForShortenedString(decl), ctx, desiredLength, stripPrefix); } QString simplifiedTypeString(const AbstractType::Ptr& type, DUContext* visibilityFrom) { return shortenedTypeString(type, visibilityFrom, 100000); } QString shortenedTypeString(const AbstractType::Ptr& type, DUContext* ctx, int desiredLength, const QualifiedIdentifier& stripPrefix) { return shortenedTypeIdentifier(type, ctx, desiredLength, stripPrefix).toString(); } IndexedTypeIdentifier shortenedTypeIdentifier(const AbstractType::Ptr& type_, DUContext* ctx, int desiredLength, const QualifiedIdentifier& stripPrefix) { bool isReference = false; bool isRValue = false; auto type = type_; if (const auto& refType = type.cast()) { isReference = true; type = refType->baseType(); isRValue = refType->isRValue(); } type = shortenTypeForViewing(type); if (ctx) { type = stripType(type, ctx); } if (!type) { return IndexedTypeIdentifier(); } IndexedTypeIdentifier identifier = identifierForType(type, ctx ? ctx->topContext() : 0); - if (type.cast()) { - identifier = type.cast()->identifier(); - } identifier = stripPrefixIdentifiers(identifier, stripPrefix); if (isReference) { identifier.setIsReference(true); } if (isRValue) { identifier.setIsRValue(true); } int removeTemplateParametersFrom = 10; while (identifier.toString().length() > desiredLength * 3 && removeTemplateParametersFrom >= 0) { --removeTemplateParametersFrom; identifier = removeTemplateParameters(identifier, removeTemplateParametersFrom); } return identifier; } +QString makeSignatureString(const KDevelop::Declaration* functionDecl, const Signature& signature, const bool editingDefinition) +{ + if (!functionDecl || !functionDecl->internalContext()) { + return {}; + } + const auto visibilityFrom = functionDecl->internalContext()->parentContext(); + if (!visibilityFrom) { + return {}; + } + + QString ret; + + if (!editingDefinition) { + auto classMember = dynamic_cast(functionDecl); + if (classMember && classMember->isStatic()) { + ret += QLatin1String("static "); + } + } + + // constructors don't have a return type + if (signature.returnType.isValid()) { + ret += CodegenHelper::simplifiedTypeString(signature.returnType.abstractType(), + visibilityFrom); + ret += QLatin1Char(' '); + } + + ret += editingDefinition ? functionDecl->qualifiedIdentifier().toString() : functionDecl->identifier().toString(); + + ret += QLatin1Char('('); + int pos = 0; + + foreach(const ParameterItem &item, signature.parameters) + { + if (pos != 0) { + ret += QLatin1String(", "); + } + + AbstractType::Ptr type = item.first.abstractType(); + + QString arrayAppendix; + ArrayType::Ptr arrayType; + while ((arrayType = type.cast())) { + type = arrayType->elementType(); + //note: we have to prepend since we iterate from outside, i.e. from right to left. + if (arrayType->dimension()) { + arrayAppendix.prepend(QStringLiteral("[%1]").arg(arrayType->dimension())); + } else { + // dimensionless + arrayAppendix.prepend(QLatin1String("[]")); + } + } + ret += CodegenHelper::simplifiedTypeString(type, + visibilityFrom); + + if (!item.second.isEmpty()) { + ret += QLatin1Char(' ') + item.second; + } + ret += arrayAppendix; + if (signature.defaultParams.size() > pos && !signature.defaultParams[pos].isEmpty()) { + ret += QLatin1String(" = ") + signature.defaultParams[pos]; + } + ++pos; + } + ret += QLatin1Char(')'); + if (signature.isConst) { + ret += QLatin1String(" const"); + } + return ret; +} + } diff --git a/languages/clang/codegen/codegenhelper.h b/languages/clang/codegen/codegenhelper.h index 7ffdc6c343..910f912368 100644 --- a/languages/clang/codegen/codegenhelper.h +++ b/languages/clang/codegen/codegenhelper.h @@ -1,44 +1,48 @@ /* Copyright 2007 David Nolden This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef CODEGENHELPER_H #define CODEGENHELPER_H #include #include #include +struct Signature; + namespace CodegenHelper { ///Returns the type that should be used for shortened printing of the same. KDevelop::AbstractType::Ptr typeForShortenedString(KDevelop::Declaration* decl); ///Returns a shortened string version of the type attached to the given declaration. ///@param desiredLength the desired length. No guarantee that the resulting string will be this short. With the default-value, no shortening will happen in most cases. ///@param ctx visibility context to consider. All prefixes of types are shortened to the minimum length while staying visible from here ///@param stripPrefix this prefix will be stripped from qualified identifiers. This is useful to remove parts of the current context. QString shortenedTypeString(KDevelop::Declaration* decl, KDevelop::DUContext* ctx, int desiredLength = 10000, const KDevelop::QualifiedIdentifier& stripPrefix = {}); QString shortenedTypeString(const KDevelop::AbstractType::Ptr& type, KDevelop::DUContext* ctx, int desiredLength = 10000, const KDevelop::QualifiedIdentifier& stripPrefix = {}); KDevelop::IndexedTypeIdentifier shortenedTypeIdentifier(const KDevelop::AbstractType::Ptr& type, KDevelop::DUContext* ctx, int desiredLength = 10000, const KDevelop::QualifiedIdentifier& stripPrefix = {}); ///Returns a simplified string version of the given type: Template default-parameters are stripped away, qualified identifiers are simplified so they are as short as possible, while staying visible from the given context. QString simplifiedTypeString(const KDevelop::AbstractType::Ptr& type, KDevelop::DUContext* visibilityFrom); + +QString makeSignatureString(const KDevelop::Declaration* functionDecl, const Signature& signature, const bool editingDefinition); }; #endif // CODEGENHELPER_H diff --git a/languages/clang/codegen/sourcemanipulation.cpp b/languages/clang/codegen/sourcemanipulation.cpp new file mode 100644 index 0000000000..e5ecd2a3ae --- /dev/null +++ b/languages/clang/codegen/sourcemanipulation.cpp @@ -0,0 +1,287 @@ +/* + * This file is part of KDevelop + * + * Copyright 2009 David Nolden + * Copyright 2015 Sergey Kalinichev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "sourcemanipulation.h" + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "codegenhelper.h" +#include "adaptsignatureaction.h" +#include "util/clangdebug.h" + +using namespace KDevelop; + +namespace +{ +QualifiedIdentifier stripPrefixes(const DUContextPointer& ctx, const QualifiedIdentifier& id) +{ + if (!ctx) { + return id; + } + + auto imports = ctx->fullyApplyAliases({}, ctx->topContext()); + if (imports.contains(id)) { + return {}; /// The id is a namespace that is imported into the current context + } + + auto basicDecls = ctx->findDeclarations(id, CursorInRevision::invalid(), {}, nullptr, + (DUContext::SearchFlags)(DUContext::NoSelfLookUp | DUContext::NoFiltering)); + + if (basicDecls.isEmpty()) { + return id; + } + + auto newId = id.mid(1); + auto result = id; + while (!newId.isEmpty()) { + auto foundDecls + = ctx->findDeclarations(newId, CursorInRevision::invalid(), {}, nullptr, + (DUContext::SearchFlags)(DUContext::NoSelfLookUp | DUContext::NoFiltering)); + + if (foundDecls == basicDecls) { + result = newId; // must continue to find the shortest possible identifier + // esp. for cases where nested namespaces are used (e.g. using namespace a::b::c;) + newId = newId.mid(1); + } + } + + return result; +} + +// Re-indents the code so the leftmost line starts at zero +QString zeroIndentation(const QString& str, int fromLine = 0) +{ + QStringList lines = str.split(QLatin1Char('\n')); + QStringList ret; + + if (fromLine < lines.size()) { + ret = lines.mid(0, fromLine); + lines = lines.mid(fromLine); + } + + QRegExp nonWhiteSpace(QStringLiteral("\\S")); + int minLineStart = 10000; + foreach (const auto& line, lines) { + int lineStart = line.indexOf(nonWhiteSpace); + if (lineStart < minLineStart) { + minLineStart = lineStart; + } + } + + foreach (const auto& line, lines) { + ret << line.mid(minLineStart); + } + + return ret.join(QStringLiteral("\n")); +} +} + +DocumentChangeSet SourceCodeInsertion::changes() +{ + return m_changeSet; +} + +void SourceCodeInsertion::setSubScope(const QualifiedIdentifier& scope) +{ + m_scope = scope; + + if (!m_context) { + return; + } + + QStringList needNamespace = m_scope.toStringList(); + + bool foundChild = true; + while (!needNamespace.isEmpty() && foundChild) { + foundChild = false; + + foreach (DUContext* child, m_context->childContexts()) { + clangDebug() << "checking child" << child->localScopeIdentifier().toString() << "against" + << needNamespace.first(); + if (child->localScopeIdentifier().toString() == needNamespace.first() && child->type() == DUContext::Namespace) { + clangDebug() << "taking"; + m_context = child; + foundChild = true; + needNamespace.pop_front(); + break; + } + } + } + + m_scope = stripPrefixes(m_context, QualifiedIdentifier(needNamespace.join(QStringLiteral("::")))); +} + +QString SourceCodeInsertion::applySubScope(const QString& decl) const +{ + if (m_scope.isEmpty()) { + return decl; + } + + QString scopeType = QStringLiteral("namespace"); + QString scopeClose; + + if (m_context && m_context->type() == DUContext::Class) { + scopeType = QStringLiteral("struct"); + scopeClose = QStringLiteral(";"); + } + + QString ret; + foreach (const QString& scope, m_scope.toStringList()) { + ret += scopeType + QStringLiteral(" ") + scope + QStringLiteral(" {\n"); + } + + ret += decl; + ret += QStringLiteral("}") + scopeClose + QStringLiteral("\n").repeated(m_scope.count()); + + return ret; +} + +SourceCodeInsertion::SourceCodeInsertion(TopDUContext* topContext) + : m_context(topContext) + , m_topContext(topContext) + , m_codeRepresentation(createCodeRepresentation(m_topContext->url())) +{ +} + +SourceCodeInsertion::~SourceCodeInsertion() +{ +} + +KTextEditor::Cursor SourceCodeInsertion::end() const +{ + auto ret = m_context->rangeInCurrentRevision().end(); + if (m_codeRepresentation && m_codeRepresentation->lines() && dynamic_cast(m_context.data())) { + ret.setLine(m_codeRepresentation->lines() - 1); + ret.setColumn(m_codeRepresentation->line(ret.line()).size()); + } + return ret; +} + +KTextEditor::Range SourceCodeInsertion::insertionRange(int line) +{ + if (line == 0 || !m_codeRepresentation) { + return KTextEditor::Range(line, 0, line, 0); + } + + KTextEditor::Range range(line - 1, m_codeRepresentation->line(line - 1).size(), line - 1, + m_codeRepresentation->line(line - 1).size()); + // If the context finishes on that line, then this will need adjusting + if (!m_context->rangeInCurrentRevision().contains(range)) { + range.start() = m_context->rangeInCurrentRevision().end(); + if (range.start().column() > 0) { + range.start() = range.start() - KTextEditor::Cursor(0, 1); + } + range.end() = range.start(); + } + + return range; +} + +bool SourceCodeInsertion::insertFunctionDeclaration(KDevelop::Declaration* declaration, const Identifier& id, const QString& body) +{ + if (!m_context) { + return false; + } + + Signature signature; + const auto localDeclarations = declaration->internalContext()->localDeclarations(); + signature.parameters.reserve(localDeclarations.count()); + std::transform(localDeclarations.begin(), localDeclarations.end(), + std::back_inserter(signature.parameters), + [] (Declaration* argument) -> ParameterItem + { return {IndexedType(argument->indexedType()), argument->identifier().toString()}; }); + + auto funcType = declaration->type(); + auto returnType = funcType->returnType(); + if (auto classFunDecl = dynamic_cast(declaration)) { + if (classFunDecl->isConstructor() || classFunDecl->isDestructor()) { + returnType = nullptr; + } + } + signature.returnType = returnType->indexed(); + signature.isConst = funcType->modifiers() & AbstractType::ConstModifier; + + QString decl = CodegenHelper::makeSignatureString(declaration, signature, true); + decl.replace(declaration->qualifiedIdentifier().toString(), id.toString()); + + if (body.isEmpty()) { + decl += QStringLiteral(";"); + } else { + if (!body.startsWith(QLatin1Char(' ')) && !body.startsWith(QLatin1Char('\n'))) { + decl += QStringLiteral(" "); + } + decl += zeroIndentation(body); + } + + int line = findInsertionPoint(); + + decl = QStringLiteral("\n\n") + applySubScope(decl); + QMimeDatabase db; + QMimeType mime = db.mimeTypeForFile(declaration->url().str()); + auto i = ICore::self()->sourceFormatterController()->formatterForMimeType(mime); + if (i) { + decl = i->formatSource(decl, declaration->url().toUrl(), mime); + } + + return m_changeSet.addChange(DocumentChange(m_context->url(), insertionRange(line), QString(), decl)); +} + +int SourceCodeInsertion::findInsertionPoint() const +{ + int line = end().line(); + + foreach (auto decl, m_context->localDeclarations()) { + if (m_context->type() == DUContext::Class) { + continue; + } + + if (!dynamic_cast(decl)) { + continue; + } + + line = decl->range().end.line + 1; + if (decl->internalContext()) { + line = decl->internalContext()->range().end.line + 1; + } + } + + clangDebug() << line << m_context->scopeIdentifier(true) << m_context->rangeInCurrentRevision() + << m_context->url().toUrl() << m_context->parentContext(); + clangDebug() << "count of declarations:" << m_context->topContext()->localDeclarations().size(); + + return line; +} diff --git a/languages/clang/codegen/sourcemanipulation.h b/languages/clang/codegen/sourcemanipulation.h new file mode 100644 index 0000000000..143e311689 --- /dev/null +++ b/languages/clang/codegen/sourcemanipulation.h @@ -0,0 +1,73 @@ +/* + * This file is part of KDevelop + * + * Copyright 2009 David Nolden + * Copyright 2015 Sergey Kalinichev + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef CLANG_SOURCEMANIPULATION_H +#define CLANG_SOURCEMANIPULATION_H + +#include +#include +#include + +class SourceCodeInsertion : public QSharedData +{ +public: + explicit SourceCodeInsertion(KDevelop::TopDUContext* topContext); + ~SourceCodeInsertion(); + + /// Set optional sub-scope into which the code should be inserted, under 'context' + void setSubScope(const KDevelop::QualifiedIdentifier& scope); + + /// @param body function-body, including parens + bool insertFunctionDeclaration(KDevelop::Declaration* decl, const KDevelop::Identifier& id, const QString& body = QString()); + + KDevelop::DocumentChangeSet changes(); + +private: + + /// Returns the exact position where the item should be inserted so it is in the given line. + /// The inserted item has to start with a newline, and does not need to end with a newline. + KTextEditor::Range insertionRange(int line); + + /// Returns a line for inserting the given declaration + int findInsertionPoint() const; + + // Should apply m_scope to the given declaration string + QString applySubScope(const QString& decl) const; + + QString indentation() const; + QString applyIndentation(const QString& decl) const; + + KTextEditor::Cursor end() const; + +private: + KDevelop::DocumentChangeSet m_changeSet; + KDevelop::DUContextPointer m_context; + KDevelop::QualifiedIdentifier m_scope; + KDevelop::TopDUContextPointer m_topContext; + // Represents the whole code of the manipulated top-context for reading. + // Must be checked for zero before using. It is zero if the file could not be read. + const KDevelop::CodeRepresentation::Ptr m_codeRepresentation; +}; + +#endif // CLANG_SOURCEMANIPULATION_H diff --git a/languages/clang/duchain/builder.cpp b/languages/clang/duchain/builder.cpp index 8026900f64..ee7bd73c3e 100644 --- a/languages/clang/duchain/builder.cpp +++ b/languages/clang/duchain/builder.cpp @@ -1,1457 +1,1463 @@ /* * This file is part of KDevelop * * Copyright 2013 Olivier de Gaalon * Copyright 2015 Milian Wolff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "builder.h" #include "util/clangdebug.h" #include "templatehelpers.h" #include "cursorkindtraits.h" #include "clangducontext.h" #include "macrodefinition.h" #include "types/classspecializationtype.h" #include "util/clangdebug.h" #include "util/clangutils.h" #include "util/clangtypes.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /// Turn on for debugging the declaration building #define IF_DEBUG(x) namespace { // TODO: this is ugly, can we find a better alternative? bool jsonTestRun() { static bool runningTest = qEnvironmentVariableIsSet("KDEV_CLANG_JSON_TEST_RUN"); return runningTest; } //BEGIN helpers /** * Find the cursor that cursor @p cursor references * * First tries to get the referenced cursor via clang_getCursorReferenced, * and if that fails, tries to get them via clang_getOverloadedDecl * (which returns the referenced cursor for CXCursor_OverloadedDeclRef, for example) * * @return Valid cursor on success, else null cursor */ CXCursor referencedCursor(CXCursor cursor) { auto referenced = clang_getCursorReferenced(cursor); if (!clang_equalCursors(cursor, referenced)) { return referenced; } // get the first result for now referenced = clang_getOverloadedDecl(cursor, 0); if (!clang_Cursor_isNull(referenced)) { return referenced; } return clang_getNullCursor(); } Identifier makeId(CXCursor cursor) { return Identifier(ClangString(clang_getCursorSpelling(cursor)).toIndexed()); } QByteArray makeComment(CXComment comment) { if (Q_UNLIKELY(jsonTestRun())) { auto kind = clang_Comment_getKind(comment); if (kind == CXComment_Text) return ClangString(clang_TextComment_getText(comment)).toByteArray(); QByteArray text; int numChildren = clang_Comment_getNumChildren(comment); for (int i = 0; i < numChildren; ++i) text += makeComment(clang_Comment_getChild(comment, i)); return text; } return ClangString(clang_FullComment_getAsHTML(comment)).toByteArray(); } AbstractType* createDelayedType(CXType type) { auto t = new DelayedType; QString typeName = ClangString(clang_getTypeSpelling(type)).toString(); typeName.remove(QStringLiteral("const ")); typeName.remove(QStringLiteral("volatile ")); t->setIdentifier(IndexedTypeIdentifier(typeName)); return t; } void contextImportDecl(DUContext* context, const DeclarationPointer& decl) { auto top = context->topContext(); if (auto import = decl->logicalInternalContext(top)) { context->addImportedParentContext(import); context->topContext()->updateImportsCache(); } } //END helpers CXChildVisitResult visitCursor(CXCursor cursor, CXCursor parent, CXClientData data); //BEGIN IdType template struct IdType; template struct IdType::type> { typedef StructureType Type; }; template struct IdType::type> { typedef TypeAliasType Type; }; template struct IdType::type> { typedef TypeAliasType Type; }; template struct IdType::type> { typedef EnumerationType Type; }; template struct IdType::type> { typedef EnumeratorType Type; }; //END IdType //BEGIN DeclType template struct DeclType; template struct DeclType::type> { typedef Declaration Type; }; template struct DeclType::type> { typedef MacroDefinition Type; }; template struct DeclType::type> { typedef ForwardDeclaration Type; }; template struct DeclType::type> { typedef ClassDeclaration Type; }; template struct DeclType::type> { typedef ClassFunctionDeclaration Type; }; template struct DeclType::type> { typedef FunctionDeclaration Type; }; template struct DeclType::type> { typedef FunctionDefinition Type; }; template struct DeclType::type> { typedef NamespaceAliasDeclaration Type; }; template struct DeclType::type> { typedef ClassMemberDeclaration Type; }; //END DeclType //BEGIN CurrentContext struct CurrentContext { CurrentContext(DUContext* context) : context(context) { DUChainReadLocker lock; previousChildContexts = context->childContexts(); previousChildDeclarations = context->localDeclarations(); } ~CurrentContext() { DUChainWriteLocker lock; qDeleteAll(previousChildContexts); qDeleteAll(previousChildDeclarations); if (resortChildContexts) { context->resortChildContexts(); } if (resortLocalDeclarations) { context->resortLocalDeclarations(); } } DUContext* context; // when updatig, this contains child contexts of the current parent context QVector previousChildContexts; // when updatig, this contains child declarations of the current parent context QVector previousChildDeclarations; bool resortChildContexts = false; bool resortLocalDeclarations = false; }; //END CurrentContext //BEGIN Visitor struct Visitor { explicit Visitor(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update); AbstractType *makeType(CXType type, CXCursor parent); AbstractType::Ptr makeAbsType(CXType type, CXCursor parent) { return AbstractType::Ptr(makeType(type, parent)); } //BEGIN dispatch* template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template = dummy> CXChildVisitResult dispatchCursor(CXCursor cursor, CXCursor parent); template AbstractType *dispatchType(CXType type, CXCursor cursor) { IF_DEBUG(clangDebug() << "TK:" << type.kind;) auto kdevType = createType(type, cursor); if (kdevType) { setTypeModifiers(type, kdevType); } return kdevType; } //BEGIN dispatch* //BEGIN build* template CXChildVisitResult buildDeclaration(CXCursor cursor); CXChildVisitResult buildUse(CXCursor cursor); CXChildVisitResult buildMacroExpansion(CXCursor cursor); CXChildVisitResult buildCompoundStatement(CXCursor cursor); CXChildVisitResult buildCXXBaseSpecifier(CXCursor cursor); CXChildVisitResult buildParmDecl(CXCursor cursor); //END build* //BEGIN create* template DeclType* createDeclarationCommon(CXCursor cursor, const Identifier& id) { auto range = ClangHelpers::cursorSpellingNameRange(cursor, id); if (id.isEmpty()) { // This is either an anonymous function parameter e.g.: void f(int); // Or anonymous struct/class/union e.g.: struct {} anonymous; // Set empty range for it range.end = range.start; } // check if cursor is inside a macro expansion auto clangRange = clang_Cursor_getSpellingNameRange(cursor, 0, 0); unsigned int expansionLocOffset; const auto spellingLocation = clang_getRangeStart(clangRange); clang_getExpansionLocation(spellingLocation, nullptr, nullptr, nullptr, &expansionLocOffset); if (m_macroExpansionLocations.contains(expansionLocOffset)) { unsigned int spellingLocOffset; clang_getSpellingLocation(spellingLocation, nullptr, nullptr, nullptr, &spellingLocOffset); // Set empty ranges for declarations inside macro expansion if (spellingLocOffset == expansionLocOffset) { range.end = range.start; } } if (m_update) { const IndexedIdentifier indexedId(id); DUChainWriteLocker lock; auto it = m_parentContext->previousChildDeclarations.begin(); while (it != m_parentContext->previousChildDeclarations.end()) { auto decl = dynamic_cast(*it); if (decl && decl->indexedIdentifier() == indexedId) { decl->setRange(range); m_parentContext->resortLocalDeclarations = true; setDeclData(cursor, decl); m_cursorToDeclarationCache[cursor] = decl; m_parentContext->previousChildDeclarations.erase(it); return decl; } ++it; } } auto decl = new DeclType(range, nullptr); decl->setIdentifier(id); m_cursorToDeclarationCache[cursor] = decl; setDeclData(cursor, decl); { DUChainWriteLocker lock; decl->setContext(m_parentContext->context); } return decl; } template Declaration* createDeclaration(CXCursor cursor, const Identifier& id, DUContext *context) { auto decl = createDeclarationCommon(cursor, id); auto type = createType(cursor); DUChainWriteLocker lock; if (context) decl->setInternalContext(context); setDeclType(decl, type); setDeclInCtxtData(cursor, decl); return decl; } template DUContext* createContext(CXCursor cursor, const QualifiedIdentifier& scopeId = {}) { // wtf: why is the DUContext API requesting a QID when it needs a plain Id?! // see: testNamespace auto range = ClangRange(clang_getCursorExtent(cursor)).toRangeInRevision(); DUChainWriteLocker lock; if (m_update) { const IndexedQualifiedIdentifier indexedScopeId(scopeId); auto it = m_parentContext->previousChildContexts.begin(); while (it != m_parentContext->previousChildContexts.end()) { auto ctx = *it; if (ctx->type() == Type && ctx->indexedLocalScopeIdentifier() == indexedScopeId) { ctx->setRange(range); m_parentContext->resortChildContexts = true; m_parentContext->previousChildContexts.erase(it); return ctx; } ++it; } } //TODO: (..type, id..) constructor for DUContext? auto context = new ClangNormalDUContext(range, m_parentContext->context); context->setType(Type); context->setLocalScopeIdentifier(scopeId); if (Type == DUContext::Other || Type == DUContext::Function) context->setInSymbolTable(false); if (CK == CXCursor_CXXMethod) { CXCursor semParent = clang_getCursorSemanticParent(cursor); // only import the semantic parent if it differs from the lexical parent if (!clang_Cursor_isNull(semParent) && !clang_equalCursors(semParent, clang_getCursorLexicalParent(cursor))) { auto semParentDecl = findDeclaration(semParent); if (semParentDecl) { contextImportDecl(context, semParentDecl); } } } return context; } template = dummy> AbstractType *createType(CXType, CXCursor) { // TODO: would be nice to instantiate a ConstantIntegralType here and set a value if possible // but unfortunately libclang doesn't offer API to that // also see http://marc.info/?l=cfe-commits&m=131609142917881&w=2 return new IntegralType(CursorKindTraits::integralType(TK)); } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto ptr = new PointerType; ptr->setBaseType(makeAbsType(clang_getPointeeType(type), parent)); return ptr; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto arr = new ArrayType; arr->setDimension((TK == CXType_IncompleteArray || TK == CXType_VariableArray || TK == CXType_DependentSizedArray) ? 0 : clang_getArraySize(type)); arr->setElementType(makeAbsType(clang_getArrayElementType(type), parent)); return arr; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto ref = new ReferenceType; ref->setIsRValue(type.kind == CXType_RValueReference); ref->setBaseType(makeAbsType(clang_getPointeeType(type), parent)); return ref; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto func = new FunctionType; func->setReturnType(makeAbsType(clang_getResultType(type), parent)); const int numArgs = clang_getNumArgTypes(type); for (int i = 0; i < numArgs; ++i) { func->addArgument(makeAbsType(clang_getArgType(type, i), parent)); } if (clang_isFunctionTypeVariadic(type)) { auto type = new DelayedType; static const auto id = IndexedTypeIdentifier(QStringLiteral("...")); type->setIdentifier(id); type->setKind(DelayedType::Unresolved); func->addArgument(AbstractType::Ptr(type)); } return func; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { DeclarationPointer decl = findDeclaration(clang_getTypeDeclaration(type)); DUChainReadLocker lock; if (!decl) { // probably a forward-declared type decl = ClangHelpers::findForwardDeclaration(type, m_parentContext->context, parent); } if (clang_Type_getNumTemplateArguments(type) != -1) { return createClassTemplateSpecializationType(type, decl); } auto t = new StructureType; if (decl) { t->setDeclaration(decl.data()); } else { // fallback, at least give the spelling to the user t->setDeclarationId(DeclarationId(IndexedQualifiedIdentifier(QualifiedIdentifier(ClangString(clang_getTypeSpelling(type)).toString())))); } return t; } template = dummy> AbstractType *createType(CXType type, CXCursor) { auto t = new EnumerationType; setIdTypeDecl(clang_getTypeDeclaration(type), t); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto t = new TypeAliasType; CXCursor location = clang_getTypeDeclaration(type); t->setType(makeAbsType(clang_getTypedefDeclUnderlyingType(location), parent)); setIdTypeDecl(location, t); return t; } template = dummy> AbstractType *createType(CXType, CXCursor /*parent*/) { auto t = new DelayedType; static const IndexedTypeIdentifier id(QLatin1String(CursorKindTraits::delayedTypeName(TK))); t->setIdentifier(id); return t; } template = dummy> AbstractType *createType(CXType type, CXCursor /*parent*/) { return createDelayedType(type); } template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto numTA = clang_Type_getNumTemplateArguments(type); - if (numTA != -1) { - // This is a class template specialization. + // TODO: We should really expose more types to libclang! + if (numTA != -1 && ClangString(clang_getTypeSpelling(type)).toString().contains(QLatin1Char('<'))) { return createClassTemplateSpecializationType(type); } // Maybe it's the ElaboratedType. E.g.: "struct Type foo();" or "NS::Type foo();" or "void foo(enum Enum e);" e.t.c. auto oldType = type; type = clang_getCanonicalType(type); bool isElaboratedType = type.kind != CXType_FunctionProto && type.kind != CXType_FunctionNoProto && type.kind != CXType_Unexposed && type.kind != CXType_Invalid && type.kind != CXType_Record; return !isElaboratedType ? createDelayedType(oldType) : makeType(type, parent); } template = dummy> typename IdType::Type *createType(CXCursor) { return new typename IdType::Type; } template = dummy> EnumeratorType *createType(CXCursor cursor) { auto type = new EnumeratorType; type->setValue(clang_getEnumConstantDeclUnsignedValue(cursor)); return type; } template = dummy> TypeAliasType *createType(CXCursor cursor) { auto type = new TypeAliasType; type->setType(makeAbsType(clang_getTypedefDeclUnderlyingType(cursor), cursor)); return type; } template = dummy> AbstractType* createType(CXCursor cursor) { auto clangType = clang_getCursorType(cursor); #if CINDEX_VERSION_MINOR < 31 if (clangType.kind == CXType_Unexposed) { // Clang sometimes can return CXType_Unexposed for CXType_FunctionProto kind. E.g. if it's AttributedType. return dispatchType(clangType, cursor); } #endif return makeType(clangType, cursor); } template = dummy> AbstractType *createType(CXCursor) { auto t = new DelayedType; static const IndexedTypeIdentifier id(QStringLiteral("Label")); t->setIdentifier(id); return t; } template = dummy> AbstractType *createType(CXCursor cursor) { auto clangType = clang_getCursorType(cursor); return makeType(clangType, cursor); } #if CINDEX_VERSION_MINOR >= 31 template = dummy> AbstractType *createType(CXType type, CXCursor parent) { auto deducedType = clang_getCanonicalType(type); bool isDeduced = deducedType.kind != CXType_Invalid && deducedType.kind != CXType_Auto; return !isDeduced ? createDelayedType(type) : makeType(deducedType, parent); } #endif /// @param declaration an optional declaration that will be associated with created type AbstractType* createClassTemplateSpecializationType(CXType type, const DeclarationPointer declaration = {}) { auto numTA = clang_Type_getNumTemplateArguments(type); Q_ASSERT(numTA != -1); auto typeDecl = clang_getTypeDeclaration(type); if (!declaration && typeDecl.kind == CXCursor_NoDeclFound) { // clang_getTypeDeclaration doesn't handle all types, fall back to delayed type... return createDelayedType(type); } QStringList typesStr; QString tStr = ClangString(clang_getTypeSpelling(type)).toString(); ParamIterator iter(QStringLiteral("<>"), tStr); while (iter) { typesStr.append(*iter); ++iter; } auto cst = new ClassSpecializationType; for (int i = 0; i < numTA; i++) { auto argumentType = clang_Type_getTemplateArgumentAsType(type, i); AbstractType::Ptr currentType; if (argumentType.kind == CXType_Invalid) { if(i >= typesStr.size()){ currentType = createDelayedType(argumentType); } else { auto t = new DelayedType; t->setIdentifier(IndexedTypeIdentifier(typesStr[i])); currentType = t; } } else { currentType = makeType(argumentType, typeDecl); } if (currentType) { cst->addParameter(currentType->indexed()); } } auto decl = declaration ? declaration : findDeclaration(typeDecl); DUChainReadLocker lock; if (decl) { cst->setDeclaration(decl.data()); } else { // fallback, at least give the spelling to the user Identifier id(tStr); id.clearTemplateIdentifiers(); cst->setDeclarationId(DeclarationId(IndexedQualifiedIdentifier(QualifiedIdentifier(id)))); } return cst; } //END create* //BEGIN setDeclData template void setDeclData(CXCursor cursor, Declaration *decl, bool setComment = true) const; template void setDeclData(CXCursor cursor, MacroDefinition* decl) const; template void setDeclData(CXCursor cursor, ClassMemberDeclaration *decl) const; template = dummy> void setDeclData(CXCursor cursor, ClassDeclaration* decl) const; template = dummy> void setDeclData(CXCursor cursor, ClassDeclaration* decl) const; template void setDeclData(CXCursor cursor, AbstractFunctionDeclaration* decl) const; template void setDeclData(CXCursor cursor, ClassFunctionDeclaration* decl) const; template void setDeclData(CXCursor cursor, FunctionDeclaration *decl, bool setComment = true) const; template void setDeclData(CXCursor cursor, FunctionDefinition *decl) const; template void setDeclData(CXCursor cursor, NamespaceAliasDeclaration *decl) const; //END setDeclData //BEGIN setDeclInCtxtData template void setDeclInCtxtData(CXCursor, Declaration*) { //No-op } template void setDeclInCtxtData(CXCursor cursor, ClassFunctionDeclaration *decl) { // HACK to retrieve function-constness // This looks like a bug in Clang -- In theory setTypeModifiers should take care of setting the const modifier // however, clang_isConstQualifiedType() for TK == CXType_FunctionProto always returns false // TODO: Debug further auto type = decl->abstractType(); if (type) { if (clang_CXXMethod_isConst(cursor)) { type->setModifiers(type->modifiers() | AbstractType::ConstModifier); decl->setAbstractType(type); } } } template void setDeclInCtxtData(CXCursor cursor, FunctionDefinition *def) { setDeclInCtxtData(cursor, static_cast(def)); const CXCursor canon = clang_getCanonicalCursor(cursor); if (auto decl = findDeclaration(canon)) { def->setDeclaration(decl.data()); } } //END setDeclInCtxtData //BEGIN setDeclType template void setDeclType(Declaration *decl, typename IdType::Type *type) { setDeclType(decl, static_cast(type)); setDeclType(decl, static_cast(type)); } template void setDeclType(Declaration *decl, IdentifiedType *type) { type->setDeclaration(decl); } template void setDeclType(Declaration *decl, AbstractType *type) { decl->setAbstractType(AbstractType::Ptr(type)); } //END setDeclType template void setTypeModifiers(CXType type, AbstractType* kdevType) const; const CXFile m_file; const IncludeFileContexts &m_includes; DeclarationPointer findDeclaration(CXCursor cursor) const; void setIdTypeDecl(CXCursor typeCursor, IdentifiedType* idType) const; std::unordered_map> m_uses; /// At these location offsets (cf. @ref clang_getExpansionLocation) we encountered macro expansions QSet m_macroExpansionLocations; mutable QHash m_cursorToDeclarationCache; CurrentContext *m_parentContext; const bool m_update; }; //BEGIN setTypeModifiers template void Visitor::setTypeModifiers(CXType type, AbstractType* kdevType) const { quint64 modifiers = 0; if (clang_isConstQualifiedType(type)) { modifiers |= AbstractType::ConstModifier; } if (clang_isVolatileQualifiedType(type)) { modifiers |= AbstractType::VolatileModifier; } if (TK == CXType_Short || TK == CXType_UShort) { modifiers |= AbstractType::ShortModifier; } if (TK == CXType_Long || TK == CXType_LongDouble || TK == CXType_ULong) { modifiers |= AbstractType::LongModifier; } if (TK == CXType_LongLong || TK == CXType_ULongLong) { modifiers |= AbstractType::LongLongModifier; } if (TK == CXType_SChar) { modifiers |= AbstractType::SignedModifier; } if (TK == CXType_UChar || TK == CXType_UInt || TK == CXType_UShort || TK == CXType_UInt128 || TK == CXType_ULong || TK == CXType_ULongLong) { modifiers |= AbstractType::UnsignedModifier; } kdevType->setModifiers(modifiers); } //END setTypeModifiers //BEGIN dispatchCursor template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { const bool decision = CursorKindTraits::isClass(clang_getCursorKind(parent)); return decision ? dispatchCursor(cursor, parent) : dispatchCursor(cursor, parent); } template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { IF_DEBUG(clangDebug() << "IsInClass:" << IsInClass << "- isDefinition:" << IsDefinition;) const bool isDefinition = clang_isCursorDefinition(cursor); return isDefinition ? dispatchCursor(cursor, parent) : dispatchCursor(cursor, parent); } template> CXChildVisitResult Visitor::dispatchCursor(CXCursor cursor, CXCursor parent) { IF_DEBUG(clangDebug() << "IsInClass:" << IsInClass << "- isDefinition:" << IsDefinition;) // We may end up visiting the same cursor twice in some cases // see discussion on https://git.reviewboard.kde.org/r/119526/ // TODO: Investigate why this is happening in libclang if ((CursorKindTraits::isClass(CK) || CK == CXCursor_EnumDecl) && clang_getCursorKind(parent) == CXCursor_VarDecl) { return CXChildVisit_Continue; } constexpr bool isClassMember = IsInClass == Decision::True; constexpr bool isDefinition = IsDefinition == Decision::True; constexpr bool hasContext = CursorKindTraits::isFunction(CK) || (IsDefinition == Decision::True); return buildDeclaration::Type, hasContext>(cursor); } //END dispatchCursor //BEGIN setDeclData template void Visitor::setDeclData(CXCursor cursor, Declaration *decl, bool setComment) const { if (setComment) decl->setComment(makeComment(clang_Cursor_getParsedComment(cursor))); if (CursorKindTraits::isAliasType(CK)) { decl->setIsTypeAlias(true); } if (CK == CXCursor_Namespace) decl->setKind(Declaration::Namespace); if (CK == CXCursor_EnumDecl || CK == CXCursor_EnumConstantDecl || CursorKindTraits::isClass(CK) || CursorKindTraits::isAliasType(CK)) decl->setKind(Declaration::Type); int isAlwaysDeprecated; clang_getCursorPlatformAvailability(cursor, &isAlwaysDeprecated, nullptr, nullptr, nullptr, nullptr, 0); decl->setDeprecated(isAlwaysDeprecated); } template void Visitor::setDeclData(CXCursor cursor, MacroDefinition* decl) const { setDeclData(cursor, static_cast(decl)); if (m_update) { decl->clearParameters(); } auto unit = clang_Cursor_getTranslationUnit(cursor); auto range = clang_getCursorExtent(cursor); // TODO: Quite lacking API in libclang here. // No way to find out if this macro is function-like or not // cf. http://clang.llvm.org/doxygen/classclang_1_1MacroInfo.html // And no way to get the actual definition text range // Should be quite easy to expose that in libclang, though // Let' still get some basic support for this and parse on our own, it's not that difficult const QString contents = QString::fromUtf8(ClangUtils::getRawContents(unit, range)); const int firstOpeningParen = contents.indexOf(QLatin1Char('(')); const int firstWhitespace = contents.indexOf(QLatin1Char(' ')); const bool isFunctionLike = (firstOpeningParen != -1) && (firstOpeningParen < firstWhitespace); decl->setFunctionLike(isFunctionLike); // now extract the actual definition text int start = -1; if (isFunctionLike) { const int closingParen = findClose(contents, firstOpeningParen); if (closingParen != -1) { start = closingParen + 2; // + ')' + ' ' // extract macro function parameters const QString parameters = contents.mid(firstOpeningParen, closingParen - firstOpeningParen + 1); ParamIterator paramIt(QStringLiteral("():"), parameters, 0); while (paramIt) { decl->addParameter(IndexedString(*paramIt)); ++paramIt; } } } else { start = firstWhitespace + 1; // + ' ' } if (start == -1) { // unlikely: invalid macro definition, insert the complete #define statement decl->setDefinition(IndexedString(QLatin1String("#define ") + contents)); } else if (start < contents.size()) { decl->setDefinition(IndexedString(contents.mid(start))); } // else: macro has no body => leave the definition text empty } template void Visitor::setDeclData(CXCursor cursor, ClassMemberDeclaration *decl) const { setDeclData(cursor, static_cast(decl)); //A CXCursor_VarDecl in a class is static (otherwise it'd be a CXCursor_FieldDecl) if (CK == CXCursor_VarDecl) decl->setStatic(true); decl->setAccessPolicy(CursorKindTraits::kdevAccessPolicy(clang_getCXXAccessSpecifier(cursor))); #if CINDEX_VERSION_MINOR >= 31 decl->setMutable(clang_CXXField_isMutable(cursor)); #endif #if CINDEX_VERSION_MINOR >= 30 if (!jsonTestRun()) { auto offset = clang_Cursor_getOffsetOfField(cursor); if (offset >= 0) { // don't add this info to the json tests, it invalidates the comment structure auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignedTo = clang_Type_getAlignOf(type); + const auto byteOffset = offset / 8; + const auto bitOffset = offset % 8; + const QString byteOffsetStr = i18np("1 Byte", "%1 Bytes", byteOffset); + const QString bitOffsetStr = bitOffset ? i18np("1 Bit", "%1 Bits", bitOffset) : QString(); + const QString offsetStr = bitOffset ? i18nc("%1: bytes, %2: bits", "%1, %2", byteOffsetStr, bitOffsetStr) : byteOffsetStr; + decl->setComment(decl->comment() - + i18n("
offset in parent: %1 Bit
" + + i18n("
offset in parent: %1
" "size: %2 Bytes
" - "aligned to: %3 Bytes", offset, sizeOf, alignedTo).toUtf8()); + "aligned to: %3 Bytes", offsetStr, sizeOf, alignedTo).toUtf8()); } } #endif } template> void Visitor::setDeclData(CXCursor cursor, ClassDeclaration* decl) const { CXCursorKind kind = clang_getTemplateCursorKind(cursor); switch (kind) { case CXCursor_UnionDecl: setDeclData(cursor, decl); break; case CXCursor_StructDecl: setDeclData(cursor, decl); break; case CXCursor_ClassDecl: setDeclData(cursor, decl); break; default: Q_ASSERT(false); break; } } template> void Visitor::setDeclData(CXCursor cursor, ClassDeclaration* decl) const { if (m_update) { decl->clearBaseClasses(); } setDeclData(cursor, static_cast(decl)); if (CK == CXCursor_UnionDecl) decl->setClassType(ClassDeclarationData::Union); if (CK == CXCursor_StructDecl) decl->setClassType(ClassDeclarationData::Struct); if (clang_isCursorDefinition(cursor)) { decl->setDeclarationIsDefinition(true); } if (!jsonTestRun()) { // don't add this info to the json tests, it invalidates the comment structure auto type = clang_getCursorType(cursor); auto sizeOf = clang_Type_getSizeOf(type); auto alignOf = clang_Type_getAlignOf(type); if (sizeOf >= 0 && alignOf >= 0) { decl->setComment(decl->comment() + i18n("
size: %1 Bytes
" "aligned to: %2 Bytes", sizeOf, alignOf).toUtf8()); } } } template void Visitor::setDeclData(CXCursor cursor, AbstractFunctionDeclaration* decl) const { if (m_update) { decl->clearDefaultParameters(); } // No setDeclData(...) here: AbstractFunctionDeclaration is an interface // TODO: Can we get the default arguments directly from Clang? // also see http://clang-developers.42468.n3.nabble.com/Finding-default-value-for-function-argument-with-clang-c-API-td4036919.html const QVector defaultArgs = ClangUtils::getDefaultArguments(cursor, ClangUtils::MinimumSize); foreach (const QString& defaultArg, defaultArgs) { decl->addDefaultParameter(IndexedString(defaultArg)); } } template void Visitor::setDeclData(CXCursor cursor, ClassFunctionDeclaration* decl) const { setDeclData(cursor, static_cast(decl)); setDeclData(cursor, static_cast(decl)); decl->setAbstract(clang_CXXMethod_isPureVirtual(cursor)); decl->setStatic(clang_CXXMethod_isStatic(cursor)); decl->setVirtual(clang_CXXMethod_isVirtual(cursor)); const auto qtAttribute = ClangUtils::specialQtAttributes(cursor); decl->setIsSignal(qtAttribute == ClangUtils::QtSignalAttribute); decl->setIsSlot(qtAttribute == ClangUtils::QtSlotAttribute); } template void Visitor::setDeclData(CXCursor cursor, FunctionDeclaration *decl, bool setComment) const { setDeclData(cursor, static_cast(decl)); setDeclData(cursor, static_cast(decl), setComment); } template void Visitor::setDeclData(CXCursor cursor, FunctionDefinition *decl) const { bool setComment = clang_equalCursors(clang_getCanonicalCursor(cursor), cursor); setDeclData(cursor, static_cast(decl), setComment); } template void Visitor::setDeclData(CXCursor cursor, NamespaceAliasDeclaration *decl) const { setDeclData(cursor, static_cast(decl)); clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor /*parent*/, CXClientData data) -> CXChildVisitResult { Q_ASSERT(clang_getCursorKind(cursor) == CXCursor_NamespaceRef); const auto id = QualifiedIdentifier(ClangString(clang_getCursorSpelling(cursor)).toString()); reinterpret_cast(data)->setImportIdentifier(id); return CXChildVisit_Break; }, decl); } //END setDeclData //BEGIN build* template CXChildVisitResult Visitor::buildDeclaration(CXCursor cursor) { auto id = makeId(cursor); if (CK == CXCursor_UnexposedDecl && id.isEmpty()) { // skip unexposed declarations that have no identifier set // this is useful to skip e.g. friend declarations return CXChildVisit_Recurse; } IF_DEBUG(clangDebug() << "id:" << id << "- CK:" << CK << "- DeclType:" << typeid(DeclType).name() << "- hasContext:" << hasContext;) // Code path for class declarations that may be defined "out-of-line", e.g. // "SomeNameSpace::SomeClass {};" QScopedPointer helperContext; if (CursorKindTraits::isClass(CK) || CursorKindTraits::isFunction(CK)) { const auto lexicalParent = clang_getCursorLexicalParent(cursor); const auto semanticParent = clang_getCursorSemanticParent(cursor); const bool isOutOfLine = !clang_equalCursors(lexicalParent, semanticParent); if (isOutOfLine) { const QString scope = ClangUtils::getScope(cursor); auto context = createContext(cursor, QualifiedIdentifier(scope)); helperContext.reset(new CurrentContext(context)); } } // if helperContext is null, this is a no-op PushValue pushCurrent(m_parentContext, helperContext.isNull() ? m_parentContext : helperContext.data()); if (hasContext) { auto context = createContext(cursor, QualifiedIdentifier(id)); createDeclaration(cursor, id, context); CurrentContext newParent(context); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, &visitCursor, this); return CXChildVisit_Continue; } createDeclaration(cursor, id, nullptr); return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildParmDecl(CXCursor cursor) { return buildDeclaration::Type, false>(cursor); } CXChildVisitResult Visitor::buildUse(CXCursor cursor) { m_uses[m_parentContext->context].push_back(cursor); return cursor.kind == CXCursor_DeclRefExpr || cursor.kind == CXCursor_MemberRefExpr ? CXChildVisit_Recurse : CXChildVisit_Continue; } CXChildVisitResult Visitor::buildMacroExpansion(CXCursor cursor) { buildUse(cursor); // cache that we encountered a macro expansion at this location unsigned int offset; clang_getSpellingLocation(clang_getCursorLocation(cursor), nullptr, nullptr, nullptr, &offset); m_macroExpansionLocations << offset; return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildCompoundStatement(CXCursor cursor) { if (m_parentContext->context->type() == DUContext::Function) { auto context = createContext(cursor); CurrentContext newParent(context); PushValue pushCurrent(m_parentContext, &newParent); clang_visitChildren(cursor, &visitCursor, this); return CXChildVisit_Continue; } return CXChildVisit_Recurse; } CXChildVisitResult Visitor::buildCXXBaseSpecifier(CXCursor cursor) { auto currentContext = m_parentContext->context; bool virtualInherited = clang_isVirtualBase(cursor); Declaration::AccessPolicy access = CursorKindTraits::kdevAccessPolicy(clang_getCXXAccessSpecifier(cursor)); auto classDeclCursor = clang_getCursorReferenced(cursor); auto decl = findDeclaration(classDeclCursor); if (!decl) { // this happens for templates with template-dependent base classes e.g. - dunno whether we can/should do more here clangDebug() << "failed to find declaration for base specifier:" << clang_getCursorDisplayName(cursor); return CXChildVisit_Recurse; } DUChainWriteLocker lock; contextImportDecl(currentContext, decl); auto classDecl = dynamic_cast(currentContext->owner()); Q_ASSERT(classDecl); classDecl->addBaseClass({decl->indexedType(), access, virtualInherited}); return CXChildVisit_Recurse; } //END build* DeclarationPointer Visitor::findDeclaration(CXCursor cursor) const { const auto it = m_cursorToDeclarationCache.constFind(cursor); if (it != m_cursorToDeclarationCache.constEnd()) { return *it; } // fallback, and cache result auto decl = ClangHelpers::findDeclaration(cursor, m_includes); m_cursorToDeclarationCache.insert(cursor, decl); return decl; } void Visitor::setIdTypeDecl(CXCursor typeCursor, IdentifiedType* idType) const { DeclarationPointer decl = findDeclaration(typeCursor); DUChainReadLocker lock; if (decl) { idType->setDeclaration(decl.data()); } } AbstractType *Visitor::makeType(CXType type, CXCursor parent) { #define UseKind(TypeKind) case TypeKind: return dispatchType(type, parent) switch (type.kind) { UseKind(CXType_Void); UseKind(CXType_Bool); UseKind(CXType_Short); UseKind(CXType_UShort); UseKind(CXType_Int); UseKind(CXType_UInt); UseKind(CXType_Long); UseKind(CXType_ULong); UseKind(CXType_LongLong); UseKind(CXType_ULongLong); UseKind(CXType_Float); UseKind(CXType_LongDouble); UseKind(CXType_Double); UseKind(CXType_Char_U); UseKind(CXType_Char_S); UseKind(CXType_UChar); UseKind(CXType_SChar); UseKind(CXType_Char16); UseKind(CXType_Char32); UseKind(CXType_Pointer); UseKind(CXType_MemberPointer); UseKind(CXType_ObjCObjectPointer); UseKind(CXType_ConstantArray); UseKind(CXType_VariableArray); UseKind(CXType_IncompleteArray); UseKind(CXType_DependentSizedArray); UseKind(CXType_LValueReference); UseKind(CXType_RValueReference); UseKind(CXType_FunctionNoProto); UseKind(CXType_FunctionProto); UseKind(CXType_Record); UseKind(CXType_Enum); UseKind(CXType_Typedef); UseKind(CXType_Int128); UseKind(CXType_UInt128); UseKind(CXType_Vector); UseKind(CXType_Unexposed); UseKind(CXType_WChar); UseKind(CXType_ObjCInterface); UseKind(CXType_ObjCId); UseKind(CXType_ObjCClass); UseKind(CXType_ObjCSel); UseKind(CXType_NullPtr); #if CINDEX_VERSION_MINOR >= 31 UseKind(CXType_Auto); #endif case CXType_Invalid: return nullptr; default: qCWarning(KDEV_CLANG) << "Unhandled type:" << type.kind << clang_getTypeSpelling(type); return nullptr; } #undef UseKind } RangeInRevision rangeInRevisionForUse(CXCursor cursor, CXCursorKind referencedCursorKind, CXSourceRange useRange, const QSet& macroExpansionLocations) { auto range = ClangRange(useRange).toRangeInRevision(); //TODO: Fix in clang, happens for operator<<, operator<, probably more if (clang_Range_isNull(useRange)) { useRange = clang_getCursorExtent(cursor); range = ClangRange(useRange).toRangeInRevision(); } if (referencedCursorKind == CXCursor_ConversionFunction) { range.end = range.start; range.start.column--; } // For uses inside macro expansions, create an empty use range at the spelling location // the empty range is required in order to not "overlap" the macro expansion range // and to allow proper navigation for the macro expansion // also see JSON test 'macros.cpp' if (clang_getCursorKind(cursor) != CXCursor_MacroExpansion) { unsigned int expansionLocOffset; const auto spellingLocation = clang_getRangeStart(useRange); clang_getExpansionLocation(spellingLocation, nullptr, nullptr, nullptr, &expansionLocOffset); if (macroExpansionLocations.contains(expansionLocOffset)) { unsigned int spellingLocOffset; clang_getSpellingLocation(spellingLocation, nullptr, nullptr, nullptr, &spellingLocOffset); if (spellingLocOffset == expansionLocOffset) { range.end = range.start; } } } else { // Workaround for wrong use range returned by clang for macro expansions const auto contents = ClangUtils::getRawContents(clang_Cursor_getTranslationUnit(cursor), useRange); const int firstOpeningParen = contents.indexOf('('); if (firstOpeningParen != -1) { range.end.column = range.start.column + firstOpeningParen; range.end.line = range.start.line; } } return range; } Visitor::Visitor(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update) : m_file(file) , m_includes(includes) , m_parentContext(nullptr) , m_update(update) { CXCursor tuCursor = clang_getTranslationUnitCursor(tu); CurrentContext parent(includes[file]); m_parentContext = &parent; clang_visitChildren(tuCursor, &visitCursor, this); TopDUContext *top = m_parentContext->context->topContext(); if (m_update) { DUChainWriteLocker lock; top->deleteUsesRecursively(); } for (const auto &contextUses : m_uses) { for (const auto &cursor : contextUses.second) { auto referenced = referencedCursor(cursor); if (clang_Cursor_isNull(referenced)) { continue; } // first, try the canonical referenced cursor // this is important to get the correct function declaration e.g. auto canonicalReferenced = clang_getCanonicalCursor(referenced); auto used = findDeclaration(canonicalReferenced); if (!used) { // if the above failed, try the non-canonicalized version as a fallback // this is required for friend declarations that occur before // the real declaration. there, the canonical cursor points to // the friend declaration which is not what we are looking for used = findDeclaration(referenced); } if (!used) { // as a last resort, try to resolve the forward declaration DUChainReadLocker lock; DeclarationPointer decl = ClangHelpers::findForwardDeclaration(clang_getCursorType(referenced), contextUses.first, referenced); used = decl; if (!used) { continue; } } #if CINDEX_VERSION_MINOR >= 29 if (clang_Cursor_getNumTemplateArguments(referenced) >= 0) { // Ideally, we don't need this, but for function templates clang_getCanonicalCursor returns a function definition // See also the testUsesCreatedForDeclarations test DUChainReadLocker lock; used = DUChainUtils::declarationForDefinition(used.data()); } #endif const auto useRange = clang_getCursorReferenceNameRange(cursor, 0, 0); const auto range = rangeInRevisionForUse(cursor, referenced.kind, useRange, m_macroExpansionLocations); DUChainWriteLocker lock; auto usedIndex = top->indexForUsedDeclaration(used.data()); contextUses.first->createUse(usedIndex, range); } } } //END Visitor CXChildVisitResult visitCursor(CXCursor cursor, CXCursor parent, CXClientData data) { Visitor *visitor = static_cast(data); const auto kind = clang_getCursorKind(cursor); auto location = clang_getCursorLocation(cursor); CXFile file; clang_getFileLocation(location, &file, nullptr, nullptr, nullptr); // don't skip MemberRefExpr with invalid location, see also: // http://lists.cs.uiuc.edu/pipermail/cfe-dev/2015-May/043114.html if (!ClangUtils::isFileEqual(file, visitor->m_file) && (file || kind != CXCursor_MemberRefExpr)) { return CXChildVisit_Continue; } #define UseCursorKind(CursorKind, ...) case CursorKind: return visitor->dispatchCursor(__VA_ARGS__); switch (kind) { UseCursorKind(CXCursor_UnexposedDecl, cursor, parent); UseCursorKind(CXCursor_StructDecl, cursor, parent); UseCursorKind(CXCursor_UnionDecl, cursor, parent); UseCursorKind(CXCursor_ClassDecl, cursor, parent); UseCursorKind(CXCursor_EnumDecl, cursor, parent); UseCursorKind(CXCursor_FieldDecl, cursor, parent); UseCursorKind(CXCursor_EnumConstantDecl, cursor, parent); UseCursorKind(CXCursor_FunctionDecl, cursor, parent); UseCursorKind(CXCursor_VarDecl, cursor, parent); UseCursorKind(CXCursor_TypeAliasDecl, cursor, parent); UseCursorKind(CXCursor_TypedefDecl, cursor, parent); UseCursorKind(CXCursor_CXXMethod, cursor, parent); UseCursorKind(CXCursor_Namespace, cursor, parent); UseCursorKind(CXCursor_NamespaceAlias, cursor, parent); UseCursorKind(CXCursor_Constructor, cursor, parent); UseCursorKind(CXCursor_Destructor, cursor, parent); UseCursorKind(CXCursor_ConversionFunction, cursor, parent); UseCursorKind(CXCursor_TemplateTypeParameter, cursor, parent); UseCursorKind(CXCursor_NonTypeTemplateParameter, cursor, parent); UseCursorKind(CXCursor_TemplateTemplateParameter, cursor, parent); UseCursorKind(CXCursor_FunctionTemplate, cursor, parent); UseCursorKind(CXCursor_ClassTemplate, cursor, parent); UseCursorKind(CXCursor_ClassTemplatePartialSpecialization, cursor, parent); UseCursorKind(CXCursor_ObjCInterfaceDecl, cursor, parent); UseCursorKind(CXCursor_ObjCCategoryDecl, cursor, parent); UseCursorKind(CXCursor_ObjCProtocolDecl, cursor, parent); UseCursorKind(CXCursor_ObjCPropertyDecl, cursor, parent); UseCursorKind(CXCursor_ObjCIvarDecl, cursor, parent); UseCursorKind(CXCursor_ObjCInstanceMethodDecl, cursor, parent); UseCursorKind(CXCursor_ObjCClassMethodDecl, cursor, parent); UseCursorKind(CXCursor_ObjCImplementationDecl, cursor, parent); UseCursorKind(CXCursor_ObjCCategoryImplDecl, cursor, parent); UseCursorKind(CXCursor_MacroDefinition, cursor, parent); UseCursorKind(CXCursor_LabelStmt, cursor, parent); case CXCursor_TypeRef: case CXCursor_TemplateRef: case CXCursor_NamespaceRef: case CXCursor_MemberRef: case CXCursor_LabelRef: case CXCursor_OverloadedDeclRef: case CXCursor_VariableRef: case CXCursor_DeclRefExpr: case CXCursor_MemberRefExpr: case CXCursor_ObjCClassRef: return visitor->buildUse(cursor); case CXCursor_MacroExpansion: return visitor->buildMacroExpansion(cursor); case CXCursor_CompoundStmt: return visitor->buildCompoundStatement(cursor); case CXCursor_CXXBaseSpecifier: return visitor->buildCXXBaseSpecifier(cursor); case CXCursor_ParmDecl: return visitor->buildParmDecl(cursor); default: return CXChildVisit_Recurse; } } } namespace Builder { void visit(CXTranslationUnit tu, CXFile file, const IncludeFileContexts& includes, const bool update) { Visitor visitor(tu, file, includes, update); } } diff --git a/languages/clang/duchain/clangindex.cpp b/languages/clang/duchain/clangindex.cpp index 02502191dd..e8e4ba21b7 100644 --- a/languages/clang/duchain/clangindex.cpp +++ b/languages/clang/duchain/clangindex.cpp @@ -1,114 +1,119 @@ /* * This file is part of KDevelop * * Copyright 2013 Olivier de Gaalon * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "clangindex.h" #include "clangpch.h" #include "clangparsingenvironment.h" #include "documentfinderhelpers.h" #include #include #include #include #include using namespace KDevelop; ClangIndex::ClangIndex() // NOTE: We don't exclude PCH declarations. That way we could retrieve imports manually, as clang_getInclusions returns nothing on reparse with CXTranslationUnit_PrecompiledPreamble flag. : m_index(clang_createIndex(0 /*Exclude PCH Decls*/, qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_DIAGS") /*Display diags*/)) { + // demote the priority of the clang parse threads to reduce potential UI lockups + // but the code completion threads still retain their normal priority to return + // the results as quickly as possible + clang_CXIndex_setGlobalOptions(m_index, clang_CXIndex_getGlobalOptions(m_index) + | CXGlobalOpt_ThreadBackgroundPriorityForIndexing); } CXIndex ClangIndex::index() const { return m_index; } QSharedPointer ClangIndex::pch(const ClangParsingEnvironment& environment) { const auto& pchInclude = environment.pchInclude(); if (!pchInclude.isValid()) { return {}; } UrlParseLock pchLock(IndexedString(pchInclude.pathOrUrl())); static const QString pchExt = QStringLiteral(".pch"); if (QFile::exists(pchInclude.toLocalFile() + pchExt)) { QReadLocker lock(&m_pchLock); auto pch = m_pch.constFind(pchInclude); if (pch != m_pch.constEnd()) { return pch.value(); } } auto pch = QSharedPointer::create(environment, this); QWriteLocker lock(&m_pchLock); m_pch.insert(pchInclude, pch); return pch; } ClangIndex::~ClangIndex() { clang_disposeIndex(m_index); } IndexedString ClangIndex::translationUnitForUrl(const IndexedString& url) { { // try explicit pin data first QMutexLocker lock(&m_mappingMutex); auto tu = m_tuForUrl.find(url); if (tu != m_tuForUrl.end()) { if (!QFile::exists(tu.value().str())) { // TU doesn't exist, unpin m_tuForUrl.erase(tu); return url; } return tu.value(); } } // otherwise, fallback to a simple buddy search for headers if (ClangHelpers::isHeader(url.str())) { foreach(const QUrl& buddy, DocumentFinderHelpers::getPotentialBuddies(url.toUrl(), false)) { const QString buddyPath = buddy.toLocalFile(); if (QFile::exists(buddyPath)) { return IndexedString(buddyPath); } } } return url; } void ClangIndex::pinTranslationUnitForUrl(const IndexedString& tu, const IndexedString& url) { QMutexLocker lock(&m_mappingMutex); m_tuForUrl.insert(url, tu); } void ClangIndex::unpinTranslationUnitForUrl(const IndexedString& url) { QMutexLocker lock(&m_mappingMutex); m_tuForUrl.remove(url); } diff --git a/languages/clang/duchain/documentfinderhelpers.cpp b/languages/clang/duchain/documentfinderhelpers.cpp index 0873bea43d..5138be4032 100644 --- a/languages/clang/duchain/documentfinderhelpers.cpp +++ b/languages/clang/duchain/documentfinderhelpers.cpp @@ -1,268 +1,287 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #include "documentfinderhelpers.h" #include "duchain/clanghelpers.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { enum FileType { Unknown, ///< Doesn't belong to C++ Header, ///< Is a header file Source ///< Is a C(++) file }; class PotentialBuddyCollector : public DUChainUtils::DUChainItemFilter { public: enum BuddyMode { Header, Source }; explicit PotentialBuddyCollector(BuddyMode mode) : mode(mode) {} bool accept(Declaration* decl) override { if (decl->range().isEmpty()) return false; if (mode == Header && decl->isFunctionDeclaration()) { // Search for definitions of our declarations FunctionDefinition* def = FunctionDefinition::definition(decl); if (def) { vote(def->url().toUrl()); } return true; } else if (mode == Source && decl->isFunctionDeclaration()) { FunctionDefinition* fdef = dynamic_cast(decl); if (fdef) { Declaration* fdecl = fdef->declaration(); if (fdecl) { vote(fdecl->url().toUrl()); } } return true; } else { return false; } } bool accept(DUContext* ctx) override { if (ctx->type() == DUContext::Class || ctx->type() == DUContext::Namespace || ctx->type() == DUContext::Global || ctx->type() == DUContext::Other || ctx->type() == DUContext::Helper ) { return true; } else { return false; } } QUrl bestBuddy() const { QUrl ret; int bestCount = 0; for (auto it = m_buddyFiles.begin(); it != m_buddyFiles.end(); ++it) { if(it.value() > bestCount) { bestCount = it.value(); ret = it.key(); } } return ret; } private: BuddyMode mode; QHash m_buddyFiles; void vote(const QUrl& url) { m_buddyFiles[url]++; } }; /** * Tries to find a buddy file to the given file by looking at the DUChain. * * The project might keep source files separate from headers. To cover * this situation, we examine DUChain for the most probable buddy file. * This of course only works if we have parsed the buddy file, but it is * better than nothing. * * @param url url of the source/header file to find a buddy for * @param type type of the file @p url * * @returns QUrl of the most probable buddy file, or an empty url **/ QUrl duchainBuddyFile(const QUrl& url, FileType type) { DUChainReadLocker lock; auto ctx = DUChainUtils::standardContextForUrl(url); if (ctx) { PotentialBuddyCollector collector( type == Header ? PotentialBuddyCollector::Header : PotentialBuddyCollector::Source ); DUChainUtils::collectItems(ctx, collector); return collector.bestBuddy(); } return QUrl(); } /** * Generates the base path (without extension) and the file type * for the specified url. * * @returns pair of base path and file type which has been found for @p url. */ QPair basePathAndTypeForUrl(const QUrl &url) { QString path = url.toLocalFile(); int idxSlash = path.lastIndexOf(QLatin1Char('/')); int idxDot = path.lastIndexOf(QLatin1Char('.')); FileType fileType = Unknown; QString basePath; if (idxSlash >= 0 && idxDot >= 0 && idxDot > idxSlash) { basePath = path.left(idxDot); if (idxDot + 1 < path.length()) { QString extension = path.mid(idxDot + 1); if (ClangHelpers::isHeader(extension)) { fileType = Header; } else if (ClangHelpers::isSource(extension)) { fileType = Source; } } } else { basePath = path; } return qMakePair(basePath, fileType); } } namespace DocumentFinderHelpers { QStringList mimeTypesList() { static const QStringList mimeTypes = { QStringLiteral("text/x-chdr"), QStringLiteral("text/x-c++hdr"), QStringLiteral("text/x-csrc"), QStringLiteral("text/x-c++src"), QStringLiteral("text/x-objcsrc") }; return mimeTypes; } bool areBuddies(const QUrl &url1, const QUrl& url2) { auto type1 = basePathAndTypeForUrl(url1); auto type2 = basePathAndTypeForUrl(url2); QUrl headerPath; QUrl sourcePath; // Check that one file is a header, the other one is source if (type1.second == Header && type2.second == Source) { headerPath = url1; sourcePath = url2; } else if (type1.second == Source && type2.second == Header) { headerPath = url2; sourcePath = url1; } else { // Some other file constellation return false; } // The simplest directory layout is with header + source in one directory. // So check that first. if (type1.first == type2.first) { return true; } // Also check if the DUChain thinks this is likely if (duchainBuddyFile(sourcePath, Source) == headerPath) { return true; } return false; } bool buddyOrder(const QUrl &url1, const QUrl& url2) { auto type1 = basePathAndTypeForUrl(url1); auto type2 = basePathAndTypeForUrl(url2); // Precondition is that the two URLs are buddies, so don't check it return(type1.second == Header && type2.second == Source); } QVector< QUrl > getPotentialBuddies(const QUrl &url, bool checkDUChain) { auto type = basePathAndTypeForUrl(url); // Don't do anything for types we don't know if (type.second == Unknown) { return {}; } // Depending on the buddy's file type we either generate source extensions (for headers) // or header extensions (for sources) const auto& extensions = ( type.second == Header ? ClangHelpers::sourceExtensions() : ClangHelpers::headerExtensions() ); QVector< QUrl > buddies; for(const QString& extension : extensions) { if (!extension.contains(QLatin1Char('.'))) { buddies.append(QUrl::fromLocalFile(type.first + QLatin1Char('.') + extension)); } else { buddies.append(QUrl::fromLocalFile(type.first + extension)); } } if (checkDUChain) { // Also ask DUChain for a guess QUrl bestBuddy = duchainBuddyFile(url, type.second); if (!buddies.contains(bestBuddy)) { buddies.append(bestBuddy); } } return buddies; } +QString sourceForHeader(const QString& headerPath) +{ + if (!ClangHelpers::isHeader(headerPath)) { + return {}; + } + + QString targetUrl; + auto buddies = DocumentFinderHelpers::getPotentialBuddies(QUrl::fromLocalFile(headerPath)); + for (const auto& buddy : buddies) { + const auto local = buddy.toLocalFile(); + if (QFileInfo::exists(local)) { + targetUrl = local; + break; + } + } + + return targetUrl; +} + } diff --git a/languages/clang/duchain/documentfinderhelpers.h b/languages/clang/duchain/documentfinderhelpers.h index 52c26516c9..b8fa1b53d3 100644 --- a/languages/clang/duchain/documentfinderhelpers.h +++ b/languages/clang/duchain/documentfinderhelpers.h @@ -1,52 +1,59 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * */ #ifndef DOCUMENTFINDERHELPERS_H #define DOCUMENTFINDERHELPERS_H #include #include #include #include "clangprivateexport.h" /// Helper class for handling @see IBuddyDocumentFinder features. namespace DocumentFinderHelpers { /// @return All supported mime types KDEVCLANGPRIVATE_EXPORT QStringList mimeTypesList(); /** * Considers the URLs as buddy documents if the base path (without extension) * is the same, and one extension starts with h/H and the other one with c/C. * For example, foo.hpp and foo.C are buddies. */ KDEVCLANGPRIVATE_EXPORT bool areBuddies(const QUrl &url1, const QUrl& url2); /// @see KDevelop::IBuddyDocumentFinder KDEVCLANGPRIVATE_EXPORT bool buddyOrder(const QUrl &url1, const QUrl& url2); /// @see KDevelop::IBuddyDocumentFinder KDEVCLANGPRIVATE_EXPORT QVector< QUrl > getPotentialBuddies(const QUrl &url, bool checkDUChain = true); + +/** + * Returns path to the source file for given @p headerPath + * + * If no source file exists or @p headerPath is not a header an empty sting is returned + */ +KDEVCLANGPRIVATE_EXPORT QString sourceForHeader(const QString& headerPath); }; #endif // DOCUMENTFINDERHELPERS_H diff --git a/languages/clang/duchain/parsesession.cpp b/languages/clang/duchain/parsesession.cpp index a621e3d26f..6ce08d5ca6 100644 --- a/languages/clang/duchain/parsesession.cpp +++ b/languages/clang/duchain/parsesession.cpp @@ -1,446 +1,447 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff Copyright 2013 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "parsesession.h" #include #include "clangproblem.h" #include "clangdiagnosticevaluator.h" #include "todoextractor.h" #include "clanghelpers.h" #include "clangindex.h" #include "clangparsingenvironment.h" #include "util/clangdebug.h" #include "util/clangtypes.h" #include "util/clangutils.h" #include #include #include #include #include #include #include #include using namespace KDevelop; namespace { QVector extraArgs() { const auto extraArgsString = QString::fromLatin1(qgetenv("KDEV_CLANG_EXTRA_ARGUMENTS")); const auto extraArgs = KShell::splitArgs(extraArgsString); // transform to list of QByteArrays QVector result; result.reserve(extraArgs.size()); foreach (const QString& arg, extraArgs) { result << arg.toLatin1(); } clangDebug() << "Passing extra arguments to clang:" << result; return result; } QVector argsForSession(const QString& path, ParseSessionData::Options options, const ParserSettings& parserSettings) { QMimeDatabase db; if(db.mimeTypeForFile(path).name() == QStringLiteral("text/x-objcsrc")) { return {QByteArrayLiteral("-xobjective-c++")}; } if (parserSettings.parserOptions.isEmpty()) { // The parserOptions can be empty for some unit tests that use ParseSession directly auto defaultArguments = ClangSettingsManager::self()->parserSettings(nullptr).toClangAPI(); Q_ASSERT(!defaultArguments.isEmpty()); defaultArguments.append(QByteArrayLiteral("-nostdinc")); defaultArguments.append(QByteArrayLiteral("-nostdinc++")); defaultArguments.append(QByteArrayLiteral("-xc++")); return defaultArguments; } auto result = parserSettings.toClangAPI(); result.append(QByteArrayLiteral("-nostdinc")); result.append(QByteArrayLiteral("-nostdinc++")); if (options & ParseSessionData::PrecompiledHeader) { result.append(parserSettings.isCpp() ? QByteArrayLiteral("-xc++-header") : QByteArrayLiteral("-xc-header")); return result; } result.append(parserSettings.isCpp() ? QByteArrayLiteral("-xc++") : QByteArrayLiteral("-xc")); return result; } void addIncludes(QVector* args, QVector* otherArgs, const Path::List& includes, const char* cliSwitch) { foreach (const Path& url, includes) { if (url.isEmpty()) { continue; } QFileInfo info(url.toLocalFile()); QByteArray path = url.toLocalFile().toUtf8(); if (info.isFile()) { path.prepend("-include"); } else { path.prepend(cliSwitch); } otherArgs->append(path); args->append(path.constData()); } } QVector toClangApi(const QVector& unsavedFiles) { QVector unsaved; unsaved.reserve(unsavedFiles.size()); std::transform(unsavedFiles.begin(), unsavedFiles.end(), std::back_inserter(unsaved), [] (const UnsavedFile& file) { return file.toClangApi(); }); return unsaved; } bool needGccCompatibility(const ClangParsingEnvironment& environment) { const auto& defines = environment.defines(); // TODO: potentially do the same for the intel compiler? return defines.contains(QStringLiteral("__GNUC__")) && !environment.defines().contains(QStringLiteral("__clang__")); } bool hasQtIncludes(const Path::List& includePaths) { return std::find_if(includePaths.begin(), includePaths.end(), [] (const Path& path) { return path.lastPathSegment() == QLatin1String("QtCore"); }) != includePaths.end(); } } ParseSessionData::ParseSessionData(const QVector& unsavedFiles, ClangIndex* index, const ClangParsingEnvironment& environment, Options options) : m_file(nullptr) , m_unit(nullptr) { unsigned int flags = CXTranslationUnit_CXXChainedPCH | CXTranslationUnit_DetailedPreprocessingRecord; if (options.testFlag(SkipFunctionBodies)) { flags |= CXTranslationUnit_SkipFunctionBodies; } if (options.testFlag(PrecompiledHeader)) { flags |= CXTranslationUnit_ForSerialization; } else { flags |= CXTranslationUnit_CacheCompletionResults | CXTranslationUnit_PrecompiledPreamble; if (environment.quality() == ClangParsingEnvironment::Unknown) { flags |= CXTranslationUnit_Incomplete; } } const auto tuUrl = environment.translationUnitUrl(); Q_ASSERT(!tuUrl.isEmpty()); const auto arguments = argsForSession(tuUrl.str(), options, environment.parserSettings()); QVector clangArguments; const auto& includes = environment.includes(); const auto& pchInclude = environment.pchInclude(); // uses QByteArray as smart-pointer for const char* ownership QVector smartArgs; smartArgs.reserve(includes.system.size() + includes.project.size() + pchInclude.isValid() + arguments.size() + 1); clangArguments.reserve(smartArgs.size()); std::transform(arguments.constBegin(), arguments.constEnd(), std::back_inserter(clangArguments), [] (const QByteArray &argument) { return argument.constData(); }); // NOTE: the PCH include must come before all other includes! if (pchInclude.isValid()) { clangArguments << "-include"; QByteArray pchFile = pchInclude.toLocalFile().toUtf8(); smartArgs << pchFile; clangArguments << pchFile.constData(); } if (needGccCompatibility(environment)) { const auto compatFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevclangsupport/gcc_compat.h")).toUtf8(); if (!compatFile.isEmpty()) { smartArgs << compatFile; clangArguments << "-include" << compatFile.constData(); } } if (hasQtIncludes(includes.system)) { const auto wrappedQtHeaders = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevclangsupport/wrappedQtHeaders"), QStandardPaths::LocateDirectory).toUtf8(); if (!wrappedQtHeaders.isEmpty()) { smartArgs << wrappedQtHeaders; clangArguments << "-isystem" << wrappedQtHeaders.constData(); const auto qtCore = wrappedQtHeaders + "/QtCore"; smartArgs << qtCore; clangArguments << "-isystem" << qtCore.constData(); } } addIncludes(&clangArguments, &smartArgs, includes.system, "-isystem"); addIncludes(&clangArguments, &smartArgs, includes.project, "-I"); - m_definesFile.open(); - QTextStream definesStream(&m_definesFile); - Q_ASSERT(m_definesFile.isWritable()); - const auto& defines = environment.defines(); - for (auto it = defines.begin(); it != defines.end(); ++it) { - if (it.key() == QLatin1String("__VERSION__") || it.key() == QLatin1String("__clang_minor__") - || it.key() == QLatin1String("__clang_patchlevel__") || it.key() == QLatin1String("__clang_version__")) - { - // don't emit tons of "macro redefined" errors for these macros - continue; - } - definesStream << QStringLiteral("#define ") << it.key() << ' ' << it.value() << '\n'; - } - definesStream.flush(); - smartArgs << m_definesFile.fileName().toUtf8(); + smartArgs << writeDefinesFile(environment.defines()); clangArguments << "-imacros" << smartArgs.last().constData(); // append extra args from environment variable static const auto extraArgs = ::extraArgs(); foreach (const QByteArray& arg, extraArgs) { clangArguments << arg.constData(); } QVector unsaved; //For PrecompiledHeader, we don't want unsaved contents (and contents.isEmpty()) if (!options.testFlag(PrecompiledHeader)) { unsaved = toClangApi(unsavedFiles); } // debugging: print hypothetical clang invocation including args (for easy c&p for local testing) if (qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_ARGS")) { QTextStream out(stdout); out << "Invocation: clang"; foreach (const auto& arg, clangArguments) { out << " " << arg; } out << " " << tuUrl.byteArray().constData() << "\n"; } const CXErrorCode code = clang_parseTranslationUnit2( index->index(), tuUrl.byteArray().constData(), clangArguments.constData(), clangArguments.size(), unsaved.data(), unsaved.size(), flags, &m_unit ); if (code != CXError_Success) { qWarning() << "clang_parseTranslationUnit2 return with error code" << code; if (!qEnvironmentVariableIsSet("KDEV_CLANG_DISPLAY_DIAGS")) { qWarning() << " (start KDevelop with `KDEV_CLANG_DISPLAY_DIAGS=1 kdevelop` to see more diagnostics)"; } } if (m_unit) { setUnit(m_unit); m_environment = environment; if (options.testFlag(PrecompiledHeader)) { clang_saveTranslationUnit(m_unit, (tuUrl.byteArray() + ".pch").constData(), CXSaveTranslationUnit_None); } } else { qWarning() << "Failed to parse translation unit:" << tuUrl; } } ParseSessionData::~ParseSessionData() { clang_disposeTranslationUnit(m_unit); } +QByteArray ParseSessionData::writeDefinesFile(const QMap& defines) +{ + m_definesFile.open(); + Q_ASSERT(m_definesFile.isWritable()); + + QTextStream definesStream(&m_definesFile); + // don't show warnings about redefined macros + definesStream << "#pragma clang system_header\n"; + for (auto it = defines.begin(); it != defines.end(); ++it) { + definesStream << QStringLiteral("#define ") << it.key() << ' ' << it.value() << '\n'; + } + + return m_definesFile.fileName().toUtf8(); +} + void ParseSessionData::setUnit(CXTranslationUnit unit) { m_unit = unit; if (m_unit) { const ClangString unitFile(clang_getTranslationUnitSpelling(unit)); m_file = clang_getFile(m_unit, unitFile.c_str()); } else { m_file = nullptr; } } ClangParsingEnvironment ParseSessionData::environment() const { return m_environment; } ParseSession::ParseSession(const ParseSessionData::Ptr& data) : d(data) { if (d) { ENSURE_CHAIN_NOT_LOCKED d->m_mutex.lock(); } } ParseSession::~ParseSession() { if (d) { d->m_mutex.unlock(); } } void ParseSession::setData(const ParseSessionData::Ptr& data) { if (data == d) { return; } if (d) { d->m_mutex.unlock(); } d = data; if (d) { ENSURE_CHAIN_NOT_LOCKED d->m_mutex.lock(); } } ParseSessionData::Ptr ParseSession::data() const { return d; } IndexedString ParseSession::languageString() { static const IndexedString lang("Clang"); return lang; } QList ParseSession::problemsForFile(CXFile file) const { if (!d) { return {}; } QList problems; // extra clang diagnostics const uint numDiagnostics = clang_getNumDiagnostics(d->m_unit); problems.reserve(numDiagnostics); for (uint i = 0; i < numDiagnostics; ++i) { auto diagnostic = clang_getDiagnostic(d->m_unit, i); CXSourceLocation location = clang_getDiagnosticLocation(diagnostic); CXFile diagnosticFile; clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); // missing-include problems are so severe in clang that we always propagate // them to this document, to ensure that the user will see the error. if (diagnosticFile != file && ClangDiagnosticEvaluator::diagnosticType(diagnostic) != ClangDiagnosticEvaluator::IncludeFileNotFoundProblem) { continue; } ProblemPointer problem(ClangDiagnosticEvaluator::createProblem(diagnostic, d->m_unit)); problems << problem; clang_disposeDiagnostic(diagnostic); } // other problem sources TodoExtractor extractor(unit(), file); problems << extractor.problems(); #if CINDEX_VERSION_MINOR > 30 // note that the below warning is triggered on every reparse when there is a precompiled preamble // see also TestDUChain::testReparseIncludeGuard const QString path = QDir(ClangString(clang_getFileName(file)).toString()).canonicalPath(); const IndexedString indexedPath(path); if (ClangHelpers::isHeader(path) && !clang_isFileMultipleIncludeGuarded(unit(), file) && !clang_Location_isInSystemHeader(clang_getLocationForOffset(d->m_unit, file, 0))) { ProblemPointer problem(new Problem); problem->setSeverity(IProblem::Warning); problem->setDescription(i18n("Header is not guarded against multiple inclusions")); problem->setExplanation(i18n("The given header is not guarded against multiple inclusions, " "either with the conventional #ifndef/#define/#endif macro guards or with #pragma once.")); problem->setFinalLocation({indexedPath, KTextEditor::Range()}); problem->setSource(IProblem::Preprocessor); problems << problem; // TODO: Easy to add an assistant here that adds the guards -- any takers? } #endif return problems; } CXTranslationUnit ParseSession::unit() const { return d ? d->m_unit : nullptr; } CXFile ParseSession::file(const QByteArray& path) const { return clang_getFile(unit(), path.constData()); } CXFile ParseSession::mainFile() const { return d ? d->m_file : nullptr; } bool ParseSession::reparse(const QVector& unsavedFiles, const ClangParsingEnvironment& environment) { if (!d || environment != d->m_environment) { return false; } auto unsaved = toClangApi(unsavedFiles); const auto code = clang_reparseTranslationUnit(d->m_unit, unsaved.size(), unsaved.data(), clang_defaultReparseOptions(d->m_unit)); if (code != CXError_Success) { qWarning() << "clang_reparseTranslationUnit return with error code" << code; // if error code != 0 => clang_reparseTranslationUnit invalidates the old translation unit => clean up clang_disposeTranslationUnit(d->m_unit); d->setUnit(nullptr); return false; } // update state d->setUnit(d->m_unit); return true; } ClangParsingEnvironment ParseSession::environment() const { return d->m_environment; } diff --git a/languages/clang/duchain/parsesession.h b/languages/clang/duchain/parsesession.h index 38ccd35e1d..cd57c8abe0 100644 --- a/languages/clang/duchain/parsesession.h +++ b/languages/clang/duchain/parsesession.h @@ -1,139 +1,139 @@ /* This file is part of KDevelop Copyright 2013 Olivier de Gaalon Copyright 2013 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef PARSESESSION_H #define PARSESESSION_H #include #include #include #include #include #include #include #include #include "clangprivateexport.h" #include "clangparsingenvironment.h" #include "unsavedfile.h" class ClangIndex; class KDEVCLANGPRIVATE_EXPORT ParseSessionData : public KDevelop::IAstContainer { public: using Ptr = QExplicitlySharedDataPointer; enum Option { NoOption, ///< No special options SkipFunctionBodies, ///< Pass CXTranslationUnit_SkipFunctionBodies (likely unwanted) PrecompiledHeader ///< Pass CXTranslationUnit_PrecompiledPreamble and others to cache precompiled headers }; Q_DECLARE_FLAGS(Options, Option) /** * Parse the given @p contents. * * @param unsavedFiles Optional unsaved document contents from the editor. */ ParseSessionData(const QVector& unsavedFiles, ClangIndex* index, const ClangParsingEnvironment& environment, Options options = Options()); ~ParseSessionData(); ClangParsingEnvironment environment() const; private: friend class ParseSession; - void setUnit(CXTranslationUnit unit); + QByteArray writeDefinesFile(const QMap& defines); QMutex m_mutex; CXFile m_file = nullptr; CXTranslationUnit m_unit = nullptr; ClangParsingEnvironment m_environment; /// TODO: share this file for all TUs that use the same defines (probably most in a project) /// best would be a PCH, if possible QTemporaryFile m_definesFile; }; /** * Thread-safe utility class around a CXTranslationUnit. * * It will lock the mutex of the currently set ParseSessionData and thereby ensure * only one ParseSession can operate on a given CXTranslationUnit stored therein. */ class KDEVCLANGPRIVATE_EXPORT ParseSession { public: /** * @return a unique identifier for Clang documents. */ static KDevelop::IndexedString languageString(); /** * Initialize a parse session with the given data and, if that data is valid, lock its mutex. */ ParseSession(const ParseSessionData::Ptr& data); /** * Unlocks the mutex of the currently set ParseSessionData. */ ~ParseSession(); /** * Unlocks the mutex of the currently set ParseSessionData, and instead acquire the lock in @p data. */ void setData(const ParseSessionData::Ptr& data); ParseSessionData::Ptr data() const; /** * @return find the CXFile for the given path. */ CXFile file(const QByteArray& path) const; /** * @return the CXFile for the first file in this translation unit. */ CXFile mainFile() const; QList problemsForFile(CXFile file) const; CXTranslationUnit unit() const; bool reparse(const QVector& unsavedFiles, const ClangParsingEnvironment& environment); ClangParsingEnvironment environment() const; private: Q_DISABLE_COPY(ParseSession); ParseSessionData::Ptr d; }; #endif // PARSESESSION_H diff --git a/languages/clang/tests/test_assistants.cpp b/languages/clang/tests/test_assistants.cpp index 46eb29231b..2d9de683fb 100644 --- a/languages/clang/tests/test_assistants.cpp +++ b/languages/clang/tests/test_assistants.cpp @@ -1,534 +1,690 @@ /* This file is part of KDevelop Copyright 2012 Olivier de Gaalon 2014 David Stevens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "test_assistants.h" +#include "codegen/clangrefactoring.h" + #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include +#include #include #include #include using namespace KDevelop; using namespace KTextEditor; QTEST_MAIN(TestAssistants) ForegroundLock *globalTestLock = 0; StaticAssistantsManager *staticAssistantsManager() { return Core::self()->languageController()->staticAssistantsManager(); } void TestAssistants::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral(R"( *.debug=false default.debug=true kdevelop.plugins.clang.debug=true )")); QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({QStringLiteral("kdevclangsupport")}); TestCore::initialize(); DUChain::self()->disablePersistentStorage(); Core::self()->languageController()->backgroundParser()->setDelay(0); Core::self()->sourceFormatterController()->disableSourceFormatting(true); CodeRepresentation::setDiskChangesForbidden(true); globalTestLock = new ForegroundLock; } void TestAssistants::cleanupTestCase() { Core::self()->cleanup(); delete globalTestLock; globalTestLock = 0; } static QUrl createFile(const QString& fileContents, QString extension, int id) { static QTemporaryDir tempDirA; Q_ASSERT(tempDirA.isValid()); static QDir dirA(tempDirA.path()); QFile file(dirA.filePath(QString::number(id) + extension)); file.open(QIODevice::WriteOnly | QIODevice::Text); file.write(fileContents.toUtf8()); file.close(); return QUrl::fromLocalFile(file.fileName()); } class Testbed { public: enum TestDoc { HeaderDoc, CppDoc }; enum IncludeBehavior { NoAutoInclude, AutoInclude, }; Testbed(const QString& headerContents, const QString& cppContents, IncludeBehavior include = AutoInclude) : m_includeBehavior(include) { static int i = 0; int id = i; ++i; m_headerDocument.url = createFile(headerContents,".h",id); m_headerDocument.textDoc = openDocument(m_headerDocument.url); QString preamble; if (include == AutoInclude) preamble = QStringLiteral("#include \"%1\"\n").arg(m_headerDocument.url.toLocalFile()); m_cppDocument.url = createFile(preamble + cppContents,".cpp",id); m_cppDocument.textDoc = openDocument(m_cppDocument.url); } ~Testbed() { Core::self()->documentController()->documentForUrl(m_cppDocument.url)->textDocument(); Core::self()->documentController()->documentForUrl(m_cppDocument.url)->close(KDevelop::IDocument::Discard); Core::self()->documentController()->documentForUrl(m_headerDocument.url)->close(KDevelop::IDocument::Discard); staticAssistantsManager()->hideAssistant(); } void changeDocument(TestDoc which, Range where, const QString& what, bool waitForUpdate = false) { TestDocument document; if (which == CppDoc) { document = m_cppDocument; if (m_includeBehavior == AutoInclude) { where = Range(where.start().line() + 1, where.start().column(), where.end().line() + 1, where.end().column()); //The include adds a line } } else { document = m_headerDocument; } // we must activate the document, otherwise we cannot find the correct active view auto kdevdoc = ICore::self()->documentController()->documentForUrl(document.url); QVERIFY(kdevdoc); ICore::self()->documentController()->activateDocument(kdevdoc); auto view = ICore::self()->documentController()->activeTextDocumentView(); QCOMPARE(view->document(), document.textDoc); view->setSelection(where); view->removeSelectionText(); view->setCursorPosition(where.start()); view->insertText(what); QCoreApplication::processEvents(); if (waitForUpdate) { DUChain::self()->waitForUpdate(IndexedString(document.url), KDevelop::TopDUContext::AllDeclarationsAndContexts); } } QString documentText(TestDoc which) { if (which == CppDoc) { //The CPP document text shouldn't include the autogenerated include line QString text = m_cppDocument.textDoc->text(); return m_includeBehavior == AutoInclude ? text.mid(text.indexOf("\n") + 1) : text; } else return m_headerDocument.textDoc->text(); } QString includeFileName() const { return m_headerDocument.url.toLocalFile(); } KTextEditor::Document *document(TestDoc which) const { return Core::self()->documentController()->documentForUrl( which == CppDoc ? m_cppDocument.url : m_headerDocument.url)->textDocument(); } private: struct TestDocument { QUrl url; Document *textDoc; }; Document* openDocument(const QUrl& url) { Core::self()->documentController()->openDocument(url); DUChain::self()->waitForUpdate(IndexedString(url), KDevelop::TopDUContext::AllDeclarationsAndContexts); return Core::self()->documentController()->documentForUrl(url)->textDocument(); } IncludeBehavior m_includeBehavior; TestDocument m_headerDocument; TestDocument m_cppDocument; }; /** * A StateChange describes an insertion/deletion/replacement and the expected result **/ struct StateChange { StateChange(){}; StateChange(Testbed::TestDoc document, const Range& range, const QString& newText, const QString& result) : document(document) , range(range) , newText(newText) , result(result) { } Testbed::TestDoc document; Range range; QString newText; QString result; }; Q_DECLARE_METATYPE(StateChange) Q_DECLARE_METATYPE(QList) void TestAssistants::testRenameAssistant_data() { QTest::addColumn("fileContents"); QTest::addColumn("oldDeclarationName"); QTest::addColumn >("stateChanges"); QTest::addColumn("finalFileContents"); QTest::newRow("Prepend Text") << "int foo(int i)\n { i = 0; return i; }" << "i" << QList{ StateChange(Testbed::CppDoc, Range(0,12,0,12), "u", "ui"), StateChange(Testbed::CppDoc, Range(0,13,0,13), "z", "uzi"), } << "int foo(int uzi)\n { uzi = 0; return uzi; }"; QTest::newRow("Append Text") << "int foo(int i)\n { i = 0; return i; }" << "i" << (QList() << StateChange(Testbed::CppDoc, Range(0,13,0,13), "d", "id")) << "int foo(int id)\n { id = 0; return id; }"; QTest::newRow("Replace Text") << "int foo(int i)\n { i = 0; return i; }" << "i" << (QList() << StateChange(Testbed::CppDoc, Range(0,12,0,13), "u", "u")) << "int foo(int u)\n { u = 0; return u; }"; QTest::newRow("Letter-by-Letter") << "int foo(int i)\n { i = 0; return i; }" << "i" << (QList() << StateChange(Testbed::CppDoc, Range(0,12,0,13), "", "") << StateChange(Testbed::CppDoc, Range(0,12,0,12), "a", "a") << StateChange(Testbed::CppDoc, Range(0,13,0,13), "b", "ab") << StateChange(Testbed::CppDoc, Range(0,14,0,14), "c", "abc") ) << "int foo(int abc)\n { abc = 0; return abc; }"; QTest::newRow("Paste Replace") << "int foo(int abg)\n { abg = 0; return abg; }" << "abg" << (QList() << StateChange(Testbed::CppDoc, Range(0,12,0,15), "abcdefg", "abcdefg")) << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; QTest::newRow("Paste Insert") << "int foo(int abg)\n { abg = 0; return abg; }" << "abg" << (QList() << StateChange(Testbed::CppDoc, Range(0,14,0,14), "cdef", "abcdefg")) << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; QTest::newRow("Letter-by-Letter Insert") << "int foo(int abg)\n { abg = 0; return abg; }" << "abg" << (QList() << StateChange(Testbed::CppDoc, Range(0,14,0,14), "c", "abcg") << StateChange(Testbed::CppDoc, Range(0,15,0,15), "d", "abcdg") << StateChange(Testbed::CppDoc, Range(0,16,0,16), "e", "abcdeg") << StateChange(Testbed::CppDoc, Range(0,17,0,17), "f", "abcdefg") ) << "int foo(int abcdefg)\n { abcdefg = 0; return abcdefg; }"; } void TestAssistants::testRenameAssistant() { QFETCH(QString, fileContents); Testbed testbed("", fileContents); QFETCH(QString, oldDeclarationName); QFETCH(QList, stateChanges); foreach(StateChange stateChange, stateChanges) { testbed.changeDocument(Testbed::CppDoc, stateChange.range, stateChange.newText); if (stateChange.result.isEmpty()) { QVERIFY(!staticAssistantsManager()->activeAssistant() || !staticAssistantsManager()->activeAssistant()->actions().size()); } else { QVERIFY(staticAssistantsManager()->activeAssistant() && staticAssistantsManager()->activeAssistant()->actions().size()); RenameAction *r = qobject_cast(staticAssistantsManager()->activeAssistant()->actions().first().data()); QCOMPARE(r->oldDeclarationName(), oldDeclarationName); QCOMPARE(r->newDeclarationName(), stateChange.result); } } if (staticAssistantsManager()->activeAssistant() && staticAssistantsManager()->activeAssistant()->actions().size()) { staticAssistantsManager()->activeAssistant()->actions().first()->execute(); } QFETCH(QString, finalFileContents); QCOMPARE(testbed.documentText(Testbed::CppDoc), finalFileContents); } void TestAssistants::testRenameAssistantUndoRename() { Testbed testbed("", "int foo(int i)\n { i = 0; return i; }"); testbed.changeDocument(Testbed::CppDoc, Range(0,13,0,13), "d"); QVERIFY(staticAssistantsManager()->activeAssistant()); QVERIFY(staticAssistantsManager()->activeAssistant()->actions().size() > 0); RenameAction *r = qobject_cast(staticAssistantsManager()->activeAssistant()->actions().first().data()); QVERIFY(r); // now rename the variable back to its original identifier testbed.changeDocument(Testbed::CppDoc, Range(0,13,0,14), ""); // there should be no assistant anymore QVERIFY(!staticAssistantsManager()->activeAssistant()); } const QString SHOULD_ASSIST = "SHOULD_ASSIST"; //An assistant will be visible const QString NO_ASSIST = "NO_ASSIST"; //No assistant visible void TestAssistants::testSignatureAssistant_data() { QTest::addColumn("headerContents"); QTest::addColumn("cppContents"); QTest::addColumn >("stateChanges"); QTest::addColumn("finalHeaderContents"); QTest::addColumn("finalCppContents"); QTest::newRow("change_argument_type") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::HeaderDoc, Range(1,8,1,11), "char", SHOULD_ASSIST)) << "class Foo {\nint bar(char a, char* b, int c = 10); \n};" << "int Foo::bar(char a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; QTest::newRow("prepend_arg_header") << "class Foo { void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 21, 0, 21), "char c, ", SHOULD_ASSIST)) << "class Foo { void bar(char c, int i); };" << "void Foo::bar(char c, int i)\n{}"; QTest::newRow("prepend_arg_cpp") << "class Foo { void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 14, 0, 14), "char c, ", SHOULD_ASSIST)) << "class Foo { void bar(char c, int i); };" << "void Foo::bar(char c, int i)\n{}"; QTest::newRow("change_default_parameter") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::HeaderDoc, Range(1,29,1,34), "", NO_ASSIST)) << "class Foo {\nint bar(int a, char* b, int c); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; QTest::newRow("change_function_type") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::CppDoc, Range(0,0,0,3), "char", SHOULD_ASSIST)) << "class Foo {\nchar bar(int a, char* b, int c = 10); \n};" << "char Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }"; QTest::newRow("swap_args_definition_side") << "class Foo {\nint bar(int a, char* b, int c = 10); \n};" << "int Foo::bar(int a, char* b, int c)\n{ a = c; b = new char; return a + *b; }" << (QList() << StateChange(Testbed::CppDoc, Range(0,13,0,28), "char* b, int a,", SHOULD_ASSIST)) << "class Foo {\nint bar(char* b, int a, int c = 10); \n};" << "int Foo::bar(char* b, int a, int c)\n{ a = c; b = new char; return a + *b; }"; // see https://bugs.kde.org/show_bug.cgi?id=299393 // actually related to the whitespaces in the header... QTest::newRow("change_function_constness") << "class Foo {\nvoid bar(const Foo&) const;\n};" << "void Foo::bar(const Foo&) const\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0,25,0,31), "", SHOULD_ASSIST)) << "class Foo {\nvoid bar(const Foo&);\n};" << "void Foo::bar(const Foo&)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=356179 QTest::newRow("keep_static_cpp") << "class Foo { static void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 19, 0, 19), ", char c", SHOULD_ASSIST)) << "class Foo { static void bar(int i, char c); };" << "void Foo::bar(int i, char c)\n{}"; QTest::newRow("keep_static_header") << "class Foo { static void bar(int i); };" << "void Foo::bar(int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 33, 0, 33), ", char c", SHOULD_ASSIST)) << "class Foo { static void bar(int i, char c); };" << "void Foo::bar(int i, char c)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=356178 QTest::newRow("keep_default_args_cpp_before") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 14, 0, 14), "char c, ", SHOULD_ASSIST)) << "class Foo { void bar(char c, bool b, int i = 0); };" << "void Foo::bar(char c, bool b, int i)\n{}"; QTest::newRow("keep_default_args_cpp_after") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::CppDoc, Range(0, 27, 0, 27), ", char c", SHOULD_ASSIST)) << "class Foo { void bar(bool b, int i = 0, char c = {} /* TODO */); };" << "void Foo::bar(bool b, int i, char c)\n{}"; QTest::newRow("keep_default_args_header_before") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 29, 0, 29), "char c = 'A', ", SHOULD_ASSIST)) << "class Foo { void bar(bool b, char c = 'A', int i = 0); };" << "void Foo::bar(bool b, char c, int i)\n{}"; QTest::newRow("keep_default_args_header_after") << "class Foo { void bar(bool b, int i = 0); };" << "void Foo::bar(bool b, int i)\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 38, 0, 38), ", char c = 'A'", SHOULD_ASSIST)) << "class Foo { void bar(bool b, int i = 0, char c = 'A'); };" << "void Foo::bar(bool b, int i, char c)\n{}"; // see https://bugs.kde.org/show_bug.cgi?id=355356 QTest::newRow("no_retval_on_ctor") << "class Foo { Foo(); };" << "Foo::Foo()\n{}" << (QList() << StateChange(Testbed::HeaderDoc, Range(0, 16, 0, 16), "char c", SHOULD_ASSIST)) << "class Foo { Foo(char c); };" << "Foo::Foo(char c)\n{}"; } void TestAssistants::testSignatureAssistant() { QFETCH(QString, headerContents); QFETCH(QString, cppContents); Testbed testbed(headerContents, cppContents); QFETCH(QList, stateChanges); foreach (StateChange stateChange, stateChanges) { testbed.changeDocument(stateChange.document, stateChange.range, stateChange.newText, true); if (stateChange.result == SHOULD_ASSIST) { QEXPECT_FAIL("change_function_type", "Clang sees that return type of out-of-line definition differs from that in the declaration and won't parse the code...", Abort); QVERIFY(staticAssistantsManager()->activeAssistant() && !staticAssistantsManager()->activeAssistant()->actions().isEmpty()); } else { QVERIFY(!staticAssistantsManager()->activeAssistant() || staticAssistantsManager()->activeAssistant()->actions().isEmpty()); } } if (staticAssistantsManager()->activeAssistant() && !staticAssistantsManager()->activeAssistant()->actions().isEmpty()) staticAssistantsManager()->activeAssistant()->actions().first()->execute(); QFETCH(QString, finalHeaderContents); QFETCH(QString, finalCppContents); QCOMPARE(testbed.documentText(Testbed::HeaderDoc), finalHeaderContents); QCOMPARE(testbed.documentText(Testbed::CppDoc), finalCppContents); } enum UnknownDeclarationActions { NoUnknownDeclaration = 0x0, ForwardDecls = 0x1, MissingInclude = 0x2 }; Q_DECLARE_METATYPE(UnknownDeclarationActions) void TestAssistants::testUnknownDeclarationAssistant_data() { QTest::addColumn("headerContents"); QTest::addColumn("globalText"); QTest::addColumn("functionText"); QTest::addColumn("actions"); QTest::newRow("unincluded_struct") << "struct test{};" << "" << "test" << static_cast(ForwardDecls | MissingInclude); QTest::newRow("forward_declared_struct") << "struct test{};" << "struct test;" << "test *f; f->" << static_cast(MissingInclude); QTest::newRow("unknown_struct") << "" << "" << "test" << static_cast(ForwardDecls); } void TestAssistants::testUnknownDeclarationAssistant() { QFETCH(QString, headerContents); QFETCH(QString, globalText); QFETCH(QString, functionText); QFETCH(UnknownDeclarationActions, actions); static const auto cppContents = QStringLiteral("%1\nvoid f_u_n_c_t_i_o_n() {\n}"); Testbed testbed(headerContents, cppContents.arg(globalText), Testbed::NoAutoInclude); const int line = testbed.document(Testbed::CppDoc)->lines() - 1; testbed.changeDocument(Testbed::CppDoc, Range(line, 0, line, 0), functionText, true); if (actions == NoUnknownDeclaration) { QVERIFY(!staticAssistantsManager()->activeAssistant()); return; } QVERIFY(staticAssistantsManager()->activeAssistant()); const auto assistantActions = staticAssistantsManager()->activeAssistant()->actions(); QStringList actionDescriptions; for (auto action: assistantActions) { actionDescriptions << action->description(); } { const bool hasForwardDecls = actionDescriptions.contains(QObject::tr("Forward declare as 'struct'")) && actionDescriptions.contains(QObject::tr("Forward declare as 'class'")); QCOMPARE(hasForwardDecls, static_cast(actions & ForwardDecls)); } { auto fileName = testbed.includeFileName(); fileName = fileName.mid(fileName.lastIndexOf('/') + 1); const auto description = QObject::tr("Insert \'%1\'") .arg(QStringLiteral("#include \"%1\"").arg(fileName)); const bool hasMissingInclude = actionDescriptions.contains(description); QCOMPARE(hasMissingInclude, static_cast(actions & MissingInclude)); } } + +void TestAssistants::testMoveIntoSource() +{ + QFETCH(QString, origHeader); + QFETCH(QString, origImpl); + QFETCH(QString, newHeader); + QFETCH(QString, newImpl); + QFETCH(QualifiedIdentifier, id); + + TestFile header(origHeader, "h"); + TestFile impl("#include \"" + header.url().byteArray() + "\"\n" + origImpl, "cpp", &header); + + impl.parse(KDevelop::TopDUContext::AllDeclarationsContextsAndUses); + QVERIFY(impl.waitForParsed()); + + IndexedDeclaration declaration; + { + DUChainReadLocker lock; + auto headerCtx = DUChain::self()->chainForDocument(header.url()); + QVERIFY(headerCtx); + auto decls = headerCtx->findDeclarations(id); + Q_ASSERT(!decls.isEmpty()); + declaration = IndexedDeclaration(decls.first()); + QVERIFY(declaration.isValid()); + } + CodeRepresentation::setDiskChangesForbidden(false); + ClangRefactoring refactoring; + QCOMPARE(refactoring.moveIntoSource(declaration), QString()); + CodeRepresentation::setDiskChangesForbidden(true); + + QCOMPARE(header.fileContents(), newHeader); + QVERIFY(impl.fileContents().endsWith(newImpl)); +} + +void TestAssistants::testMoveIntoSource_data() +{ + QTest::addColumn("origHeader"); + QTest::addColumn("origImpl"); + QTest::addColumn("newHeader"); + QTest::addColumn("newImpl"); + QTest::addColumn("id"); + + const QualifiedIdentifier fooId("foo"); + + QTest::newRow("globalfunction") << QString("int foo()\n{\n int i = 0;\n return 0;\n}\n") + << QString() + << QString("int foo();\n") + << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") + << fooId; + + QTest::newRow("staticfunction") << QString("static int foo()\n{\n int i = 0;\n return 0;\n}\n") + << QString() + << QString("static int foo();\n") + << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") + << fooId; + + QTest::newRow("funcsameline") << QString("int foo() {\n int i = 0;\n return 0;\n}\n") + << QString() + << QString("int foo();\n") + << QString("\nint foo() {\n int i = 0;\n return 0;\n}\n") + << fooId; + + QTest::newRow("func-comment") << QString("int foo()\n/* foobar */ {\n int i = 0;\n return 0;\n}\n") + << QString() + << QString("int foo()\n/* foobar */;\n") + << QString("\nint foo() {\n int i = 0;\n return 0;\n}\n") + << fooId; + + QTest::newRow("func-comment2") << QString("int foo()\n/*asdf*/\n{\n int i = 0;\n return 0;\n}\n") + << QString() + << QString("int foo()\n/*asdf*/;\n") + << QString("\nint foo()\n{\n int i = 0;\n return 0;\n}\n") + << fooId; + + const QualifiedIdentifier aFooId("a::foo"); + QTest::newRow("class-method") << QString("class a {\n int foo(){\n return 0;\n }\n};\n") + << QString() + << QString("class a {\n int foo();\n};\n") + << QString("\nint a::foo() {\n return 0;\n }\n") + << aFooId; + + QTest::newRow("class-method-const") << QString("class a {\n int foo() const\n {\n return 0;\n }\n};\n") + << QString() + << QString("class a {\n int foo() const;\n};\n") + << QString("\nint a::foo() const\n {\n return 0;\n }\n") + << aFooId; + + QTest::newRow("class-method-const-sameline") << QString("class a {\n int foo() const{\n return 0;\n }\n};\n") + << QString() + << QString("class a {\n int foo() const;\n};\n") + << QString("\nint a::foo() const {\n return 0;\n }\n") + << aFooId; + QTest::newRow("elaborated-type") << QString("namespace NS{class C{};} class a {\nint foo(const NS::C c) const{\nreturn 0;\n}\n};\n") + << QString() + << QString("namespace NS{class C{};} class a {\nint foo(const NS::C c) const;\n};\n") + << QString("\nint a::foo(const NS::C c) const {\nreturn 0;\n}\n") + << aFooId; + QTest::newRow("add-into-namespace") << QString("namespace NS{class a {\nint foo() const {\nreturn 0;\n}\n};\n}") + << QString("namespace NS{\n}") + << QString("namespace NS{class a {\nint foo() const;\n};\n}") + << QString("namespace NS{\n\nint a::foo() const {\nreturn 0;\n}\n}") + << QualifiedIdentifier("NS::a::foo"); + QTest::newRow("class-template-parameter") + << QString(R"( + namespace first { + template + class Test{}; + + namespace second { + template + class List; + } + + class MoveIntoSource + { + public: + void f(const second::List>*>& param){} + };} + )") + << QString("") + << QString(R"( + namespace first { + template + class Test{}; + + namespace second { + template + class List; + } + + class MoveIntoSource + { + public: + void f(const second::List>*>& param); + };} + )") + << QString("namespace first {\nvoid MoveIntoSource::f(const first::second::List< const volatile first::Test< first::second::List< int* > >* >& param) {}}\n\n") + << QualifiedIdentifier("first::MoveIntoSource::f"); + + QTest::newRow("move-unexposed-type") + << QString("namespace std { template class basic_string; \ntypedef basic_string string;}\n void move(std::string i){}") + << QString("") + << QString("namespace std { template class basic_string; \ntypedef basic_string string;}\n void move(std::string i);") + << QString("void move(std::string i) {}\n") + << QualifiedIdentifier("move"); + QTest::newRow("move-constructor") + << QString("class Class{Class(){}\n};") + << QString("") + << QString("class Class{Class();\n};") + << QString("Class::Class() {}\n") + << QualifiedIdentifier("Class::Class"); +} diff --git a/languages/clang/tests/test_assistants.h b/languages/clang/tests/test_assistants.h index 43d168e246..88cd6794ea 100644 --- a/languages/clang/tests/test_assistants.h +++ b/languages/clang/tests/test_assistants.h @@ -1,41 +1,44 @@ /* This file is part of KDevelop Copyright 2012 Olivier de Gaalon 2014 David Stevens This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TESTASSISTANTS_H #define TESTASSISTANTS_H #include class TestAssistants : public QObject { Q_OBJECT private slots: void initTestCase(); void cleanupTestCase(); void testRenameAssistant_data(); void testRenameAssistant(); void testRenameAssistantUndoRename(); void testSignatureAssistant_data(); void testSignatureAssistant(); void testUnknownDeclarationAssistant_data(); void testUnknownDeclarationAssistant(); + + void testMoveIntoSource_data(); + void testMoveIntoSource(); }; #endif diff --git a/languages/clang/tests/test_duchain.cpp b/languages/clang/tests/test_duchain.cpp index 6c6581ad92..cbfb3800ae 100644 --- a/languages/clang/tests/test_duchain.cpp +++ b/languages/clang/tests/test_duchain.cpp @@ -1,1649 +1,1650 @@ /* * Copyright 2014 Milian Wolff * Copyright 2014 Kevin Funk * Copyright 2015 Sergey Kalinichev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_duchain.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "duchain/clangparsingenvironmentfile.h" #include "duchain/clangparsingenvironment.h" #include "duchain/parsesession.h" #include #include QTEST_MAIN(TestDUChain); using namespace KDevelop; class TestEnvironmentProvider final : public IDefinesAndIncludesManager::BackgroundProvider { public: ~TestEnvironmentProvider() override = default; QHash< QString, QString > definesInBackground(const QString& /*path*/) const override { return defines; } Path::List includesInBackground(const QString& /*path*/) const override { return includes; } IDefinesAndIncludesManager::Type type() const override { return IDefinesAndIncludesManager::UserDefined; } QHash defines; Path::List includes; }; TestDUChain::~TestDUChain() = default; void TestDUChain::initTestCase() { QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.plugins.clang.debug=true\n")); QVERIFY(qputenv("KDEV_DISABLE_PLUGINS", "kdevcppsupport")); QVERIFY(qputenv("KDEV_CLANG_DISPLAY_DIAGS", "1")); AutoTestShell::init({QStringLiteral("kdevclangsupport")}); auto core = TestCore::initialize(); delete core->projectController(); m_projectController = new TestProjectController(core); core->setProjectController(m_projectController); } void TestDUChain::cleanupTestCase() { TestCore::shutdown(); } void TestDUChain::cleanup() { if (m_provider) { IDefinesAndIncludesManager::manager()->unregisterBackgroundProvider(m_provider.data()); } } void TestDUChain::init() { m_provider.reset(new TestEnvironmentProvider); IDefinesAndIncludesManager::manager()->registerBackgroundProvider(m_provider.data()); } struct ExpectedComment { QString identifier; QString comment; }; Q_DECLARE_METATYPE(ExpectedComment) Q_DECLARE_METATYPE(AbstractType::WhichType) void TestDUChain::testComments() { QFETCH(QString, code); QFETCH(ExpectedComment, expectedComment); TestFile file(code, "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); auto candidates = top->findDeclarations(QualifiedIdentifier(expectedComment.identifier)); QVERIFY(!candidates.isEmpty()); auto decl = candidates.first(); QString comment = QString::fromLocal8Bit(decl->comment()); comment = KDevelop::htmlToPlainText(comment, KDevelop::CompleteMode); QCOMPARE(comment, expectedComment.comment); } void TestDUChain::testComments_data() { QTest::addColumn("code"); QTest::addColumn("expectedComment"); // note: Clang only retrieves the comments when in doxygen-style format (i.e. '///', '/**', '///<') QTest::newRow("invalid1") << "//this is foo\nint foo;" << ExpectedComment{"foo", QString()}; QTest::newRow("invalid2") << "/*this is foo*/\nint foo;" << ExpectedComment{"foo", QString()}; QTest::newRow("basic1") << "///this is foo\nint foo;" << ExpectedComment{"foo", "this is foo"}; QTest::newRow("basic2") << "/**this is foo*/\nint foo;" << ExpectedComment{"foo", "this is foo"}; QTest::newRow("enumerator") << "enum Foo { bar1, ///localDeclarations().size(), 2); auto decl = file.topContext()->localDeclarations()[1]; QVERIFY(decl); auto function = dynamic_cast(decl); QVERIFY(function); auto functionType = function->type(); QVERIFY(functionType); QEXPECT_FAIL("namespace", "The ElaboratedType is not exposed through the libclang interface, not much we can do here", Abort); QVERIFY(functionType->returnType()->whichType() != AbstractType::TypeDelayed); QEXPECT_FAIL("typedef", "After using clang_getCanonicalType on ElaboratedType all typedef information get's stripped away", Continue); QCOMPARE(functionType->returnType()->whichType(), type); } void TestDUChain::testElaboratedType_data() { QTest::addColumn("code"); QTest::addColumn("type"); QTest::newRow("namespace") << "namespace NS{struct Type{};} struct NS::Type foo();" << AbstractType::TypeStructure; QTest::newRow("enum") << "enum Enum{}; enum Enum foo();" << AbstractType::TypeEnumeration; QTest::newRow("typedef") << "namespace NS{typedef int type;} NS::type foo();" << AbstractType::TypeAlias; } void TestDUChain::testInclude() { TestFile header("int foo() { return 42; }\n", "h"); // NOTE: header is _not_ explictly being parsed, instead the impl job does that TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); impl.parse(TopDUContext::AllDeclarationsContextsAndUses); auto implCtx = impl.topContext(); QVERIFY(implCtx); DUChainReadLocker lock; QCOMPARE(implCtx->localDeclarations().size(), 1); auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(!headerCtx->parsingEnvironmentFile()->needsUpdate()); QCOMPARE(headerCtx->localDeclarations().size(), 1); QVERIFY(implCtx->imports(headerCtx, CursorInRevision(0, 10))); Declaration* foo = headerCtx->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); QCOMPARE(foo->uses().begin().key(), impl.url()); QCOMPARE(foo->uses().begin()->size(), 1); QCOMPARE(foo->uses().begin()->first(), RangeInRevision(1, 20, 1, 23)); } void TestDUChain::testMissingInclude() { auto code = R"( +#pragma once #include "missing1.h" template class A { T a; }; #include "missing2.h" class B : public A { }; )"; // NOTE: This fails and needs fixing. If the include of "missing2.h" // above is commented out, then it doesn't fail. Maybe // clang stops processing when it encounters the second missing // header, or similar. TestFile header(code, "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n", "cpp", &header); QVERIFY(impl.parseAndWait(TopDUContext::AllDeclarationsContextsAndUses)); DUChainReadLocker lock; auto top = impl.topContext(); QVERIFY(top); QCOMPARE(top->importedParentContexts().count(), 1); TopDUContext* headerCtx = dynamic_cast(top->importedParentContexts().first().context(top)); QVERIFY(headerCtx); QCOMPARE(headerCtx->url(), header.url()); QEXPECT_FAIL("", "Second missing header isn't reported", Continue); QCOMPARE(headerCtx->problems().count(), 2); QCOMPARE(headerCtx->localDeclarations().count(), 2); auto a = dynamic_cast(headerCtx->localDeclarations().first()); QVERIFY(a); auto b = dynamic_cast(headerCtx->localDeclarations().last()); QVERIFY(b); QEXPECT_FAIL("", "Base class isn't assigned correctly", Continue); QCOMPARE(b->baseClassesSize(), 1u); // at least the one problem we have should have been propagated QCOMPARE(top->problems().count(), 1); } QByteArray createCode(const QByteArray& prefix, const int functions) { QByteArray code; code += "#ifndef " + prefix + "_H\n"; code += "#define " + prefix + "_H\n"; for (int i = 0; i < functions; ++i) { code += "void myFunc_" + prefix + "(int arg1, char arg2, const char* arg3);\n"; } code += "#endif\n"; return code; } void TestDUChain::testIncludeLocking() { TestFile header1(createCode("Header1", 1000), "h"); TestFile header2(createCode("Header2", 1000), "h"); TestFile header3(createCode("Header3", 1000), "h"); ICore::self()->languageController()->backgroundParser()->setThreadCount(3); TestFile impl1("#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header2.url().byteArray() + "\"\n" "#include \"" + header3.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); TestFile impl2("#include \"" + header2.url().byteArray() + "\"\n" "#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header3.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); TestFile impl3("#include \"" + header3.url().byteArray() + "\"\n" "#include \"" + header1.url().byteArray() + "\"\n" "#include \"" + header2.url().byteArray() + "\"\n" "int main() { return 0; }", "cpp"); impl1.parse(TopDUContext::AllDeclarationsContextsAndUses); impl2.parse(TopDUContext::AllDeclarationsContextsAndUses); impl3.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(impl1.waitForParsed(5000)); QVERIFY(impl2.waitForParsed(5000)); QVERIFY(impl3.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(DUChain::self()->chainForDocument(header1.url())); QVERIFY(DUChain::self()->chainForDocument(header2.url())); QVERIFY(DUChain::self()->chainForDocument(header3.url())); } void TestDUChain::testReparse() { TestFile file("int main() { int i = 42; return i; }", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); DeclarationPointer mainDecl; DeclarationPointer iDecl; for (int i = 0; i < 3; ++i) { QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 1); DUContext *exprContext = file.topContext()->childContexts().first()->childContexts().first(); QCOMPARE(exprContext->localDeclarations().size(), 1); if (i) { QVERIFY(mainDecl); QCOMPARE(mainDecl.data(), file.topContext()->localDeclarations().first()); QVERIFY(iDecl); QCOMPARE(iDecl.data(), exprContext->localDeclarations().first()); } mainDecl = file.topContext()->localDeclarations().first(); iDecl = exprContext->localDeclarations().first(); QVERIFY(mainDecl->uses().isEmpty()); QCOMPARE(iDecl->uses().size(), 1); QCOMPARE(iDecl->uses().begin()->size(), 1); if (i == 1) { file.setFileContents("int main()\n{\nfloat i = 13; return i - 5;\n}\n"); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testReparseError() { TestFile file("int i = 1 / 0;\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { QVERIFY(file.waitForParsed(500)); DUChainReadLocker lock; QVERIFY(file.topContext()); if (!i) { QCOMPARE(file.topContext()->problems().size(), 1); file.setFileContents("int i = 0;\n"); } else { QCOMPARE(file.topContext()->problems().size(), 0); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testTemplate() { TestFile file("template struct foo { T bar; };\n" "int main() { foo myFoo; return myFoo.bar; }\n", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto fooDecl = file.topContext()->localDeclarations().first(); QVERIFY(fooDecl->internalContext()); QCOMPARE(fooDecl->internalContext()->localDeclarations().size(), 2); QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo")).size(), 1); QCOMPARE(file.topContext()->findDeclarations(QualifiedIdentifier("foo::bar")).size(), 1); auto mainCtx = file.topContext()->localDeclarations().last()->internalContext()->childContexts().first(); QVERIFY(mainCtx); auto myFoo = mainCtx->localDeclarations().first(); QVERIFY(myFoo); QCOMPARE(myFoo->abstractType()->toString().remove(' '), QStringLiteral("foo")); } void TestDUChain::testNamespace() { TestFile file("namespace foo { struct bar { int baz; }; }\n" "int main() { foo::bar myBar; }\n", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto fooDecl = file.topContext()->localDeclarations().first(); QVERIFY(fooDecl->internalContext()); QCOMPARE(fooDecl->internalContext()->localDeclarations().size(), 1); DUContext* top = file.topContext().data(); DUContext* mainCtx = file.topContext()->childContexts().last(); auto foo = top->localDeclarations().first(); QCOMPARE(foo->qualifiedIdentifier().toString(), QString("foo")); DUContext* fooCtx = file.topContext()->childContexts().first(); QCOMPARE(fooCtx->localScopeIdentifier().toString(), QString("foo")); QCOMPARE(fooCtx->scopeIdentifier(true).toString(), QString("foo")); QCOMPARE(fooCtx->localDeclarations().size(), 1); auto bar = fooCtx->localDeclarations().first(); QCOMPARE(bar->qualifiedIdentifier().toString(), QString("foo::bar")); QCOMPARE(fooCtx->childContexts().size(), 1); DUContext* barCtx = fooCtx->childContexts().first(); QCOMPARE(barCtx->localScopeIdentifier().toString(), QString("bar")); QCOMPARE(barCtx->scopeIdentifier(true).toString(), QString("foo::bar")); QCOMPARE(barCtx->localDeclarations().size(), 1); auto baz = barCtx->localDeclarations().first(); QCOMPARE(baz->qualifiedIdentifier().toString(), QString("foo::bar::baz")); for (auto ctx : {top, mainCtx}) { QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo")).size(), 1); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo::bar")).size(), 1); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo::bar::baz")).size(), 1); } } void TestDUChain::testAutoTypeDeduction() { TestFile file(R"( const volatile auto foo = 5; template struct myTemplate {}; myTemplate& > templRefParam; auto autoTemplRefParam = templRefParam; )", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* ctx = file.topContext().data(); QVERIFY(ctx); QCOMPARE(ctx->localDeclarations().size(), 4); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("foo")).size(), 1); Declaration* decl = ctx->findDeclarations(QualifiedIdentifier("foo"))[0]; QCOMPARE(decl->identifier(), Identifier("foo")); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "No type deduction here unfortunately, missing API in Clang", Continue); #endif QVERIFY(decl->type()); #if CINDEX_VERSION_MINOR < 31 QCOMPARE(decl->toString(), QStringLiteral("const volatile auto foo")); #else QCOMPARE(decl->toString(), QStringLiteral("const volatile int foo")); #endif decl = ctx->findDeclarations(QualifiedIdentifier("autoTemplRefParam"))[0]; QVERIFY(decl); QVERIFY(decl->abstractType()); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "Auto type is not exposed via LibClang", Continue); #endif QCOMPARE(decl->abstractType()->toString(), QStringLiteral("myTemplate< myTemplate< int >& >")); } void TestDUChain::testTypeDeductionInTemplateInstantiation() { // see: http://clang-developers.42468.n3.nabble.com/RFC-missing-libclang-query-functions-features-td2504253.html TestFile file("template struct foo { T member; } foo f; auto i = f.member;", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* ctx = file.topContext().data(); QVERIFY(ctx); QCOMPARE(ctx->localDeclarations().size(), 3); Declaration* decl = 0; // check 'foo' declaration decl = ctx->localDeclarations()[0]; QVERIFY(decl); QCOMPARE(decl->identifier(), Identifier("foo")); // check type of 'member' inside declaration-scope QCOMPARE(ctx->childContexts().size(), 1); DUContext* fooCtx = ctx->childContexts().first(); QVERIFY(fooCtx); // Should there really be two declarations? QCOMPARE(fooCtx->localDeclarations().size(), 2); decl = fooCtx->localDeclarations()[1]; QCOMPARE(decl->identifier(), Identifier("member")); // check type of 'member' in definition of 'f' decl = ctx->localDeclarations()[1]; QCOMPARE(decl->identifier(), Identifier("f")); decl = ctx->localDeclarations()[2]; QCOMPARE(decl->identifier(), Identifier("i")); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "No type deduction here unfortunately, missing API in Clang", Continue); #endif QVERIFY(decl->type()); } void TestDUChain::testVirtualMemberFunction() { //Forward-declarations with "struct" or "class" are considered equal, so make sure the override is detected correctly. TestFile file("struct S {}; struct A { virtual S* ret(); }; struct B : public A { virtual S* ret(); };", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* top = file.topContext().data(); QVERIFY(top); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 3); QCOMPARE(top->childContexts()[2]->localDeclarations().count(), 1); Declaration* decl = top->childContexts()[2]->localDeclarations()[0]; QCOMPARE(decl->identifier(), Identifier("ret")); QVERIFY(DUChainUtils::getOverridden(decl)); } void TestDUChain::testBaseClasses() { TestFile file("class Base {}; class Inherited : public Base {};", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; DUContext* top = file.topContext().data(); QVERIFY(top); QCOMPARE(top->localDeclarations().count(), 2); Declaration* baseDecl = top->localDeclarations().first(); QCOMPARE(baseDecl->identifier(), Identifier("Base")); ClassDeclaration* inheritedDecl = dynamic_cast(top->localDeclarations()[1]); QCOMPARE(inheritedDecl->identifier(), Identifier("Inherited")); QVERIFY(inheritedDecl); QCOMPARE(inheritedDecl->baseClassesSize(), 1u); QCOMPARE(baseDecl->uses().count(), 1); QCOMPARE(baseDecl->uses().first().count(), 1); QCOMPARE(baseDecl->uses().first().first(), RangeInRevision(0, 40, 0, 44)); } void TestDUChain::testReparseBaseClasses() { TestFile file("struct a{}; struct b : a {};\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { qDebug() << "run: " << i; QVERIFY(file.waitForParsed(500)); DUChainWriteLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->childContexts().first()->importers().size(), 1); QCOMPARE(file.topContext()->childContexts().last()->importedParentContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto aDecl = dynamic_cast(file.topContext()->localDeclarations().first()); QVERIFY(aDecl); QCOMPARE(aDecl->baseClassesSize(), 0u); auto bDecl = dynamic_cast(file.topContext()->localDeclarations().last()); QVERIFY(bDecl); QCOMPARE(bDecl->baseClassesSize(), 1u); int distance = 0; QVERIFY(bDecl->isPublicBaseClass(aDecl, file.topContext(), &distance)); QCOMPARE(distance, 1); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testReparseBaseClassesTemplates() { TestFile file("template struct a{}; struct b : a {};\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); for (int i = 0; i < 2; ++i) { qDebug() << "run: " << i; QVERIFY(file.waitForParsed(500)); DUChainWriteLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->childContexts().first()->importers().size(), 1); QCOMPARE(file.topContext()->childContexts().last()->importedParentContexts().size(), 1); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto aDecl = dynamic_cast(file.topContext()->localDeclarations().first()); QVERIFY(aDecl); QCOMPARE(aDecl->baseClassesSize(), 0u); auto bDecl = dynamic_cast(file.topContext()->localDeclarations().last()); QVERIFY(bDecl); QCOMPARE(bDecl->baseClassesSize(), 1u); int distance = 0; QVERIFY(bDecl->isPublicBaseClass(aDecl, file.topContext(), &distance)); QCOMPARE(distance, 1); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); } } void TestDUChain::testGetInheriters_data() { QTest::addColumn("code"); QTest::newRow("inline") << "struct Base { struct Inner {}; }; struct Inherited : Base, Base::Inner {};"; QTest::newRow("outline") << "struct Base { struct Inner; }; struct Base::Inner {}; struct Inherited : Base, Base::Inner {};"; } void TestDUChain::testGetInheriters() { QFETCH(QString, code); TestFile file(code, "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(top->problems().isEmpty()); QCOMPARE(top->localDeclarations().count(), 2); Declaration* baseDecl = top->localDeclarations().first(); QCOMPARE(baseDecl->identifier(), Identifier("Base")); DUContext* baseCtx = baseDecl->internalContext(); QVERIFY(baseCtx); QCOMPARE(baseCtx->localDeclarations().count(), 1); Declaration* innerDecl = baseCtx->localDeclarations().first(); QCOMPARE(innerDecl->identifier(), Identifier("Inner")); if (auto forward = dynamic_cast(innerDecl)) { innerDecl = forward->resolve(top); } QVERIFY(dynamic_cast(innerDecl)); Declaration* inheritedDecl = top->localDeclarations().last(); QVERIFY(inheritedDecl); QCOMPARE(inheritedDecl->identifier(), Identifier("Inherited")); uint maxAllowedSteps = uint(-1); auto baseInheriters = DUChainUtils::getInheriters(baseDecl, maxAllowedSteps); QCOMPARE(baseInheriters, QList() << inheritedDecl); maxAllowedSteps = uint(-1); auto innerInheriters = DUChainUtils::getInheriters(innerDecl, maxAllowedSteps); QCOMPARE(innerInheriters, QList() << inheritedDecl); maxAllowedSteps = uint(-1); auto inheritedInheriters = DUChainUtils::getInheriters(inheritedDecl, maxAllowedSteps); QCOMPARE(inheritedInheriters.count(), 0); } void TestDUChain::testGlobalFunctionDeclaration() { TestFile file("void foo(int arg1, char arg2);\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); file.waitForParsed(); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); QCOMPARE(file.topContext()->childContexts().size(), 1); QVERIFY(!file.topContext()->childContexts().first()->inSymbolTable()); } void TestDUChain::testFunctionDefinitionVsDeclaration() { TestFile file("void func(); void func() {}\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto funcDecl = file.topContext()->localDeclarations()[0]; QVERIFY(!funcDecl->isDefinition()); QVERIFY(!dynamic_cast(funcDecl)); auto funcDef = file.topContext()->localDeclarations()[1]; QVERIFY(dynamic_cast(funcDef)); QVERIFY(funcDef->isDefinition()); } void TestDUChain::testEnsureNoDoubleVisit() { // On some language construct, we may up visiting the same cursor multiple times // Example: "struct SomeStruct {} s;" // decl: "SomeStruct SomeStruct " of kind StructDecl (2) in main.cpp@[(1,1),(1,17)] // decl: "struct SomeStruct s " of kind VarDecl (9) in main.cpp@[(1,1),(1,19)] // decl: "SomeStruct SomeStruct " of kind StructDecl (2) in main.cpp@[(1,1),(1,17)] // // => We end up visiting the StructDecl twice (or more) // That's because we use clang_visitChildren not just on the translation unit cursor. // Apparently just "recursing" vs. "visiting children explicitly" // results in a different AST traversal TestFile file("struct SomeStruct {} s;\n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); // there should only be one declaration for "SomeStruct" auto candidates = top->findDeclarations(QualifiedIdentifier("SomeStruct")); QCOMPARE(candidates.size(), 1); } void TestDUChain::testParsingEnvironment() { const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; IndexedTopDUContext indexed; ClangParsingEnvironment lastEnv; { TestFile file("int main() {}\n", "cpp"); auto astFeatures = static_cast(features | TopDUContext::AST); file.parse(astFeatures); file.setKeepDUChainData(true); QVERIFY(file.waitForParsed()); DUChainWriteLocker lock; auto top = file.topContext(); QVERIFY(top); auto sessionData = ParseSessionData::Ptr(dynamic_cast(top->ast().data())); lock.unlock(); ParseSession session(sessionData); lock.lock(); QVERIFY(session.data()); QVERIFY(top); auto envFile = QExplicitlySharedDataPointer( dynamic_cast(file.topContext()->parsingEnvironmentFile().data())); QCOMPARE(envFile->features(), astFeatures); QVERIFY(envFile->featuresSatisfied(astFeatures)); QCOMPARE(envFile->environmentQuality(), ClangParsingEnvironment::Source); // if no environment is given, no update should be triggered QVERIFY(!envFile->needsUpdate()); // same env should also not trigger a reparse ClangParsingEnvironment env = session.environment(); QCOMPARE(env.quality(), ClangParsingEnvironment::Source); QVERIFY(!envFile->needsUpdate(&env)); // but changing the environment should trigger an update env.addIncludes(Path::List() << Path("/foo/bar/baz")); QVERIFY(envFile->needsUpdate(&env)); envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); // setting the environment quality higher should require an update env.setQuality(ClangParsingEnvironment::BuildSystem); QVERIFY(envFile->needsUpdate(&env)); envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); // changing defines requires an update env.addDefines(QHash{ { "foo", "bar" } }); QVERIFY(envFile->needsUpdate(&env)); // but only when changing the defines for the envFile's TU const auto barTU = IndexedString("bar.cpp"); const auto oldTU = env.translationUnitUrl(); env.setTranslationUnitUrl(barTU); QCOMPARE(env.translationUnitUrl(), barTU); QVERIFY(!envFile->needsUpdate(&env)); env.setTranslationUnitUrl(oldTU); QVERIFY(envFile->needsUpdate(&env)); // update it again envFile->setEnvironment(env); QVERIFY(!envFile->needsUpdate(&env)); lastEnv = env; // now compare against a lower quality environment // in such a case, we do not want to trigger an update env.setQuality(ClangParsingEnvironment::Unknown); env.setTranslationUnitUrl(barTU); QVERIFY(!envFile->needsUpdate(&env)); // even when the environment changes env.addIncludes(Path::List() << Path("/lalalala")); QVERIFY(!envFile->needsUpdate(&env)); indexed = top->indexed(); } DUChain::self()->storeToDisk(); { DUChainWriteLocker lock; QVERIFY(!DUChain::self()->isInMemory(indexed.index())); QVERIFY(indexed.data()); QVERIFY(DUChain::self()->environmentFileForDocument(indexed)); auto envFile = QExplicitlySharedDataPointer( dynamic_cast(DUChain::self()->environmentFileForDocument(indexed).data())); QVERIFY(envFile); QCOMPARE(envFile->features(), features); QVERIFY(envFile->featuresSatisfied(features)); QVERIFY(!envFile->needsUpdate(&lastEnv)); DUChain::self()->removeDocumentChain(indexed.data()); } } void TestDUChain::testActiveDocumentHasASTAttached() { const TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; IndexedTopDUContext indexed; ClangParsingEnvironment lastEnv; { TestFile file("int main() {}\n", "cpp"); auto astFeatures = static_cast(features | TopDUContext::AST); file.parse(astFeatures); file.setKeepDUChainData(true); QVERIFY(file.waitForParsed()); DUChainWriteLocker lock; auto top = file.topContext(); QVERIFY(top); auto sessionData = ParseSessionData::Ptr(dynamic_cast(top->ast().data())); lock.unlock(); ParseSession session(sessionData); lock.lock(); QVERIFY(session.data()); QVERIFY(top); QVERIFY(top->ast()); indexed = top->indexed(); } DUChain::self()->storeToDisk(); { DUChainWriteLocker lock; QVERIFY(!DUChain::self()->isInMemory(indexed.index())); QVERIFY(indexed.data()); } QUrl url; { DUChainReadLocker lock; auto ctx = indexed.data(); QVERIFY(ctx); QVERIFY(!ctx->ast()); url = ctx->url().toUrl(); } // Here the file is already deleted, so clang_parseTranslationUnit2 will fail, but we still get ParseSessionData attached. auto document = ICore::self()->documentController()->openDocument(url); QVERIFY(document); ICore::self()->documentController()->activateDocument(document); QApplication::processEvents(); ICore::self()->languageController()->backgroundParser()->parseDocuments(); QThread::sleep(1); document->close(KDevelop::IDocument::Discard); { DUChainReadLocker lock; auto ctx = indexed.data(); QVERIFY(ctx); QVERIFY(ctx->ast()); } DUChainWriteLocker lock; DUChain::self()->removeDocumentChain(indexed.data()); } void TestDUChain::testSystemIncludes() { ClangParsingEnvironment env; Path::List projectIncludes = { Path("/projects/1"), Path("/projects/1/sub"), Path("/projects/2"), Path("/projects/2/sub") }; env.addIncludes(projectIncludes); auto includes = env.includes(); // no project paths set, so everything is considered a system include QCOMPARE(includes.system, projectIncludes); QVERIFY(includes.project.isEmpty()); Path::List systemIncludes = { Path("/sys"), Path("/sys/sub") }; env.addIncludes(systemIncludes); includes = env.includes(); QCOMPARE(includes.system, projectIncludes + systemIncludes); QVERIFY(includes.project.isEmpty()); Path::List projects = { Path("/projects/1"), Path("/projects/2") }; env.setProjectPaths(projects); // now the list should be properly separated QCOMPARE(env.projectPaths(), projects); includes = env.includes(); QCOMPARE(includes.system, systemIncludes); QCOMPARE(includes.project, projectIncludes); } void TestDUChain::benchDUChainBuilder() { QBENCHMARK_ONCE { TestFile file( "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n" "#include \n", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(60000)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); } } void TestDUChain::testReparseWithAllDeclarationsContextsAndUses() { TestFile file("int foo() { return 0; } int main() { return foo(); }", "cpp"); file.parse(TopDUContext::VisibleDeclarationsAndContexts); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto dec = file.topContext()->localDeclarations().at(0); QEXPECT_FAIL("", "Skipping of function bodies is disabled for now", Continue); QVERIFY(dec->uses().isEmpty()); } file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(500)); { DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->childContexts().size(), 2); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto mainDecl = file.topContext()->localDeclarations()[1]; QVERIFY(mainDecl->uses().isEmpty()); auto foo = file.topContext()->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); } } void TestDUChain::testReparseOnDocumentActivated() { TestFile file("int foo() { return 0; } int main() { return foo(); }", "cpp"); file.parse(TopDUContext::VisibleDeclarationsAndContexts); QVERIFY(file.waitForParsed(1000)); { DUChainReadLocker lock; auto ctx = file.topContext(); QVERIFY(ctx); QCOMPARE(ctx->childContexts().size(), 2); QCOMPARE(ctx->localDeclarations().size(), 2); auto dec = ctx->localDeclarations().at(0); QEXPECT_FAIL("", "Skipping of function bodies was disabled for now", Continue); QVERIFY(dec->uses().isEmpty()); QVERIFY(!ctx->ast()); } auto backgroundParser = ICore::self()->languageController()->backgroundParser(); QVERIFY(!backgroundParser->isQueued(file.url())); auto doc = ICore::self()->documentController()->openDocument(file.url().toUrl()); QVERIFY(doc); QVERIFY(backgroundParser->isQueued(file.url())); QSignalSpy spy(backgroundParser, &BackgroundParser::parseJobFinished); spy.wait(); doc->close(KDevelop::IDocument::Discard); { DUChainReadLocker lock; auto ctx = file.topContext(); QCOMPARE(ctx->features() & TopDUContext::AllDeclarationsContextsAndUses, static_cast(TopDUContext::AllDeclarationsContextsAndUses)); QVERIFY(ctx->topContext()->ast()); } } void TestDUChain::testReparseInclude() { TestFile header("int foo() { return 42; }\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); // Use TopDUContext::AST to imitate that document is opened in the editor, so that ClangParseJob can store translation unit, that'll be used for reparsing. impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsAndContexts|TopDUContext::AST)); QVERIFY(impl.waitForParsed(5000)); { DUChainReadLocker lock; auto implCtx = impl.topContext(); QVERIFY(implCtx); QCOMPARE(implCtx->importedParentContexts().size(), 1); } impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(impl.waitForParsed(5000)); DUChainReadLocker lock; auto implCtx = impl.topContext(); QVERIFY(implCtx); QCOMPARE(implCtx->localDeclarations().size(), 1); QCOMPARE(implCtx->importedParentContexts().size(), 1); auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(!headerCtx->parsingEnvironmentFile()->needsUpdate()); QCOMPARE(headerCtx->localDeclarations().size(), 1); QVERIFY(implCtx->imports(headerCtx, CursorInRevision(0, 10))); Declaration* foo = headerCtx->localDeclarations().first(); QCOMPARE(foo->uses().size(), 1); QCOMPARE(foo->uses().begin().key(), impl.url()); QCOMPARE(foo->uses().begin()->size(), 1); QCOMPARE(foo->uses().begin()->first(), RangeInRevision(1, 20, 1, 23)); QCOMPARE(DUChain::self()->allEnvironmentFiles(header.url()).size(), 1); QCOMPARE(DUChain::self()->allEnvironmentFiles(impl.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(header.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(impl.url()).size(), 1); } void TestDUChain::testReparseChangeEnvironment() { TestFile header("int foo() { return 42; }\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n" "int main() { return foo(); }", "cpp", &header); uint hashes[3] = {0, 0, 0}; for (int i = 0; i < 3; ++i) { impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(impl.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(impl.topContext()); auto env = dynamic_cast(impl.topContext()->parsingEnvironmentFile().data()); QVERIFY(env); QCOMPARE(env->environmentQuality(), ClangParsingEnvironment::Source); hashes[i] = env->environmentHash(); QVERIFY(hashes[i]); // we should never end up with multiple env files or chains in memory for these files QCOMPARE(DUChain::self()->allEnvironmentFiles(impl.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(impl.url()).size(), 1); QCOMPARE(DUChain::self()->allEnvironmentFiles(header.url()).size(), 1); QCOMPARE(DUChain::self()->chainsForDocument(header.url()).size(), 1); } // in every run, we expect the environment to have changed for (int j = 0; j < i; ++j) { QVERIFY(hashes[i] != hashes[j]); } if (i == 0) { // 1) change defines m_provider->defines.insert("foooooooo", "baaar!"); } else if (i == 1) { // 2) change includes m_provider->includes.append(Path("/foo/bar/asdf/lalala")); } // 3) stop } } void TestDUChain::testMacrosRanges() { TestFile file("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x);", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,19)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,11)); } void TestDUChain::testMultiLineMacroRanges() { TestFile file("#define FUNC_MACROS(x) struct str##x{};\nFUNC_MACROS(x\n);", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 2); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,19)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,11)); } void TestDUChain::testNestedMacroRanges() { TestFile file("#define INNER int var; var = 0;\n#define MACRO() INNER\nint main(){MACRO(\n);}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 3); auto main = file.topContext()->localDeclarations()[2]; QVERIFY(main); auto mainCtx = main->internalContext()->childContexts().first(); QVERIFY(mainCtx); QCOMPARE(mainCtx->localDeclarations().size(), 1); auto var = mainCtx->localDeclarations().first(); QVERIFY(var); QCOMPARE(var->range(), RangeInRevision(2,11,2,11)); QCOMPARE(var->uses().size(), 1); QCOMPARE(var->uses().begin()->first(), RangeInRevision(2,11,2,11)); } void TestDUChain::testNestedImports() { TestFile B("#pragma once\nint B();\n", "h"); TestFile C("#pragma once\n#include \"" + B.url().byteArray() + "\"\nint C();\n", "h"); TestFile A("#include \"" + B.url().byteArray() + "\"\n" + "#include \"" + C.url().byteArray() + "\"\nint A();\n", "cpp"); A.parse(); QVERIFY(A.waitForParsed(5000)); DUChainReadLocker lock; auto BCtx = DUChain::self()->chainForDocument(B.url().toUrl()); QVERIFY(BCtx); QVERIFY(BCtx->importedParentContexts().isEmpty()); auto CCtx = DUChain::self()->chainForDocument(C.url().toUrl()); QVERIFY(CCtx); QCOMPARE(CCtx->importedParentContexts().size(), 1); QVERIFY(CCtx->imports(BCtx, CursorInRevision(1, 10))); auto ACtx = A.topContext(); QVERIFY(ACtx); QCOMPARE(ACtx->importedParentContexts().size(), 2); QVERIFY(ACtx->imports(BCtx, CursorInRevision(0, 10))); QVERIFY(ACtx->imports(CCtx, CursorInRevision(1, 10))); } void TestDUChain::testEnvironmentWithDifferentOrderOfElements() { TestFile file("int main();\n", "cpp"); m_provider->includes.clear(); m_provider->includes.append(Path("/path1")); m_provider->includes.append(Path("/path2")); m_provider->defines.clear(); m_provider->defines.insert("key1", "value1"); m_provider->defines.insert("key2", "value2"); m_provider->defines.insert("key3", "value3"); uint previousHash = 0; for (int i: {0, 1, 2, 3}) { file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); auto env = dynamic_cast(file.topContext()->parsingEnvironmentFile().data()); QVERIFY(env); QCOMPARE(env->environmentQuality(), ClangParsingEnvironment::Source); if (previousHash) { if (i == 3) { QVERIFY(previousHash != env->environmentHash()); } else { QCOMPARE(previousHash, env->environmentHash()); } } previousHash = env->environmentHash(); QVERIFY(previousHash); } if (i == 0) { //Change order of defines. Hash of the environment should stay the same. m_provider->defines.clear(); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key1", "value1"); m_provider->defines.insert("key2", "value2"); } else if (i == 1) { //Add the same macros twice. Hash of the environment should stay the same. m_provider->defines.clear(); m_provider->defines.insert("key2", "value2"); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key3", "value3"); m_provider->defines.insert("key1", "value1"); } else if (i == 2) { //OTOH order of includes should change hash of the environment. m_provider->includes.clear(); m_provider->includes.append(Path("/path2")); m_provider->includes.append(Path("/path1")); } } } void TestDUChain::testReparseMacro() { TestFile file("#define DECLARE(a) typedef struct a##_ {} *a;\nDECLARE(D);\nD d;", "cpp"); file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST)); QVERIFY(file.waitForParsed(5000)); { DUChainReadLocker lock; QVERIFY(file.topContext()); } file.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate)); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 5); auto macroDefinition = file.topContext()->localDeclarations()[0]; QVERIFY(macroDefinition); QCOMPARE(macroDefinition->range(), RangeInRevision(0,8,0,15)); QCOMPARE(macroDefinition->uses().size(), 1); QCOMPARE(macroDefinition->uses().begin()->first(), RangeInRevision(1,0,1,7)); auto structDeclaration = file.topContext()->localDeclarations()[1]; QVERIFY(structDeclaration); QCOMPARE(structDeclaration->range(), RangeInRevision(1,0,1,0)); auto structTypedef = file.topContext()->localDeclarations()[3]; QVERIFY(structTypedef); QCOMPARE(structTypedef->range(), RangeInRevision(1,8,1,9)); QCOMPARE(structTypedef->uses().size(), 1); QCOMPARE(structTypedef->uses().begin()->first(), RangeInRevision(2,0,2,1)); } void TestDUChain::testGotoStatement() { TestFile file("int main() {\ngoto label;\ngoto label;\nlabel: return 0;}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 1); auto main = file.topContext()->localDeclarations()[0]; QVERIFY(main); auto mainCtx = main->internalContext()->childContexts().first(); QVERIFY(mainCtx); QCOMPARE(mainCtx->localDeclarations().size(), 1); auto label = mainCtx->localDeclarations().first(); QVERIFY(label); QCOMPARE(label->range(), RangeInRevision(3,0,3,5)); QCOMPARE(label->uses().size(), 1); QCOMPARE(label->uses().begin()->first(), RangeInRevision(1,5,1,10)); QCOMPARE(label->uses().begin()->last(), RangeInRevision(2,5,2,10)); } void TestDUChain::testRangesOfOperatorsInsideMacro() { TestFile file("class Test{public: Test& operator++(int);};\n#define MACRO(var) var++;\nint main(){\nTest tst; MACRO(tst)}", "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QVERIFY(file.topContext()); QCOMPARE(file.topContext()->localDeclarations().size(), 3); auto testClass = file.topContext()->localDeclarations()[0]; QVERIFY(testClass); auto operatorPlusPlus = testClass->internalContext()->localDeclarations().first(); QVERIFY(operatorPlusPlus); QCOMPARE(operatorPlusPlus->uses().size(), 1); QCOMPARE(operatorPlusPlus->uses().begin()->first(), RangeInRevision(3,10,3,10)); } void TestDUChain::testUsesCreatedForDeclarations() { auto code = R"(template void functionTemplate(T); template void functionTemplate(U) {} namespace NS { class Class{}; } using NS::Class; Class function(); NS::Class function() { return {}; } int main () { functionTemplate(int()); function(); } )"; TestFile file(code, "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; QVERIFY(file.topContext()); auto functionTemplate = file.topContext()->findDeclarations(QualifiedIdentifier("functionTemplate")); QVERIFY(!functionTemplate.isEmpty()); auto functionTemplateDeclaration = DUChainUtils::declarationForDefinition(functionTemplate.first()); QVERIFY(!functionTemplateDeclaration->isDefinition()); #if CINDEX_VERSION_MINOR < 29 QEXPECT_FAIL("", "No API in LibClang to determine function template type", Continue); #endif QCOMPARE(functionTemplateDeclaration->uses().count(), 1); auto function = file.topContext()->findDeclarations(QualifiedIdentifier("function")); QVERIFY(!function.isEmpty()); auto functionDeclaration = DUChainUtils::declarationForDefinition(function.first()); QVERIFY(!functionDeclaration->isDefinition()); QCOMPARE(functionDeclaration->uses().count(), 1); } void TestDUChain::testReparseIncludeGuard() { TestFile header("#ifndef GUARD\n#define GUARD\nint something;\n#endif\n", "h"); TestFile impl("#include \"" + header.url().byteArray() + "\"\n", "cpp", &header); impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST )); { DUChainReadLocker lock; QCOMPARE(static_cast(impl.topContext()-> importedParentContexts().first().context(impl.topContext()))->problems().size(), 0); } impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); { DUChainReadLocker lock; QCOMPARE(static_cast(impl.topContext()-> importedParentContexts().first().context(impl.topContext()))->problems().size(), 0); } } void TestDUChain::testExternC() { auto code = R"(extern "C" { void foo(); })"; TestFile file(code, "cpp"); file.parse(TopDUContext::AllDeclarationsContextsAndUses); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(!top->findDeclarations(QualifiedIdentifier("foo")).isEmpty()); } void TestDUChain::testReparseUnchanged_data() { QTest::addColumn("headerCode"); QTest::addColumn("implCode"); QTest::newRow("include-guards") << R"( #ifndef GUARD #define GUARD int something; #endif )" << R"( #include "%1" )"; QTest::newRow("template-default-parameters") << R"( #ifndef TEST_H #define TEST_H template class dummy; template class dummy { int field[T]; }; #endif )" << R"( #include "%1" int main(int, char **) { dummy<> x; (void)x; } )"; } void TestDUChain::testReparseUnchanged() { QFETCH(QString, headerCode); QFETCH(QString, implCode); TestFile header(headerCode, "h"); TestFile impl(implCode.arg(header.url().str()), "cpp", &header); auto checkProblems = [&] (bool reparsed) { DUChainReadLocker lock; auto headerCtx = DUChain::self()->chainForDocument(header.url()); QVERIFY(headerCtx); QVERIFY(headerCtx->problems().isEmpty()); auto implCtx = DUChain::self()->chainForDocument(impl.url()); QVERIFY(implCtx); - if (reparsed && CINDEX_VERSION_MINOR > 29) { + if (reparsed && CINDEX_VERSION_MINOR > 29 && CINDEX_VERSION_MINOR < 33) { QEXPECT_FAIL("template-default-parameters", "the precompiled preamble messes the default template parameters up in clang 3.7", Continue); } QVERIFY(implCtx->problems().isEmpty()); }; impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::AST )); checkProblems(false); impl.parseAndWait(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses | TopDUContext::ForceUpdateRecursive)); checkProblems(true); } void TestDUChain::testTypeAliasTemplate() { TestFile file("template using TypeAliasTemplate = T;", "cpp"); QVERIFY(file.parseAndWait()); DUChainReadLocker lock; QVERIFY(file.topContext()); auto templateAlias = file.topContext()->localDeclarations().last(); QVERIFY(templateAlias); #if CINDEX_VERSION_MINOR < 31 QEXPECT_FAIL("", "TypeAliasTemplate is not exposed via LibClang", Abort); #endif QVERIFY(templateAlias->abstractType()); QCOMPARE(templateAlias->abstractType()->toString(), QStringLiteral("TypeAliasTemplate")); } static bool containsErrors(const QList& problems) { auto it = std::find_if(problems.begin(), problems.end(), [] (const Problem::Ptr& problem) { return problem->severity() == Problem::Error; }); return it != problems.end(); } static bool expectedXmmintrinErrors(const QList& problems) { foreach (const auto& problem, problems) { if (problem->severity() == Problem::Error && !problem->description().contains("Cannot initialize a parameter of type")) { return false; } } return true; } static void verifyNoErrors(TopDUContext* top, QSet& checked) { const auto problems = top->problems(); if (containsErrors(problems)) { qDebug() << top->url() << top->problems(); if (top->url().str().endsWith("xmmintrin.h") && expectedXmmintrinErrors(problems)) { QEXPECT_FAIL("", "there are still some errors in xmmintrin.h b/c some clang provided intrinsincs are more strict than the GCC ones.", Continue); QVERIFY(false); } else { QFAIL("parse error detected"); } } const auto imports = top->importedParentContexts(); foreach (const auto& import, imports) { auto ctx = import.context(top); QVERIFY(ctx); auto importedTop = ctx->topContext(); if (checked.contains(importedTop)) { continue; } checked.insert(importedTop); verifyNoErrors(importedTop, checked); } } void TestDUChain::testGccCompatibility() { // TODO: make it easier to change the compiler provider for testing purposes QTemporaryDir dir; auto project = new TestProject(Path(dir.path()), this); auto definesAndIncludesConfig = project->projectConfiguration()->group("CustomDefinesAndIncludes"); auto pathConfig = definesAndIncludesConfig.group("ProjectPath0"); pathConfig.writeEntry("Path", "."); pathConfig.group("Compiler").writeEntry("Name", "GCC"); m_projectController->addProject(project); { TestFile file(R"( #include int main() { return 0; } )", "c", project, dir.path()); file.parse(); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; QSet checked; verifyNoErrors(file.topContext(), checked); } m_projectController->closeAllProjects(); } void TestDUChain::testQtIntegration() { // TODO: make it easier to change the compiler provider for testing purposes QTemporaryDir includeDir; { QDir dir(includeDir.path()); dir.mkdir("QtCore"); // create the file but don't put anything in it QFile header(includeDir.path() + "/QtCore/qobjectdefs.h"); QVERIFY(header.open(QIODevice::WriteOnly | QIODevice::Text)); } QTemporaryDir dir; auto project = new TestProject(Path(dir.path()), this); auto definesAndIncludesConfig = project->projectConfiguration()->group("CustomDefinesAndIncludes"); auto pathConfig = definesAndIncludesConfig.group("ProjectPath0"); pathConfig.writeEntry("Path", "."); pathConfig.group("Includes").writeEntry("1", QString(includeDir.path() + "/QtCore")); m_projectController->addProject(project); { TestFile file(R"( #define slots #define signals #define Q_SLOTS #define Q_SIGNALS #include struct MyObject { public: void other1(); public slots: void slot1(); signals: void signal1(); private Q_SLOTS: void slot2(); Q_SIGNALS: void signal2(); public: void other2(); }; )", "cpp", project, dir.path()); file.parse(); QVERIFY(file.waitForParsed(5000)); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(top->problems().isEmpty()); const auto methods = top->childContexts().last()->localDeclarations(); QCOMPARE(methods.size(), 6); foreach(auto method, methods) { auto classFunction = dynamic_cast(method); QVERIFY(classFunction); auto id = classFunction->identifier().toString(); QCOMPARE(classFunction->isSignal(), id.startsWith(QLatin1String("signal"))); QCOMPARE(classFunction->isSlot(), id.startsWith(QLatin1String("slot"))); } } m_projectController->closeAllProjects(); }