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 }