File indexing completed on 2024-05-05 16:42:26
0001 /* 0002 SPDX-FileCopyrightText: 2007 Piyush verma <piyush.verma@gmail.com> 0003 SPDX-FileCopyrightText: 2007 Andreas Pakulat <apaku@gmx.de> 0004 SPDX-FileCopyrightText: 2010-2013 Sven Brauch <svenbrauch@googlemail.com> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "pythonlanguagesupport.h" 0010 #include "pythoneditorintegrator.h" 0011 #include "dumpchain.h" 0012 #include "usebuilder.h" 0013 #include "contextbuilder.h" 0014 #include "pythonducontext.h" 0015 #include "pythonparsejob.h" 0016 #include "declarationbuilder.h" 0017 #include "helpers.h" 0018 #include "duchaindebug.h" 0019 0020 #include <ktexteditor/document.h> 0021 0022 #include <language/duchain/ducontext.h> 0023 #include <language/duchain/declaration.h> 0024 #include <language/duchain/duchainlock.h> 0025 #include <language/duchain/topducontext.h> 0026 #include <language/duchain/parsingenvironment.h> 0027 #include <language/backgroundparser/backgroundparser.h> 0028 #include <language/editor/rangeinrevision.h> 0029 #include <language/editor/cursorinrevision.h> 0030 #include <interfaces/icore.h> 0031 #include <interfaces/idocumentcontroller.h> 0032 #include <interfaces/ilanguagecontroller.h> 0033 #include <interfaces/iprojectcontroller.h> 0034 #include <interfaces/iproject.h> 0035 #include <project/projectmodel.h> 0036 0037 using namespace KDevelop; 0038 0039 using namespace KTextEditor; 0040 0041 namespace Python 0042 { 0043 0044 ReferencedTopDUContext ContextBuilder::build(const IndexedString& url, Ast* node, 0045 const ReferencedTopDUContext& updateContext_) 0046 { 0047 ReferencedTopDUContext updateContext(updateContext_); 0048 if (!updateContext) { 0049 DUChainReadLocker lock(DUChain::lock()); 0050 updateContext = DUChain::self()->chainForDocument(url); 0051 if ( updateContext ) { 0052 Q_ASSERT(updateContext->type() == DUContext::Global); 0053 } 0054 } 0055 if (updateContext) { 0056 qCDebug(KDEV_PYTHON_DUCHAIN) << " ====> DUCHAIN ====> rebuilding duchain for" << url.str() << "(was built before)"; 0057 DUChainWriteLocker lock(DUChain::lock()); 0058 Q_ASSERT(updateContext->type() == DUContext::Global); 0059 updateContext->clearImportedParentContexts(); 0060 updateContext->parsingEnvironmentFile()->clearModificationRevisions(); 0061 updateContext->clearProblems(); 0062 } else { 0063 qCDebug(KDEV_PYTHON_DUCHAIN) << " ====> DUCHAIN ====> building duchain for" << url.str(); 0064 } 0065 return ContextBuilderBase::build(url, node, updateContext); 0066 } 0067 0068 PythonEditorIntegrator* ContextBuilder::editor() const 0069 { 0070 return m_editor; 0071 } 0072 0073 IndexedString ContextBuilder::currentlyParsedDocument() const 0074 { 0075 return m_currentlyParsedDocument; 0076 } 0077 0078 void ContextBuilder::setCurrentlyParsedDocument(const IndexedString &document) 0079 { 0080 m_currentlyParsedDocument = document; 0081 } 0082 0083 void ContextBuilder::setFutureModificationRevision(const ModificationRevision &rev) 0084 { 0085 m_futureModificationRevision = rev; 0086 } 0087 0088 RangeInRevision ContextBuilder::rangeForNode(Ast* node, bool moveRight) 0089 { 0090 return RangeInRevision(node->startLine, node->startCol, node->endLine, node->endCol + (int) moveRight); 0091 } 0092 0093 RangeInRevision ContextBuilder::rangeForNode(Identifier* node, bool moveRight) 0094 { 0095 return rangeForNode(static_cast<Ast*>(node), moveRight); 0096 } 0097 0098 TopDUContext* ContextBuilder::newTopContext(const RangeInRevision& range, ParsingEnvironmentFile* file) 0099 { 0100 IndexedString currentDocumentUrl = currentlyParsedDocument(); 0101 if ( !file ) { 0102 file = new ParsingEnvironmentFile(currentDocumentUrl); 0103 file->setLanguage(IndexedString("python")); 0104 } 0105 TopDUContext* top = new PythonTopDUContext(currentDocumentUrl, range, file); 0106 ReferencedTopDUContext ref(top); 0107 m_topContext = ref; 0108 return top; 0109 } 0110 0111 DUContext* ContextBuilder::newContext(const RangeInRevision& range) 0112 { 0113 return new PythonNormalDUContext(range, currentContext()); 0114 } 0115 0116 void ContextBuilder::addUnresolvedImport(const IndexedString &module) 0117 { 0118 m_unresolvedImports.append(module); 0119 } 0120 0121 QList<IndexedString> ContextBuilder::unresolvedImports() const 0122 { 0123 return m_unresolvedImports; 0124 } 0125 0126 void ContextBuilder::setEditor(PythonEditorIntegrator* editor) 0127 { 0128 m_editor = editor; 0129 } 0130 0131 void ContextBuilder::startVisiting(Ast* node) 0132 { 0133 visitNode(node); 0134 } 0135 0136 void ContextBuilder::setContextOnNode(Ast* node, DUContext* context) 0137 { 0138 node->context = context; 0139 } 0140 0141 DUContext* ContextBuilder::contextFromNode(Ast* node) 0142 { 0143 return node->context; 0144 } 0145 0146 RangeInRevision ContextBuilder::editorFindRange(Ast* fromNode, Ast* toNode) 0147 { 0148 return editor()->findRange(fromNode, toNode); 0149 } 0150 0151 CursorInRevision ContextBuilder::editorFindPositionSafe(Ast* node) { 0152 if ( !node ) { 0153 return CursorInRevision::invalid(); 0154 } 0155 return editor()->findPosition(node); 0156 } 0157 0158 CursorInRevision ContextBuilder::startPos( Ast* node ) 0159 { 0160 return m_editor->findPosition(node, PythonEditorIntegrator::FrontEdge); 0161 } 0162 0163 QualifiedIdentifier ContextBuilder::identifierForNode( Python::Identifier* node ) 0164 { 0165 return QualifiedIdentifier(node->value); 0166 } 0167 0168 void ContextBuilder::addImportedContexts() 0169 { 0170 if ( compilingContexts() && !m_importedParentContexts.isEmpty() ) 0171 { 0172 DUChainWriteLocker lock( DUChain::lock() ); 0173 foreach( DUContext* imported, m_importedParentContexts ) 0174 currentContext()->addImportedParentContext( imported ); 0175 0176 m_importedParentContexts.clear(); 0177 } 0178 } 0179 0180 void ContextBuilder::closeAlreadyOpenedContext(DUContextPointer context) 0181 { 0182 Q_ASSERT(currentContext() == context.data()); 0183 while ( ! m_temporarilyClosedContexts.isEmpty() ) { 0184 openContext(m_temporarilyClosedContexts.last().data()); 0185 m_temporarilyClosedContexts.removeLast(); 0186 } 0187 } 0188 0189 void ContextBuilder::activateAlreadyOpenedContext(DUContextPointer context) 0190 { 0191 Q_ASSERT(m_temporarilyClosedContexts.isEmpty()); 0192 Q_ASSERT(contextAlreadyOpen(context)); 0193 DUContext* current = currentContext(); 0194 bool reallyCompilingContexts = compilingContexts(); 0195 setCompilingContexts(false); // TODO this is very hackish. 0196 while ( current ) { 0197 if ( current == context.data() ) { 0198 setCompilingContexts(reallyCompilingContexts); 0199 return; 0200 } 0201 m_temporarilyClosedContexts.append(DUContextPointer(current)); 0202 closeContext(); 0203 current = currentContext(); 0204 } 0205 setCompilingContexts(reallyCompilingContexts); 0206 } 0207 0208 bool ContextBuilder::contextAlreadyOpen(DUContextPointer context) 0209 { 0210 DUContext* current = currentContext(); 0211 while ( current ) { 0212 if ( context.data() == current ) return true; 0213 current = current->parentContext(); 0214 } 0215 return false; 0216 } 0217 0218 void ContextBuilder::visitListComprehension(ListComprehensionAst* node) 0219 { 0220 visitComprehensionCommon(node); 0221 } 0222 0223 void ContextBuilder::visitDictionaryComprehension(DictionaryComprehensionAst* node) 0224 { 0225 visitComprehensionCommon(node); 0226 } 0227 0228 void ContextBuilder::visitGeneratorExpression(GeneratorExpressionAst* node) 0229 { 0230 visitComprehensionCommon(node); 0231 } 0232 0233 RangeInRevision ContextBuilder::comprehensionRange(Ast* node) 0234 { 0235 RangeInRevision range = editorFindRange(node, node); 0236 return range; 0237 } 0238 0239 void ContextBuilder::visitComprehensionCommon(Ast* node) 0240 { 0241 RangeInRevision range = comprehensionRange(node); 0242 Q_ASSERT(range.isValid()); 0243 if ( range.isValid() ) { 0244 DUChainWriteLocker lock; 0245 openContext(node, range, KDevelop::DUContext::Other); 0246 qCDebug(KDEV_PYTHON_DUCHAIN) << "creating comprehension context" << node << range; 0247 Q_ASSERT(currentContext()); 0248 // currentContext()->setLocalScopeIdentifier(QualifiedIdentifier("<generator>")); 0249 lock.unlock(); 0250 if ( node->astType == Ast::DictionaryComprehensionAstType ) 0251 Python::AstDefaultVisitor::visitDictionaryComprehension(static_cast<DictionaryComprehensionAst*>(node)); 0252 if ( node->astType == Ast::ListComprehensionAstType ) 0253 Python::AstDefaultVisitor::visitListComprehension(static_cast<ListComprehensionAst*>(node)); 0254 if ( node->astType == Ast::GeneratorExpressionAstType ) 0255 Python::AstDefaultVisitor::visitGeneratorExpression(static_cast<GeneratorExpressionAst*>(node)); 0256 if ( node->astType == Ast::SetComprehensionAstType ) 0257 Python::AstDefaultVisitor::visitSetComprehension(static_cast<SetComprehensionAst*>(node)); 0258 lock.lock(); 0259 closeContext(); 0260 } 0261 } 0262 0263 void ContextBuilder::openContextForClassDefinition(ClassDefinitionAst* node) 0264 { 0265 // make sure the contexts ends at the next DEDENT token, not at the last statement. 0266 // also, make the context begin *after* the parent list and class name. 0267 int endLine = editor()->indent()->nextChange(node->endLine, FileIndentInformation::Dedent); 0268 CursorInRevision start = CursorInRevision(node->body.first()->startLine, node->body.first()->startCol); 0269 if ( start.line > node->startLine ) { 0270 start = CursorInRevision(node->startLine + 1, 0); 0271 } 0272 RangeInRevision range(start, CursorInRevision(endLine, 0)); 0273 DUChainWriteLocker lock; 0274 openContext(node, range, DUContext::Class, node->name); 0275 currentContext()->setLocalScopeIdentifier(identifierForNode(node->name)); 0276 lock.unlock(); 0277 addImportedContexts(); 0278 } 0279 0280 void ContextBuilder::visitClassDefinition( ClassDefinitionAst* node ) 0281 { 0282 openContextForClassDefinition(node); 0283 Python::AstDefaultVisitor::visitClassDefinition(node); 0284 closeContext(); 0285 } 0286 0287 void ContextBuilder::visitCode(CodeAst* node) { 0288 IndexedString doc = Helper::getDocumentationFile(); 0289 Q_ASSERT(currentlyParsedDocument().toUrl().isValid()); 0290 if ( currentlyParsedDocument() != doc ) { 0291 // Search for the python built-in functions file, and dump its contents into the current file. 0292 auto internal = Helper::getDocumentationFileContext(); 0293 if ( ! internal ) { 0294 // If the built-in functions file is not yet parsed, schedule it with a high priority. 0295 m_unresolvedImports.append(doc); 0296 KDevelop::ICore::self()->languageController()->backgroundParser() 0297 ->addDocument(doc, KDevelop::TopDUContext::ForceUpdate, 0298 BackgroundParser::BestPriority*2, nullptr, ParseJob::FullSequentialProcessing); 0299 // This must NOT be called from parse threads! It's only meant to be used from the foreground thread, and will 0300 // cause thread starvation if called from here. 0301 // KDevelop::ICore::self()->languageController()->backgroundParser()->parseDocuments(); 0302 } 0303 else { 0304 DUChainWriteLocker wlock; 0305 currentContext()->addImportedParentContext(internal); 0306 } 0307 } 0308 AstDefaultVisitor::visitCode(node); 0309 } 0310 0311 QPair<QUrl, QStringList> ContextBuilder::findModulePath(const QString& name, const QUrl& currentDocument) 0312 { 0313 QStringList nameComponents = name.split("."); 0314 QVector<QUrl> searchPaths; 0315 if ( name.startsWith('.') ) { 0316 /* To take care for imports like "from ....xxxx.yyy import zzz" 0317 * we need to take current doc path and run "cd .." enough times 0318 */ 0319 nameComponents.removeFirst(); 0320 QString tname = name.mid(1); // remove first dot 0321 QDir curPathDir = QDir(currentDocument.adjusted(QUrl::RemoveFilename).toLocalFile()); 0322 foreach(QString c, tname) { 0323 if (c != ".") 0324 break; 0325 curPathDir.cdUp(); 0326 nameComponents.removeFirst(); 0327 } 0328 searchPaths << QUrl::fromLocalFile(curPathDir.path()); 0329 } 0330 else { 0331 // If this is not a relative import, use the project directory, 0332 // the current directory, and all system include paths. 0333 // FIXME: If absolute imports enabled, don't add curently parsed doc path 0334 searchPaths = Helper::getSearchPaths(currentDocument); 0335 } 0336 // Loop over all the name components, and find matching folders or files. 0337 QDir tmp; 0338 QStringList leftNameComponents; 0339 foreach ( const QUrl& currentPath, searchPaths ) { 0340 tmp.setPath(currentPath.toLocalFile()); 0341 leftNameComponents = nameComponents; 0342 foreach ( QString component, nameComponents ) { 0343 if ( component == "*" ) { 0344 // For "from ... import *", if "..." is a directory, use the "__init__.py" file 0345 component = QStringLiteral("__init__"); 0346 } 0347 else { 0348 // only empty the list if not importing *, this is convenient later on 0349 leftNameComponents.removeFirst(); 0350 } 0351 QString testFilename = tmp.path() + "/" + component; 0352 0353 bool can_continue = tmp.cd(component); 0354 QFileInfo sourcedir(testFilename); 0355 const bool dir_exists = sourcedir.exists() && sourcedir.isDir(); 0356 0357 // we can only parse those, so we don't care about anything else for now. 0358 // Any C modules (.so, .dll) will be ignored, and highlighted as "not found". TODO fix this 0359 static QStringList valid_extensions{".py", ".pyx"}; 0360 foreach ( const auto& extension, valid_extensions ) { 0361 QFile sourcefile(testFilename + extension); 0362 if ( ! dir_exists || leftNameComponents.isEmpty() ) { 0363 // If the search cannot continue further down into a hierarchy of directories, 0364 // the file matching the next name component will be returned, 0365 // toegether with a list of names which must be resolved inside that file. 0366 if ( sourcefile.exists() ) { 0367 auto sourceUrl = QUrl::fromLocalFile(testFilename + extension); 0368 // TODO QUrl: cleanPath? 0369 return qMakePair(sourceUrl, leftNameComponents); 0370 } 0371 else if ( dir_exists ) { 0372 auto path = QUrl::fromLocalFile(testFilename + "/__init__.py"); 0373 // TODO QUrl: cleanPath? 0374 return qMakePair(path, leftNameComponents); 0375 } 0376 } 0377 } 0378 if ( ! can_continue ) { 0379 // if not returned yet and the cd into the next component failed, 0380 // abort and try the next search path. 0381 break; 0382 } 0383 } 0384 } 0385 return {}; 0386 } 0387 0388 void ContextBuilder::visitLambda(LambdaAst* node) 0389 { 0390 openContext(node, editorFindRange(node, node->body), DUContext::Other); 0391 AstDefaultVisitor::visitLambda(node); 0392 closeContext(); 0393 } 0394 0395 RangeInRevision ContextBuilder::rangeForArgumentsContext(FunctionDefinitionAst* node) 0396 { 0397 auto start = node->name->range().end(); 0398 auto end = start; 0399 auto args = node->arguments; 0400 if ( args->kwarg ) { 0401 end = args->kwarg->range().end(); 0402 } 0403 else if ( args->vararg && 0404 ( args->arguments.isEmpty() || 0405 ! args->vararg->appearsBefore(args->arguments.last())) ) { 0406 end = args->vararg->range().end(); 0407 } 0408 else if ( ! args->arguments.isEmpty() ) { 0409 end = args->arguments.last()->range().end(); 0410 } 0411 0412 if ( ! args->defaultValues.isEmpty() ) { 0413 end = qMax<KTextEditor::Cursor>(args->defaultValues.last()->range().end(), end); 0414 } 0415 0416 RangeInRevision range(start.line(), start.column(), end.line(), end.column()); 0417 range.start.column += 1; // Don't include end of name. 0418 range.end.column += 1; // Include end parenthesis (unless spaces...) 0419 return range; 0420 } 0421 0422 void ContextBuilder::visitFunctionArguments(FunctionDefinitionAst* node) 0423 { 0424 RangeInRevision range = rangeForArgumentsContext(node); 0425 0426 // The DUChain expects the context containing a function's arguments to be of type Function. 0427 // The function body will have DUContext::Other as type, as it contains only code. 0428 DUContext* funcctx = openContext(node->arguments, range, DUContext::Function, node->name); 0429 AstDefaultVisitor::visitArguments(node->arguments); 0430 visitArguments(node->arguments); 0431 closeContext(); 0432 // the parameters should be visible in the function body, so import that context there 0433 m_importedParentContexts.append(funcctx); 0434 } 0435 0436 void ContextBuilder::visitFunctionDefinition(FunctionDefinitionAst* node) 0437 { 0438 visitNodeList(node->decorators); 0439 visitNode(node->returns); 0440 visitFunctionArguments(node); 0441 visitFunctionBody(node); 0442 } 0443 0444 void ContextBuilder::visitFunctionBody(FunctionDefinitionAst* node) 0445 { 0446 // The function should end at the next DEDENT token, not at the body's last statement 0447 int endLine = node->endLine; 0448 if ( ! node->body.isEmpty() ) { 0449 endLine = node->body.last()->startLine; 0450 } 0451 if ( node->endLine != node->startLine ) { 0452 endLine = editor()->indent()->nextChange(endLine, FileIndentInformation::Dedent); 0453 if ( ! node->body.isEmpty() ) { 0454 endLine = qMax<int>(endLine, node->body.last()->endLine + 1); 0455 } 0456 } 0457 CursorInRevision end = CursorInRevision(endLine, node->startLine == node->endLine ? INT_MAX : 0); 0458 CursorInRevision start = rangeForArgumentsContext(node).end; 0459 if ( start.line < node->body.first()->startLine ) { 0460 start = CursorInRevision(node->startLine + 1, 0); 0461 } 0462 RangeInRevision range(start, end); 0463 0464 // Open the context for the function body (the list of statements) 0465 // It's of type Other, as it contains only code 0466 openContext(node, range, DUContext::Other, identifierForNode(node->name)); 0467 { 0468 DUChainWriteLocker lock; 0469 currentContext()->setLocalScopeIdentifier(identifierForNode(node->name)); 0470 } 0471 // import the parameters into the function body 0472 addImportedContexts(); 0473 0474 visitNodeList(node->body); 0475 0476 closeContext(); 0477 } 0478 0479 }