File indexing completed on 2024-05-05 16:42:28
0001 /* 0002 SPDX-FileCopyrightText: 2011-2013 Sven Brauch <svenbrauch@googlemail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "helpers.h" 0008 0009 #include <QList> 0010 #include <QProcess> 0011 #include <QSettings> 0012 #include <QStandardPaths> 0013 0014 #include <QDebug> 0015 #include "duchaindebug.h" 0016 0017 #include <language/duchain/types/unsuretype.h> 0018 #include <language/duchain/types/integraltype.h> 0019 #include <language/duchain/types/containertypes.h> 0020 #include <language/duchain/types/functiontype.h> 0021 #include <language/duchain/duchainutils.h> 0022 #include <language/duchain/duchainlock.h> 0023 #include <language/duchain/duchain.h> 0024 #include <language/duchain/classdeclaration.h> 0025 #include <language/duchain/aliasdeclaration.h> 0026 #include <language/duchain/types/typeutils.h> 0027 #include <language/backgroundparser/backgroundparser.h> 0028 #include <interfaces/iproject.h> 0029 #include <interfaces/iprojectcontroller.h> 0030 #include <interfaces/icore.h> 0031 #include <interfaces/ilanguagecontroller.h> 0032 #include <interfaces/idocumentcontroller.h> 0033 #include <util/path.h> 0034 0035 #include <KTextEditor/View> 0036 #include <KConfigGroup> 0037 0038 #include "ast.h" 0039 #include "types/hintedtype.h" 0040 #include "types/unsuretype.h" 0041 #include "types/indexedcontainer.h" 0042 #include "kdevpythonversion.h" 0043 #include "expressionvisitor.h" 0044 0045 #if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) 0046 #define SkipEmptyParts Qt::SkipEmptyParts 0047 #else 0048 #define SkipEmptyParts QString::SkipEmptyParts 0049 #endif 0050 0051 using namespace KDevelop; 0052 0053 namespace Python { 0054 0055 QMap<IProject*, QVector<QUrl>> Helper::cachedCustomIncludes; 0056 QMap<IProject*, QVector<QUrl>> Helper::cachedSearchPaths; 0057 QVector<QUrl> Helper::projectSearchPaths; 0058 QStringList Helper::dataDirs; 0059 IndexedString Helper::documentationFile; 0060 DUChainPointer<TopDUContext> Helper::documentationFileContext = DUChainPointer<TopDUContext>(nullptr); 0061 QStringList Helper::correctionFileDirs; 0062 QString Helper::localCorrectionFileDir; 0063 QMutex Helper::cacheMutex; 0064 QMutex Helper::projectPathLock; 0065 0066 void Helper::scheduleDependency(const IndexedString& dependency, int betterThanPriority) 0067 { 0068 BackgroundParser* bgparser = KDevelop::ICore::self()->languageController()->backgroundParser(); 0069 bool needsReschedule = true; 0070 if ( bgparser->isQueued(dependency) ) { 0071 const auto priority= bgparser->priorityForDocument(dependency); 0072 if ( priority > betterThanPriority - 1 ) { 0073 bgparser->removeDocument(dependency); 0074 } 0075 else { 0076 needsReschedule = false; 0077 } 0078 } 0079 if ( needsReschedule ) { 0080 bgparser->addDocument(dependency, TopDUContext::ForceUpdate, betterThanPriority - 1, 0081 nullptr, ParseJob::FullSequentialProcessing); 0082 } 0083 } 0084 0085 IndexedDeclaration Helper::declarationUnderCursor(bool allowUse) 0086 { 0087 KDevelop::IDocument* doc = ICore::self()->documentController()->activeDocument(); 0088 const auto view = ICore::self()->documentController()->activeTextDocumentView(); 0089 if ( doc && doc->textDocument() && view ) { 0090 DUChainReadLocker lock; 0091 const auto cursor = view->cursorPosition(); 0092 if ( allowUse ) { 0093 return IndexedDeclaration(DUChainUtils::itemUnderCursor(doc->url(), cursor).declaration); 0094 } 0095 else { 0096 return DUChainUtils::declarationInLine(cursor, DUChainUtils::standardContextForUrl(doc->url())); 0097 } 0098 } 0099 0100 return KDevelop::IndexedDeclaration(); 0101 } 0102 0103 Declaration* Helper::accessAttribute(const AbstractType::Ptr accessed, 0104 const IndexedIdentifier& attribute, 0105 const TopDUContext* topContext) 0106 { 0107 if ( ! accessed ) { 0108 return nullptr; 0109 } 0110 // if the type is unsure, search all the possibilities (but return the first match) 0111 auto structureTypes = Helper::filterType<StructureType>(accessed, 0112 [](AbstractType::Ptr toFilter) { 0113 auto type = Helper::resolveAliasType(toFilter); 0114 return type && type->whichType() == AbstractType::TypeStructure; 0115 }, 0116 [](AbstractType::Ptr toMap) { 0117 return Helper::resolveAliasType(toMap).staticCast<StructureType>(); 0118 } 0119 ); 0120 auto docFileContext = Helper::getDocumentationFileContext(); 0121 0122 for ( const auto& type: structureTypes ) { 0123 auto searchContexts = Helper::internalContextsForClass(type, topContext); 0124 for ( const auto ctx: searchContexts ) { 0125 auto found = ctx->findDeclarations(attribute, CursorInRevision::invalid(), 0126 topContext, DUContext::DontSearchInParent); 0127 if ( !found.isEmpty() && ( 0128 found.last()->topContext() != docFileContext || 0129 ctx->topContext() == docFileContext) ) { 0130 // never consider decls from the builtins 0131 return found.last(); 0132 } 0133 } 0134 } 0135 return nullptr; 0136 } 0137 0138 AbstractType::Ptr Helper::resolveAliasType(const AbstractType::Ptr eventualAlias) 0139 { 0140 return TypeUtils::resolveAliasType(eventualAlias); 0141 } 0142 0143 AbstractType::Ptr Helper::extractTypeHints(AbstractType::Ptr type) 0144 { 0145 return Helper::foldTypes(Helper::filterType<AbstractType>(type, [](AbstractType::Ptr t) -> bool { 0146 auto hint = t.dynamicCast<HintedType>(); 0147 return !hint || hint->isValid(); 0148 })); 0149 } 0150 0151 Helper::FuncInfo Helper::functionForCalled(Declaration* called, bool isAlias) 0152 { 0153 if ( ! called ) { 0154 return { nullptr, false }; 0155 } 0156 else if ( called->isFunctionDeclaration() ) { 0157 return { static_cast<FunctionDeclaration*>( called ), false }; 0158 } 0159 // If we're calling a type object (isAlias == true), look for a constructor. 0160 static const IndexedIdentifier initId(KDevelop::Identifier("__init__")); 0161 0162 // Otherwise look for a `__call__()` method. 0163 static const IndexedIdentifier callId(KDevelop::Identifier("__call__")); 0164 0165 auto attr = accessAttribute(called->abstractType(), (isAlias ? initId : callId), called->topContext()); 0166 return { dynamic_cast<FunctionDeclaration*>(attr), isAlias }; 0167 } 0168 0169 Declaration* Helper::declarationForName(const QString& name, const CursorInRevision& location, 0170 KDevelop::DUChainPointer<const DUContext> context) 0171 { 0172 DUChainReadLocker lock(DUChain::lock()); 0173 auto identifier = KDevelop::Identifier(name); 0174 auto localDeclarations = context->findLocalDeclarations(identifier, location, nullptr, 0175 AbstractType::Ptr(), DUContext::DontResolveAliases); 0176 if ( !localDeclarations.isEmpty() ) { 0177 return localDeclarations.last(); 0178 } 0179 0180 QList<Declaration*> declarations; 0181 const DUContext* currentContext = context.data(); 0182 bool findInNext = true, findBeyondUse = false; 0183 do { 0184 if (findInNext) { 0185 CursorInRevision findUntil = findBeyondUse ? currentContext->topContext()->range().end : location; 0186 declarations = currentContext->findDeclarations(identifier, findUntil); 0187 0188 for (Declaration* declaration: declarations) { 0189 if (declaration->context()->type() != DUContext::Class || 0190 (currentContext->type() == DUContext::Function && declaration->context() == currentContext->parentContext())) { 0191 // Declarations from class decls must be referenced through `self.<foo>`, except 0192 // in their local scope (handled above) or when used as default arguments for methods of the same class. 0193 // Otherwise, we're done! 0194 return declaration; 0195 } 0196 } 0197 if (!declarations.isEmpty()) { 0198 // If we found declarations but rejected all of them (i.e. didn't return), we need to keep searching. 0199 findInNext = true; 0200 declarations.clear(); 0201 } 0202 } 0203 0204 if (!findBeyondUse && currentContext->owner() && currentContext->owner()->isFunctionDeclaration()) { 0205 // Names in the body may be defined after the function definition, before the function is called. 0206 // Note: only the parameter list has type DUContext::Function, so we have to do this instead. 0207 findBeyondUse = findInNext = true; 0208 } 0209 } while ((currentContext = currentContext->parentContext())); 0210 0211 return nullptr; 0212 } 0213 0214 0215 Declaration* Helper::declarationForName(const Python::NameAst* name, CursorInRevision location, 0216 KDevelop::DUChainPointer<const DUContext> context) 0217 { 0218 const Ast* checkNode = name; 0219 while ((checkNode = checkNode->parent)) { 0220 switch (checkNode->astType) { 0221 default: 0222 continue; 0223 case Ast::ListComprehensionAstType: 0224 case Ast::SetComprehensionAstType: 0225 case Ast::DictionaryComprehensionAstType: 0226 case Ast::GeneratorExpressionAstType: 0227 // Variables in comprehensions are used before their definition. `[foo for foo in bar]` 0228 auto cmpEnd = CursorInRevision(checkNode->endLine, checkNode->endCol); 0229 if (cmpEnd > location) { 0230 location = cmpEnd; 0231 } 0232 } 0233 } 0234 return declarationForName(name->identifier->value, location, context); 0235 } 0236 0237 QVector<DUContext*> Helper::internalContextsForClass(const StructureType::Ptr classType, 0238 const TopDUContext* context, ContextSearchFlags flags, int depth) 0239 { 0240 QVector<DUContext*> searchContexts; 0241 if ( ! classType ) { 0242 return searchContexts; 0243 } 0244 if ( auto c = classType->internalContext(context) ) { 0245 searchContexts << c; 0246 } 0247 auto decl = Helper::resolveAliasDeclaration(classType->declaration(context)); 0248 if ( auto classDecl = dynamic_cast<ClassDeclaration*>(decl) ) { 0249 FOREACH_FUNCTION ( const auto& base, classDecl->baseClasses ) { 0250 if ( flags == PublicOnly && base.access == KDevelop::Declaration::Private ) { 0251 continue; 0252 } 0253 auto baseClassType = base.baseClass.type<StructureType>(); 0254 // recursive call, because the base class will have more base classes eventually 0255 if ( depth < 10 ) { 0256 searchContexts.append(Helper::internalContextsForClass(baseClassType, context, flags, depth + 1)); 0257 } 0258 } 0259 } 0260 return searchContexts; 0261 } 0262 0263 Declaration* Helper::resolveAliasDeclaration(Declaration* decl) 0264 { 0265 AliasDeclaration* alias = dynamic_cast<AliasDeclaration*>(decl); 0266 if ( alias ) { 0267 DUChainReadLocker lock; 0268 return alias->aliasedDeclaration().data(); 0269 } 0270 else 0271 return decl; 0272 } 0273 0274 QStringList Helper::getDataDirs() { 0275 if ( Helper::dataDirs.isEmpty() ) { 0276 Helper::dataDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, "kdevpythonsupport/documentation_files", QStandardPaths::LocateDirectory); 0277 } 0278 return Helper::dataDirs; 0279 } 0280 0281 KDevelop::IndexedString Helper::getDocumentationFile() 0282 { 0283 if ( Helper::documentationFile.isEmpty() ) { 0284 auto path = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevpythonsupport/documentation_files/builtindocumentation.py"); 0285 Helper::documentationFile = IndexedString(path); 0286 } 0287 return Helper::documentationFile; 0288 } 0289 0290 ReferencedTopDUContext Helper::getDocumentationFileContext() 0291 { 0292 if ( Helper::documentationFileContext ) { 0293 return ReferencedTopDUContext(Helper::documentationFileContext.data()); 0294 } 0295 else { 0296 DUChainReadLocker lock; 0297 auto file = Helper::getDocumentationFile(); 0298 ReferencedTopDUContext ctx = ReferencedTopDUContext(DUChain::self()->chainForDocument(file)); 0299 Helper::documentationFileContext = DUChainPointer<TopDUContext>(ctx.data()); 0300 return ctx; 0301 } 0302 } 0303 0304 // stolen from KUrl. duh. 0305 static QString _relativePath(const QString &base_dir, const QString &path) 0306 { 0307 QString _base_dir(QDir::cleanPath(base_dir)); 0308 QString _path(QDir::cleanPath(path.isEmpty() || QDir::isRelativePath(path) ? _base_dir+QLatin1Char('/')+path : path)); 0309 0310 if (_base_dir.isEmpty()) 0311 return _path; 0312 0313 if (_base_dir[_base_dir.length()-1] != QLatin1Char('/')) 0314 _base_dir.append(QLatin1Char('/') ); 0315 0316 const QStringList list1 = _base_dir.split(QLatin1Char('/'), SkipEmptyParts); 0317 const QStringList list2 = _path.split(QLatin1Char('/'), SkipEmptyParts); 0318 0319 // Find where they meet 0320 int level = 0; 0321 int maxLevel = qMin(list1.count(), list2.count()); 0322 while((level < maxLevel) && (list1[level] == list2[level])) level++; 0323 0324 QString result; 0325 // Need to go down out of the first path to the common branch. 0326 for(int i = level; i < list1.count(); i++) 0327 result.append(QLatin1String("../")); 0328 0329 // Now up up from the common branch to the second path. 0330 for(int i = level; i < list2.count(); i++) 0331 result.append(list2[i]).append(QLatin1Char('/')); 0332 0333 if ((level < list2.count()) && (path[path.length()-1] != QLatin1Char('/'))) 0334 result.truncate(result.length()-1); 0335 0336 return result; 0337 } 0338 0339 QUrl Helper::getCorrectionFile(const QUrl& document) 0340 { 0341 if ( Helper::correctionFileDirs.isEmpty() ) { 0342 Helper::correctionFileDirs = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, 0343 "kdevpythonsupport/correction_files/", 0344 QStandardPaths::LocateDirectory); 0345 } 0346 0347 foreach (QString correctionFileDir, correctionFileDirs) { 0348 foreach ( const QUrl& basePath, Helper::getSearchPaths(QUrl()) ) { 0349 if ( ! basePath.isParentOf(document) ) { 0350 continue; 0351 } 0352 auto base = basePath.path(); 0353 auto doc = document.path(); 0354 auto relative = _relativePath(base, doc); 0355 auto fullPath = correctionFileDir + "/" + relative; 0356 if ( QFile::exists(fullPath) ) { 0357 return QUrl::fromLocalFile(fullPath).adjusted(QUrl::NormalizePathSegments); 0358 } 0359 } 0360 } 0361 return {}; 0362 } 0363 0364 QUrl Helper::getLocalCorrectionFile(const QUrl& document) 0365 { 0366 if ( Helper::localCorrectionFileDir.isNull() ) { 0367 Helper::localCorrectionFileDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + "kdevpythonsupport/correction_files/"; 0368 } 0369 0370 auto absolutePath = QUrl(); 0371 foreach ( const auto& basePath, Helper::getSearchPaths(QUrl()) ) { 0372 if ( ! basePath.isParentOf(document) ) { 0373 continue; 0374 } 0375 auto path = QDir(basePath.path()).relativeFilePath(document.path()); 0376 absolutePath = QUrl::fromLocalFile(Helper::localCorrectionFileDir + path); 0377 break; 0378 } 0379 return absolutePath; 0380 } 0381 0382 QString Helper::getPythonExecutablePath(IProject* project) 0383 { 0384 if ( project ) { 0385 auto interpreter = project->projectConfiguration()->group("pythonsupport").readEntry("interpreter"); 0386 if ( !interpreter.isEmpty() ) { 0387 // we have a user-configured interpreter, try using it 0388 QFile f(interpreter); 0389 if ( f.exists() ) { 0390 return interpreter; 0391 } 0392 qCWarning(KDEV_PYTHON_DUCHAIN) << "Custom python interpreter" << interpreter << "configured for project" << project->name() << "is invalid, using default"; 0393 } 0394 } 0395 0396 // Find python 3 (https://www.python.org/dev/peps/pep-0394/) 0397 auto result = QStandardPaths::findExecutable("python" PYTHON_VERSION_STR); 0398 if ( ! result.isEmpty() ) { 0399 return result; 0400 } 0401 result = QStandardPaths::findExecutable("python" PYTHON_VERSION_MAJOR_STR); 0402 if ( ! result.isEmpty() ) { 0403 return result; 0404 } 0405 result = QStandardPaths::findExecutable("python"); 0406 if ( ! result.isEmpty() ) { 0407 return result; 0408 } 0409 0410 #ifdef Q_OS_WIN 0411 QStringList extraPaths; 0412 // Check for default CPython installation path, because the 0413 // installer does not add the path to $PATH. 0414 QStringList keys = { 0415 "HKEY_LOCAL_MACHINE\\Software\\Python\\PythonCore\\PYTHON_VERSION\\InstallPath", 0416 "HKEY_LOCAL_MACHINE\\Software\\Python\\PythonCore\\PYTHON_VERSION-32\\InstallPath", 0417 "HKEY_CURRENT_USER\\Software\\Python\\PythonCore\\PYTHON_VERSION\\InstallPath", 0418 "HKEY_CURRENT_USER\\Software\\Python\\PythonCore\\PYTHON_VERSION-32\\InstallPath" 0419 }; 0420 auto version = QString(PYTHON_VERSION_STR); 0421 foreach ( QString key, keys ) { 0422 key.replace("PYTHON_VERSION", version); 0423 QSettings base(key.left(key.indexOf("Python")), QSettings::NativeFormat); 0424 if ( ! base.childGroups().contains("Python") ) { 0425 continue; 0426 } 0427 QSettings keySettings(key, QSettings::NativeFormat); 0428 auto path = keySettings.value("Default").toString(); 0429 if ( ! path.isEmpty() ) { 0430 extraPaths << path; 0431 break; 0432 } 0433 } 0434 result = QStandardPaths::findExecutable("python", extraPaths); 0435 if ( ! result.isEmpty() ) { 0436 return result; 0437 } 0438 #endif 0439 // fallback 0440 return PYTHON_EXECUTABLE; 0441 } 0442 0443 QVector<QUrl> Helper::getSearchPaths(const QUrl& workingOnDocument) 0444 { 0445 QMutexLocker lock(&Helper::cacheMutex); 0446 QVector<QUrl> searchPaths; 0447 // search in the projects, as they're packages and likely to be installed or added to PYTHONPATH later 0448 // and also add custom include paths that are defined in the projects 0449 0450 auto project = ICore::self()->projectController()->findProjectForUrl(workingOnDocument); 0451 { 0452 QMutexLocker lock(&Helper::projectPathLock); 0453 searchPaths << Helper::projectSearchPaths; 0454 searchPaths << Helper::cachedCustomIncludes.value(project); 0455 } 0456 0457 foreach ( const QString& path, getDataDirs() ) { 0458 searchPaths.append(QUrl::fromLocalFile(path)); 0459 } 0460 0461 if ( !cachedSearchPaths.contains(project) ) { 0462 QVector<QUrl> cachedForProject; 0463 qCDebug(KDEV_PYTHON_DUCHAIN) << "*** Collecting search paths..."; 0464 QStringList getpath; 0465 getpath << "-c" << "import sys; sys.stdout.write('$|$'.join(sys.path))"; 0466 0467 QProcess python; 0468 python.start(getPythonExecutablePath(project), getpath); 0469 python.waitForFinished(1000); 0470 QString pythonpath = QString::fromUtf8(python.readAllStandardOutput()); 0471 0472 if ( ! pythonpath.isEmpty() ) { 0473 const auto paths = pythonpath.split("$|$", SkipEmptyParts); 0474 foreach ( const QString& path, paths ) { 0475 cachedForProject.append(QUrl::fromLocalFile(path)); 0476 } 0477 } 0478 else { 0479 qCWarning(KDEV_PYTHON_DUCHAIN) << "Could not get search paths! Defaulting to stupid stuff."; 0480 searchPaths.append(QUrl::fromLocalFile("/usr/lib/python" PYTHON_VERSION_STR)); 0481 searchPaths.append(QUrl::fromLocalFile("/usr/lib/python" PYTHON_VERSION_STR "/site-packages")); 0482 QString path = qgetenv("PYTHONPATH"); 0483 QStringList paths = path.split(':'); 0484 foreach ( const QString& path, paths ) { 0485 cachedForProject.append(QUrl::fromLocalFile(path)); 0486 } 0487 } 0488 qCDebug(KDEV_PYTHON_DUCHAIN) << " *** Done. Got search paths: " << cachedSearchPaths; 0489 cachedSearchPaths.insert(project, cachedForProject); 0490 } 0491 0492 searchPaths.append(cachedSearchPaths.value(project)); 0493 0494 auto dir = workingOnDocument.adjusted(QUrl::RemoveFilename); 0495 if ( ! dir.isEmpty() ) { 0496 // search in the current packages 0497 searchPaths.append(dir); 0498 } 0499 0500 return searchPaths; 0501 } 0502 0503 bool Helper::isUsefulType(AbstractType::Ptr type) 0504 { 0505 return TypeUtils::isUsefulType(type); 0506 } 0507 0508 AbstractType::Ptr Helper::contentOfIterable(const AbstractType::Ptr iterable, const TopDUContext* topContext) 0509 { 0510 auto types = filterType<StructureType>(iterable, 0511 [](AbstractType::Ptr t) { return t->whichType() == AbstractType::TypeStructure; } ); 0512 0513 static const IndexedIdentifier iterId(KDevelop::Identifier("__iter__")); 0514 static const IndexedIdentifier nextId(KDevelop::Identifier("__next__")); 0515 AbstractType::Ptr content(new IntegralType(IntegralType::TypeMixed)); 0516 0517 for ( const auto& type: types ) { 0518 if ( auto map = type.dynamicCast<MapType>() ) { 0519 // Iterating over dicts gets keys, not values 0520 content = mergeTypes(content, map->keyType().abstractType()); 0521 continue; 0522 } 0523 else if ( auto list = type.dynamicCast<ListType>() ) { 0524 content = mergeTypes(content, list->contentType().abstractType()); 0525 continue; 0526 } 0527 else if ( auto indexed = type.dynamicCast<IndexedContainer>() ) { 0528 content = mergeTypes(content, indexed->asUnsureType()); 0529 continue; 0530 } 0531 DUChainReadLocker lock; 0532 // Content of an iterable object is iterable.__iter__().__next__(). 0533 if ( auto iterFunc = dynamic_cast<FunctionDeclaration*>(accessAttribute(type, iterId, topContext)) ) { 0534 if ( auto iterator = iterFunc->type<FunctionType>()->returnType().dynamicCast<StructureType>() ) { 0535 if ( auto nextFunc = dynamic_cast<FunctionDeclaration*>(accessAttribute(iterator, nextId, topContext)) ) { 0536 content = mergeTypes(content, nextFunc->type<FunctionType>()->returnType()); 0537 } 0538 } 0539 } 0540 } 0541 return content; 0542 } 0543 0544 AbstractType::Ptr Helper::mergeTypes(AbstractType::Ptr type, const AbstractType::Ptr newType) 0545 { 0546 UnsureType::Ptr ret; 0547 return TypeUtils::mergeTypes<Python::UnsureType>(type, newType); 0548 } 0549 0550 }