diff --git a/codecompletion/items/functiondeclaration.cpp b/codecompletion/items/functiondeclaration.cpp index 2abccfb9..8ee412d9 100644 --- a/codecompletion/items/functiondeclaration.cpp +++ b/codecompletion/items/functiondeclaration.cpp @@ -1,186 +1,185 @@ /***************************************************************************** * Copyright (c) 2011 Sven Brauch * * * * 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, see . * ***************************************************************************** */ #include "functiondeclaration.h" #include #include #include #include #include -#include #include #include #include #include "duchain/navigation/navigationwidget.h" #include "codecompletion/helpers.h" #include "declaration.h" #include "declarations/functiondeclaration.h" #include "duchain/helpers.h" #include #include using namespace KDevelop; using namespace KTextEditor; namespace Python { FunctionDeclarationCompletionItem::FunctionDeclarationCompletionItem(DeclarationPointer decl, CodeCompletionContext::Ptr context) : PythonDeclarationCompletionItem(decl, context) , m_atArgument(-1) , m_depth(0) , m_doNotCall(false) { } int FunctionDeclarationCompletionItem::atArgument() const { return m_atArgument; } void FunctionDeclarationCompletionItem::setDepth(int d) { m_depth = d; } void FunctionDeclarationCompletionItem::setAtArgument(int d) { m_atArgument = d; } int FunctionDeclarationCompletionItem::argumentHintDepth() const { return m_depth; } QVariant FunctionDeclarationCompletionItem::data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { DUChainReadLocker lock; FunctionDeclaration* dec = dynamic_cast(m_declaration.data()); switch ( role ) { case Qt::DisplayRole: { if ( ! dec ) { break; // use the default } if ( index.column() == KDevelop::CodeCompletionModel::Arguments ) { if (FunctionType::Ptr functionType = dec->type()) { QString ret; createArgumentList(dec, ret, nullptr, 0, false); return ret; } } if ( index.column() == KDevelop::CodeCompletionModel::Prefix ) { FunctionType::Ptr type = dec->type(); if ( type && type->returnType() ) { return i18n("function") + " -> " + type->returnType()->toString(); } } break; } case KDevelop::CodeCompletionModel::HighlightingMethod: { if ( index.column() == KDevelop::CodeCompletionModel::Arguments ) return QVariant(KDevelop::CodeCompletionModel::CustomHighlighting); break; } case KDevelop::CodeCompletionModel::CustomHighlight: { if ( index.column() == KDevelop::CodeCompletionModel::Arguments ) { if ( ! dec ) return QVariant(); QString ret; QList highlight; if ( atArgument() ) { createArgumentList(dec, ret, &highlight, atArgument(), false); } else { createArgumentList(dec, ret, nullptr, false); } return QVariant(highlight); } break; } case KDevelop::CodeCompletionModel::MatchQuality: { if ( m_typeHint == PythonCodeCompletionContext::IterableRequested && dec && dec->type() && dynamic_cast(dec->type()->returnType().data()) ) { return 2 + PythonDeclarationCompletionItem::data(index, role, model).toInt(); } return PythonDeclarationCompletionItem::data(index, role, model); } } return Python::PythonDeclarationCompletionItem::data(index, role, model); } void FunctionDeclarationCompletionItem::setDoNotCall(bool doNotCall) { m_doNotCall = doNotCall; } void FunctionDeclarationCompletionItem::executed(KTextEditor::View* view, const KTextEditor::Range& word) { qCDebug(KDEV_PYTHON_CODECOMPLETION) << "FunctionDeclarationCompletionItem executed"; KTextEditor::Document* document = view->document(); auto resolvedDecl = Helper::resolveAliasDeclaration(declaration().data()); DUChainReadLocker lock; auto functionDecl = Helper::functionForCalled(resolvedDecl).declaration; lock.unlock(); if ( ! functionDecl && (! resolvedDecl || ! resolvedDecl->abstractType() || resolvedDecl->abstractType()->whichType() != AbstractType::TypeStructure) ) { qCritical(KDEV_PYTHON_CODECOMPLETION) << "ERROR: could not get declaration data, not executing completion item!"; return; } QString suffix = "()"; KTextEditor::Range checkPrefix(word.start().line(), 0, word.start().line(), word.start().column()); KTextEditor::Range checkSuffix(word.end().line(), word.end().column(), word.end().line(), document->lineLength(word.end().line())); if ( m_doNotCall || document->text(checkSuffix).trimmed().startsWith('(') || document->text(checkPrefix).trimmed().endsWith('@') || (functionDecl && functionDecl->isProperty()) ) { // don't insert brackets if they're already there, // the item is a decorator, or if it's an import item. suffix.clear(); } // place cursor behind bracktes by default int skip = 2; if ( functionDecl ) { bool needsArguments = false; int argumentCount = functionDecl->type()->arguments().length(); if ( functionDecl->context()->type() == KDevelop::DUContext::Class ) { // it's a member function, so it has the implicit self // TODO static methods needsArguments = argumentCount > 1; } else { // it's a free function needsArguments = argumentCount > 0; } if ( needsArguments ) { // place cursor in brackets if there's parameters skip = 1; } } document->replaceText(word, declaration()->identifier().toString() + suffix); view->setCursorPosition( Cursor(word.end().line(), word.end().column() + skip) ); } FunctionDeclarationCompletionItem::~FunctionDeclarationCompletionItem() { } } diff --git a/codecompletion/items/implementfunction.cpp b/codecompletion/items/implementfunction.cpp index 0fb14c6d..f6c82dde 100644 --- a/codecompletion/items/implementfunction.cpp +++ b/codecompletion/items/implementfunction.cpp @@ -1,87 +1,86 @@ /***************************************************************************** * Copyright (c) 2011 Sven Brauch * * * * 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, see . * ***************************************************************************** */ #include "implementfunction.h" #include #include #include #include - -#include +#include #include #include #include #include using namespace KDevelop; using namespace KTextEditor; namespace Python { ImplementFunctionCompletionItem::ImplementFunctionCompletionItem(const QString& name, const QStringList& arguments, const QString& previousIndent) : m_arguments(arguments), m_name(name), m_previousIndent(previousIndent) { } void ImplementFunctionCompletionItem::execute(KTextEditor::View* view, const KTextEditor::Range& word) { auto document = view->document(); const QString finalText = m_name + "(" + m_arguments.join(", ") + "):"; document->replaceText(word, finalText); // 4 spaces is indentation for python. everyone does it like this. you must, too. // TODO use kate settings document->insertLine(word.start().line() + 1, m_previousIndent + " "); - if ( View* view = static_cast(ICore::self()->partController())->activeView() ) { + if ( View* view = ICore::self()->documentController()->activeTextDocumentView() ) { view->setCursorPosition(Cursor(word.end().line() + 1, m_previousIndent.length() + 4)); } } QVariant ImplementFunctionCompletionItem::data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { switch ( role ) { case KDevelop::CodeCompletionModel::MatchQuality: { return QVariant(m_name.startsWith("__") ? 0 : 10); } case KDevelop::CodeCompletionModel::BestMatchesCount: { return QVariant(5); } case Qt::DisplayRole: switch ( index.column() ) { case KDevelop::CodeCompletionModel::Name: return m_name + "(" + m_arguments.join(", ") + ")"; case KDevelop::CodeCompletionModel::Postfix: return ""; case KDevelop::CodeCompletionModel::Prefix: return "Override method"; default: return ""; } case Qt::DecorationRole: if( index.column() == KDevelop::CodeCompletionModel::Icon ) { KDevelop::CodeCompletionModel::CompletionProperties p(KDevelop::CodeCompletionModel::Function); return DUChainUtils::iconForProperties(p); } // Fall through default: return CompletionTreeItem::data(index, role, model); } } } // namespace Python diff --git a/duchain/helpers.cpp b/duchain/helpers.cpp index 7c931604..9f7e602b 100644 --- a/duchain/helpers.cpp +++ b/duchain/helpers.cpp @@ -1,560 +1,557 @@ /***************************************************************************** * This file is part of KDevelop * * Copyright 2011-2013 Sven Brauch * * * * 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, see . * ***************************************************************************** */ #include "helpers.h" #include #include #include #include #include #include "duchaindebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include -#include - #include #include #include "ast.h" #include "types/hintedtype.h" #include "types/unsuretype.h" #include "types/indexedcontainer.h" #include "kdevpythonversion.h" #include "expressionvisitor.h" using namespace KDevelop; namespace Python { QMap> Helper::cachedCustomIncludes; QMap> Helper::cachedSearchPaths; QVector Helper::projectSearchPaths; QStringList Helper::dataDirs; IndexedString Helper::documentationFile; DUChainPointer Helper::documentationFileContext = DUChainPointer(nullptr); QStringList Helper::correctionFileDirs; QString Helper::localCorrectionFileDir; QMutex Helper::cacheMutex; QMutex Helper::projectPathLock; void Helper::scheduleDependency(const IndexedString& dependency, int betterThanPriority) { BackgroundParser* bgparser = KDevelop::ICore::self()->languageController()->backgroundParser(); bool needsReschedule = true; if ( bgparser->isQueued(dependency) ) { const auto priority= bgparser->priorityForDocument(dependency); if ( priority > betterThanPriority - 1 ) { bgparser->removeDocument(dependency); } else { needsReschedule = false; } } if ( needsReschedule ) { bgparser->addDocument(dependency, TopDUContext::ForceUpdate, betterThanPriority - 1, nullptr, ParseJob::FullSequentialProcessing); } } IndexedDeclaration Helper::declarationUnderCursor(bool allowUse) { KDevelop::IDocument* doc = ICore::self()->documentController()->activeDocument(); - const auto view = static_cast(ICore::self()->partController())->activeView(); + const auto view = ICore::self()->documentController()->activeTextDocumentView(); if ( doc && doc->textDocument() && view ) { DUChainReadLocker lock; const auto cursor = view->cursorPosition(); if ( allowUse ) { return IndexedDeclaration(DUChainUtils::itemUnderCursor(doc->url(), cursor).declaration); } else { return DUChainUtils::declarationInLine(cursor, DUChainUtils::standardContextForUrl(doc->url())); } } return KDevelop::IndexedDeclaration(); } Declaration* Helper::accessAttribute(const AbstractType::Ptr accessed, const IndexedIdentifier& attribute, const TopDUContext* topContext) { if ( ! accessed ) { return nullptr; } // if the type is unsure, search all the possibilities (but return the first match) auto structureTypes = Helper::filterType(accessed, [](AbstractType::Ptr toFilter) { auto type = Helper::resolveAliasType(toFilter); return type && type->whichType() == AbstractType::TypeStructure; }, [](AbstractType::Ptr toMap) { return StructureType::Ptr::staticCast(Helper::resolveAliasType(toMap)); } ); auto docFileContext = Helper::getDocumentationFileContext(); for ( const auto& type: structureTypes ) { auto searchContexts = Helper::internalContextsForClass(type, topContext); for ( const auto ctx: searchContexts ) { auto found = ctx->findDeclarations(attribute, CursorInRevision::invalid(), topContext, DUContext::DontSearchInParent); if ( !found.isEmpty() && ( found.last()->topContext() != docFileContext || ctx->topContext() == docFileContext) ) { // never consider decls from the builtins return found.last(); } } } return nullptr; } AbstractType::Ptr Helper::resolveAliasType(const AbstractType::Ptr eventualAlias) { return TypeUtils::resolveAliasType(eventualAlias); } AbstractType::Ptr Helper::extractTypeHints(AbstractType::Ptr type) { return Helper::foldTypes(Helper::filterType(type, [](AbstractType::Ptr t) -> bool { auto hint = t.cast(); return !hint || hint->isValid(); })); } Helper::FuncInfo Helper::functionForCalled(Declaration* called, bool isAlias) { if ( ! called ) { return { nullptr, false }; } else if ( called->isFunctionDeclaration() ) { return { static_cast( called ), false }; } // If we're calling a type object (isAlias == true), look for a constructor. static const IndexedIdentifier initId(KDevelop::Identifier("__init__")); // Otherwise look for a `__call__()` method. static const IndexedIdentifier callId(KDevelop::Identifier("__call__")); auto attr = accessAttribute(called->abstractType(), (isAlias ? initId : callId), called->topContext()); return { dynamic_cast(attr), isAlias }; } Declaration* Helper::declarationForName(const QString& name, const CursorInRevision& location, KDevelop::DUChainPointer context) { DUChainReadLocker lock(DUChain::lock()); auto identifier = KDevelop::Identifier(name); auto localDeclarations = context->findLocalDeclarations(identifier, location, nullptr, AbstractType::Ptr(), DUContext::DontResolveAliases); if ( !localDeclarations.isEmpty() ) { return localDeclarations.last(); } QList declarations; const DUContext* currentContext = context.data(); bool findInNext = true, findBeyondUse = false; do { if (findInNext) { CursorInRevision findUntil = findBeyondUse ? currentContext->topContext()->range().end : location; declarations = currentContext->findDeclarations(identifier, findUntil); for (Declaration* declaration: declarations) { if (declaration->context()->type() != DUContext::Class || (currentContext->type() == DUContext::Function && declaration->context() == currentContext->parentContext())) { // Declarations from class decls must be referenced through `self.`, except // in their local scope (handled above) or when used as default arguments for methods of the same class. // Otherwise, we're done! return declaration; } } if (!declarations.isEmpty()) { // If we found declarations but rejected all of them (i.e. didn't return), we need to keep searching. findInNext = true; declarations.clear(); } } if (!findBeyondUse && currentContext->owner() && currentContext->owner()->isFunctionDeclaration()) { // Names in the body may be defined after the function definition, before the function is called. // Note: only the parameter list has type DUContext::Function, so we have to do this instead. findBeyondUse = findInNext = true; } } while ((currentContext = currentContext->parentContext())); return nullptr; } Declaration* Helper::declarationForName(const Python::NameAst* name, CursorInRevision location, KDevelop::DUChainPointer context) { const Ast* checkNode = name; while ((checkNode = checkNode->parent)) { switch (checkNode->astType) { default: continue; case Ast::ListComprehensionAstType: case Ast::SetComprehensionAstType: case Ast::DictionaryComprehensionAstType: case Ast::GeneratorExpressionAstType: // Variables in comprehensions are used before their definition. `[foo for foo in bar]` auto cmpEnd = CursorInRevision(checkNode->endLine, checkNode->endCol); if (cmpEnd > location) { location = cmpEnd; } } } return declarationForName(name->identifier->value, location, context); } QVector Helper::internalContextsForClass(const StructureType::Ptr classType, const TopDUContext* context, ContextSearchFlags flags, int depth) { QVector searchContexts; if ( ! classType ) { return searchContexts; } if ( auto c = classType->internalContext(context) ) { searchContexts << c; } auto decl = Helper::resolveAliasDeclaration(classType->declaration(context)); if ( auto classDecl = dynamic_cast(decl) ) { FOREACH_FUNCTION ( const auto& base, classDecl->baseClasses ) { if ( flags == PublicOnly && base.access == KDevelop::Declaration::Private ) { continue; } auto baseClassType = base.baseClass.type(); // recursive call, because the base class will have more base classes eventually if ( depth < 10 ) { searchContexts.append(Helper::internalContextsForClass(baseClassType, context, flags, depth + 1)); } } } return searchContexts; } Declaration* Helper::resolveAliasDeclaration(Declaration* decl) { AliasDeclaration* alias = dynamic_cast(decl); if ( alias ) { DUChainReadLocker lock; return alias->aliasedDeclaration().data(); } else return decl; } QStringList Helper::getDataDirs() { if ( Helper::dataDirs.isEmpty() ) { Helper::dataDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "kdevpythonsupport/documentation_files", QStandardPaths::LocateDirectory); } return Helper::dataDirs; } KDevelop::IndexedString Helper::getDocumentationFile() { if ( Helper::documentationFile.isEmpty() ) { auto path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevpythonsupport/documentation_files/builtindocumentation.py"); Helper::documentationFile = IndexedString(path); } return Helper::documentationFile; } ReferencedTopDUContext Helper::getDocumentationFileContext() { if ( Helper::documentationFileContext ) { return ReferencedTopDUContext(Helper::documentationFileContext.data()); } else { DUChainReadLocker lock; auto file = Helper::getDocumentationFile(); ReferencedTopDUContext ctx = ReferencedTopDUContext(DUChain::self()->chainForDocument(file)); Helper::documentationFileContext = DUChainPointer(ctx.data()); return ctx; } } // stolen from KUrl. duh. static QString _relativePath(const QString &base_dir, const QString &path) { QString _base_dir(QDir::cleanPath(base_dir)); QString _path(QDir::cleanPath(path.isEmpty() || QDir::isRelativePath(path) ? _base_dir+QLatin1Char('/')+path : path)); if (_base_dir.isEmpty()) return _path; if (_base_dir[_base_dir.length()-1] != QLatin1Char('/')) _base_dir.append(QLatin1Char('/') ); const QStringList list1 = _base_dir.split(QLatin1Char('/'), QString::SkipEmptyParts); const QStringList list2 = _path.split(QLatin1Char('/'), QString::SkipEmptyParts); // Find where they meet int level = 0; int maxLevel = qMin(list1.count(), list2.count()); while((level < maxLevel) && (list1[level] == list2[level])) level++; QString result; // Need to go down out of the first path to the common branch. for(int i = level; i < list1.count(); i++) result.append(QLatin1String("../")); // Now up up from the common branch to the second path. for(int i = level; i < list2.count(); i++) result.append(list2[i]).append(QLatin1Char('/')); if ((level < list2.count()) && (path[path.length()-1] != QLatin1Char('/'))) result.truncate(result.length()-1); return result; } QUrl Helper::getCorrectionFile(const QUrl& document) { if ( Helper::correctionFileDirs.isEmpty() ) { Helper::correctionFileDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "kdevpythonsupport/correction_files/", QStandardPaths::LocateDirectory); } foreach (QString correctionFileDir, correctionFileDirs) { foreach ( const QUrl& basePath, Helper::getSearchPaths(QUrl()) ) { if ( ! basePath.isParentOf(document) ) { continue; } auto base = basePath.path(); auto doc = document.path(); auto relative = _relativePath(base, doc); auto fullPath = correctionFileDir + "/" + relative; if ( QFile::exists(fullPath) ) { return QUrl::fromLocalFile(fullPath).adjusted(QUrl::NormalizePathSegments); } } } return {}; } QUrl Helper::getLocalCorrectionFile(const QUrl& document) { if ( Helper::localCorrectionFileDir.isNull() ) { Helper::localCorrectionFileDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "kdevpythonsupport/correction_files/"; } auto absolutePath = QUrl(); foreach ( const auto& basePath, Helper::getSearchPaths(QUrl()) ) { if ( ! basePath.isParentOf(document) ) { continue; } auto path = QDir(basePath.path()).relativeFilePath(document.path()); absolutePath = QUrl::fromLocalFile(Helper::localCorrectionFileDir + path); break; } return absolutePath; } QString Helper::getPythonExecutablePath(IProject* project) { if ( project ) { auto interpreter = project->projectConfiguration()->group("pythonsupport").readEntry("interpreter"); if ( !interpreter.isEmpty() ) { // we have a user-configured interpreter, try using it QFile f(interpreter); if ( f.exists() ) { return interpreter; } qCWarning(KDEV_PYTHON_DUCHAIN) << "Custom python interpreter" << interpreter << "configured for project" << project->name() << "is invalid, using default"; } } // Find python 3 (https://www.python.org/dev/peps/pep-0394/) auto result = QStandardPaths::findExecutable("python" PYTHON_VERSION_STR); if ( ! result.isEmpty() ) { return result; } result = QStandardPaths::findExecutable("python" PYTHON_VERSION_MAJOR_STR); if ( ! result.isEmpty() ) { return result; } result = QStandardPaths::findExecutable("python"); if ( ! result.isEmpty() ) { return result; } #ifdef Q_OS_WIN QStringList extraPaths; // Check for default CPython installation path, because the // installer does not add the path to $PATH. QStringList keys = { "HKEY_LOCAL_MACHINE\\Software\\Python\\PythonCore\\PYTHON_VERSION\\InstallPath", "HKEY_LOCAL_MACHINE\\Software\\Python\\PythonCore\\PYTHON_VERSION-32\\InstallPath", "HKEY_CURRENT_USER\\Software\\Python\\PythonCore\\PYTHON_VERSION\\InstallPath", "HKEY_CURRENT_USER\\Software\\Python\\PythonCore\\PYTHON_VERSION-32\\InstallPath" }; auto version = QString(PYTHON_VERSION_STR); foreach ( QString key, keys ) { key.replace("PYTHON_VERSION", version); QSettings base(key.left(key.indexOf("Python")), QSettings::NativeFormat); if ( ! base.childGroups().contains("Python") ) { continue; } QSettings keySettings(key, QSettings::NativeFormat); auto path = keySettings.value("Default").toString(); if ( ! path.isEmpty() ) { extraPaths << path; break; } } result = QStandardPaths::findExecutable("python", extraPaths); if ( ! result.isEmpty() ) { return result; } #endif // fallback return PYTHON_EXECUTABLE; } QVector Helper::getSearchPaths(const QUrl& workingOnDocument) { QMutexLocker lock(&Helper::cacheMutex); QVector searchPaths; // search in the projects, as they're packages and likely to be installed or added to PYTHONPATH later // and also add custom include paths that are defined in the projects auto project = ICore::self()->projectController()->findProjectForUrl(workingOnDocument); { QMutexLocker lock(&Helper::projectPathLock); searchPaths << Helper::projectSearchPaths; searchPaths << Helper::cachedCustomIncludes.value(project); } foreach ( const QString& path, getDataDirs() ) { searchPaths.append(QUrl::fromLocalFile(path)); } if ( !cachedSearchPaths.contains(project) ) { QVector cachedForProject; qCDebug(KDEV_PYTHON_DUCHAIN) << "*** Collecting search paths..."; QStringList getpath; getpath << "-c" << "import sys; sys.stdout.write('$|$'.join(sys.path))"; QProcess python; python.start(getPythonExecutablePath(project), getpath); python.waitForFinished(1000); QString pythonpath = QString::fromUtf8(python.readAllStandardOutput()); if ( ! pythonpath.isEmpty() ) { const auto paths = pythonpath.split("$|$", QString::SkipEmptyParts); foreach ( const QString& path, paths ) { cachedForProject.append(QUrl::fromLocalFile(path)); } } else { qCWarning(KDEV_PYTHON_DUCHAIN) << "Could not get search paths! Defaulting to stupid stuff."; searchPaths.append(QUrl::fromLocalFile("/usr/lib/python" PYTHON_VERSION_STR)); searchPaths.append(QUrl::fromLocalFile("/usr/lib/python" PYTHON_VERSION_STR "/site-packages")); QString path = qgetenv("PYTHONPATH"); QStringList paths = path.split(':'); foreach ( const QString& path, paths ) { cachedForProject.append(QUrl::fromLocalFile(path)); } } qCDebug(KDEV_PYTHON_DUCHAIN) << " *** Done. Got search paths: " << cachedSearchPaths; cachedSearchPaths.insert(project, cachedForProject); } searchPaths.append(cachedSearchPaths.value(project)); auto dir = workingOnDocument.adjusted(QUrl::RemoveFilename); if ( ! dir.isEmpty() ) { // search in the current packages searchPaths.append(dir); } return searchPaths; } bool Helper::isUsefulType(AbstractType::Ptr type) { return TypeUtils::isUsefulType(type); } AbstractType::Ptr Helper::contentOfIterable(const AbstractType::Ptr iterable, const TopDUContext* topContext) { auto types = filterType(iterable, [](AbstractType::Ptr t) { return t->whichType() == AbstractType::TypeStructure; } ); static const IndexedIdentifier iterId(KDevelop::Identifier("__iter__")); static const IndexedIdentifier nextId(KDevelop::Identifier("__next__")); AbstractType::Ptr content(new IntegralType(IntegralType::TypeMixed)); for ( const auto& type: types ) { if ( auto map = type.cast() ) { // Iterating over dicts gets keys, not values content = mergeTypes(content, map->keyType().abstractType()); continue; } else if ( auto list = type.cast() ) { content = mergeTypes(content, list->contentType().abstractType()); continue; } else if ( auto indexed = type.cast() ) { content = mergeTypes(content, indexed->asUnsureType()); continue; } DUChainReadLocker lock; // Content of an iterable object is iterable.__iter__().__next__(). if ( auto iterFunc = dynamic_cast(accessAttribute(type, iterId, topContext)) ) { if ( auto iterator = iterFunc->type()->returnType().cast() ) { if ( auto nextFunc = dynamic_cast(accessAttribute(iterator, nextId, topContext)) ) { content = mergeTypes(content, nextFunc->type()->returnType()); } } } } return content; } AbstractType::Ptr Helper::mergeTypes(AbstractType::Ptr type, const AbstractType::Ptr newType) { UnsureType::Ptr ret; return TypeUtils::mergeTypes(type, newType); } }