diff --git a/duchain/helpers.cpp b/duchain/helpers.cpp index 2d7bf3ae..2ff594b4 100644 --- a/duchain/helpers.cpp +++ b/duchain/helpers.cpp @@ -1,587 +1,596 @@ /***************************************************************************** * 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 "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; QList Helper::cachedSearchPaths; QList Helper::projectSearchPaths; QStringList Helper::dataDirs; QString Helper::documentationFile; DUChainPointer Helper::documentationFileContext = DUChainPointer(0); 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 ParseJob* job = bgparser->parseJobForDocument(dependency); int previousPriority = BackgroundParser::WorstPriority; if ( job ) { previousPriority = job->parsePriority(); } // if it's less important, reschedule it if ( job && previousPriority > betterThanPriority - 1 ) { bgparser->removeDocument(dependency); } else if ( job ) { needsReschedule = false; } } if ( needsReschedule ) { bgparser->addDocument(dependency, TopDUContext::ForceUpdate, betterThanPriority - 1, 0, ParseJob::FullSequentialProcessing); } } IndexedDeclaration Helper::declarationUnderCursor(bool allowUse) { KDevelop::IDocument* doc = ICore::self()->documentController()->activeDocument(); const auto view = static_cast(ICore::self()->partController())->activeView(); if ( doc && doc->textDocument() && view ) { DUChainReadLocker lock; const auto cursor = view->cursorPosition(); if ( allowUse ) { return DUChainUtils::itemUnderCursor(doc->url(), cursor); } else { return DUChainUtils::declarationInLine(cursor, DUChainUtils::standardContextForUrl(doc->url())); } } return KDevelop::IndexedDeclaration(); } Declaration* Helper::accessAttribute(Declaration* accessed, const QString& attribute, const DUContext* current) { if ( ! accessed || ! accessed->abstractType() ) { return 0; } // if the type is unsure, search all the possibilities auto structureTypes = Helper::filterType(accessed->abstractType(), [](AbstractType::Ptr toFilter) { auto type = Helper::resolveAliasType(toFilter); return type && type->whichType() == AbstractType::TypeStructure; } ); for ( auto type: structureTypes ) { QList searchContexts = Helper::internalContextsForClass(type, current->topContext()); for ( DUContext* c: searchContexts ) { auto found = c->findDeclarations(KDevelop::Identifier(attribute), CursorInRevision::invalid(), current->topContext(), DUContext::DontSearchInParent); std::reverse(found.begin(), found.end()); // never consider decls from the builtins if ( ! found.isEmpty() && ( found.first()->topContext() != Helper::getDocumentationFileContext() || c->topContext() == Helper::getDocumentationFileContext() ) ) { return found.first(); } } } return nullptr; } AbstractType::Ptr Helper::resolveAliasType(const AbstractType::Ptr eventualAlias) { return TypeUtils::resolveAliasType(eventualAlias); } AbstractType::Ptr Helper::extractTypeHints(AbstractType::Ptr type, TopDUContext* current) { UnsureType::Ptr result(new UnsureType()); unsigned short maxHints = 7; if ( HintedType::Ptr hinted = type.cast() ) { if ( hinted->isValid(current) && isUsefulType(hinted.cast()) ) { result->addType(type->indexed()); } } else if ( UnsureType::Ptr unsure = type.cast() ) { int len = unsure->typesSize(); for ( int i = 0; i < len && i < maxHints; i++ ) { if ( HintedType::Ptr hinted = unsure->types()[i].abstractType().cast() ) { if ( hinted->isValid(current) ) { qCDebug(KDEV_PYTHON_DUCHAIN) << "Adding type hint (multi): " << hinted->toString(); result->addType(hinted->indexed()); } else { qCDebug(KDEV_PYTHON_DUCHAIN) << "Discarding type hint (multi): " << hinted->toString(); maxHints += 1; } } else { maxHints += 1; } } } else if ( IndexedContainer::Ptr indexed = type.cast() ) { // TODO this is bad because it is slow. Make it faster! // TODO this would really be an important thing, as it will be called quite often. // TODO "how" is simple, just avoid cloning stuff if nothing needs to be changed (i.e. no hints exist). IndexedContainer::Ptr edit = IndexedContainer::Ptr(static_cast(indexed->clone())); for ( int i = 0; i < indexed->typesCount(); i++ ) { if ( HintedType::Ptr p = indexed->typeAt(i).abstractType().cast() ) { if ( ! p->isValid(current) ) { edit->replaceType(i, AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); } } } return edit.cast(); } else if ( auto variable = type.cast() ) { auto edit = ListType::Ptr(static_cast(variable->clone())); UnsureType::Ptr newContentType(new UnsureType()); AbstractType::Ptr oldContentType = edit->contentType().abstractType(); bool isHint = false; if ( oldContentType ) { if ( UnsureType::Ptr oldUnsure = oldContentType.cast() ) { for ( unsigned int i = 0; i < oldUnsure->typesSize(); i++ ) { if ( HintedType::Ptr hinted = oldUnsure->types()[i].abstractType().cast() ) { isHint = true; if ( ! hinted->isValid(current) ) { continue; } } newContentType->addType(oldUnsure->types()[i]); } } else if ( HintedType::Ptr hinted = oldContentType.cast() ) { isHint = true; if ( hinted->isValid(current) ) { newContentType->addType(hinted->indexed()); } } if ( ! isHint ) { return result.cast(); } edit->replaceContentType(newContentType.cast()); // edit->replaceKeyType(variable->keyType().abstractType()); // TODO re-enable? } return edit.cast(); } return result.cast(); } Helper::FuncInfo Helper::functionDeclarationForCalledDeclaration(DeclarationPointer ptr) { if ( ! ptr ) { return FuncInfo(); } bool isConstructor = false; DeclarationPointer calledDeclaration = ptr; if ( ! calledDeclaration->isFunctionDeclaration() ) { // not a function -- try looking for a constructor StructureType::Ptr classType = calledDeclaration->type(); auto contexts = Helper::internalContextsForClass(classType, ptr->topContext()); for ( DUContext* context: contexts ) { static KDevelop::Identifier initIdentifier("__init__"); QList constructors = context->findDeclarations(initIdentifier); if ( ! constructors.isEmpty() ) { calledDeclaration = dynamic_cast(constructors.first()); isConstructor = true; break; } } } FunctionDeclarationPointer lastFunctionDeclaration; if ( calledDeclaration ) { // It was a class -- use the constructor lastFunctionDeclaration = calledDeclaration.dynamicCast(); } else { // Use the original declaration lastFunctionDeclaration = ptr.dynamicCast(); } return QPair(lastFunctionDeclaration, isConstructor); } Declaration* Helper::declarationForName(const QualifiedIdentifier& identifier, const RangeInRevision& nodeRange, KDevelop::DUChainPointer context) { QList declarations; QList localDeclarations; QList importedLocalDeclarations; { DUChainReadLocker lock(DUChain::lock()); if ( context.data() == context->topContext() && nodeRange.isValid() ) { declarations = context->topContext()->findDeclarations(identifier, nodeRange.end); } else { declarations = context->topContext()->findDeclarations(identifier, CursorInRevision::invalid()); } localDeclarations = context->findLocalDeclarations(identifier.last(), nodeRange.end, 0, AbstractType::Ptr(0), DUContext::DontResolveAliases); importedLocalDeclarations = context->findDeclarations(identifier.last(), nodeRange.end); } Declaration* declaration = 0; if ( localDeclarations.length() ) { declaration = localDeclarations.last(); } else if ( importedLocalDeclarations.length() ) { // don't use declarations from class decls, they must be referenced through "self." do { declaration = importedLocalDeclarations.last(); importedLocalDeclarations.pop_back(); if ( !declaration || declaration->context()->type() == DUContext::Class ) { declaration = 0; } if ( importedLocalDeclarations.isEmpty() ) { break; } } while ( ! importedLocalDeclarations.isEmpty() ); } if ( !declaration && declarations.length() ) { declaration = declarations.last(); } return declaration; } QList< DUContext* > Helper::internalContextsForClass(StructureType::Ptr klassType, TopDUContext* context, ContextSearchFlags flags, int depth) { QList searchContexts; if ( ! klassType ) { return searchContexts; } if ( auto c = klassType->internalContext(context) ) { searchContexts << c; } Declaration* decl = Helper::resolveAliasDeclaration(klassType->declaration(context)); ClassDeclaration* klass = dynamic_cast(decl); if ( klass ) { FOREACH_FUNCTION ( const BaseClassInstance& base, klass->baseClasses ) { if ( flags == PublicOnly && base.access == KDevelop::Declaration::Private ) { continue; } StructureType::Ptr 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; } QString Helper::getDocumentationFile() { if ( Helper::documentationFile.isNull() ) { Helper::documentationFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevpythonsupport/documentation_files/builtindocumentation.py"); } return Helper::documentationFile; } ReferencedTopDUContext Helper::getDocumentationFileContext() { if ( Helper::documentationFileContext ) { return ReferencedTopDUContext(Helper::documentationFileContext.data()); } else { DUChainReadLocker lock; auto file = IndexedString(Helper::getDocumentationFile()); ReferencedTopDUContext ctx = ReferencedTopDUContext(DUChain::self()->chainForDocument(file)); Helper::documentationFileContext = DUChainPointer(ctx.data()); return ctx; } return ReferencedTopDUContext(0); // c++... } // 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; // TODO QUrl: cleanPath? if ( QFile::exists(fullPath) ) { return QUrl::fromLocalFile(fullPath); } } } 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 getPythonExecutablePath() { - auto result = QStandardPaths::findExecutable("python"); + // Find python 3 (https://www.python.org/dev/peps/pep-0394/) + auto result = QStandardPaths::findExecutable("python" PYTHON_VERSION_MAJOR "." PYTHON_VERSION_MINOR); + if ( ! result.isEmpty() ) { + return result; + } + result = QStandardPaths::findExecutable("python" PYTHON_VERSION_MAJOR); + 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_MAJOR) + "." + PYTHON_VERSION_MINOR; 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; } QList Helper::getSearchPaths(const QUrl& workingOnDocument) { QMutexLocker lock(&Helper::cacheMutex); QList 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.isEmpty() ) { qCDebug(KDEV_PYTHON_DUCHAIN) << "*** Gathering search paths..."; QStringList getpath; getpath << "-c" << "import sys; sys.stdout.write('$|$'.join(sys.path))"; QProcess python; python.start(getPythonExecutablePath(), getpath); python.waitForFinished(1000); QString pythonpath = QString::fromUtf8(python.readAllStandardOutput()); auto paths = pythonpath.split("$|$"); paths.removeAll(""); if ( ! pythonpath.isEmpty() ) { foreach ( const QString& path, paths ) { cachedSearchPaths.append(QUrl::fromLocalFile(path)); } } else { qCWarning(KDEV_PYTHON_DUCHAIN) << "Could not get search paths! Defaulting to stupid stuff."; searchPaths.append(QUrl::fromLocalFile("/usr/lib/python3.5")); searchPaths.append(QUrl::fromLocalFile("/usr/lib/python3.5/site-packages")); QString path = qgetenv("PYTHONPATH"); QStringList paths = path.split(':'); foreach ( const QString& path, paths ) { cachedSearchPaths.append(QUrl::fromLocalFile(path)); } } qCDebug(KDEV_PYTHON_DUCHAIN) << " *** Done. Got search paths: " << cachedSearchPaths; } else { qCDebug(KDEV_PYTHON_DUCHAIN) << " --- Search paths from cache: " << cachedSearchPaths; } searchPaths.append(cachedSearchPaths); 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) { auto items = filterType(iterable, [](AbstractType::Ptr t) { return ListType::Ptr::dynamicCast(t) || IndexedContainer::Ptr::dynamicCast(t); }, [](AbstractType::Ptr t) { if ( auto variable = ListType::Ptr::dynamicCast(t) ) { return AbstractType::Ptr(variable->contentType().abstractType()); } else { auto indexed = t.cast(); return indexed->asUnsureType(); } } ); if ( items.size() == 1 ) { return items.first(); } auto unsure = AbstractType::Ptr(new UnsureType); for ( auto type: items ) { Helper::mergeTypes(unsure, type); } return unsure; } AbstractType::Ptr Helper::mergeTypes(AbstractType::Ptr type, const AbstractType::Ptr newType) { UnsureType::Ptr ret; return TypeUtils::mergeTypes(type, newType); } }