File indexing completed on 2024-05-05 16:41:05
0001 /* 0002 SPDX-FileCopyrightText: 2008 Niko Sams <niko.sams@gmail.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "contextbuilder.h" 0008 0009 #include <KLocalizedString> 0010 0011 #include <language/duchain/duchain.h> 0012 #include <language/duchain/topducontext.h> 0013 #include <language/duchain/duchainlock.h> 0014 #include <language/duchain/declaration.h> 0015 #include <language/duchain/classdeclaration.h> 0016 #include <language/editor/documentrange.h> 0017 0018 #include <interfaces/icore.h> 0019 #include <interfaces/ilanguagecontroller.h> 0020 #include <interfaces/icompletionsettings.h> 0021 0022 #include "../editorintegrator.h" 0023 #include "../helper.h" 0024 #include "../phpducontext.h" 0025 0026 #include "../parser/parsesession.h" 0027 #include "../parser/phpast.h" 0028 #include <duchaindebug.h> 0029 0030 using namespace KDevelop; 0031 0032 namespace Php 0033 { 0034 0035 ContextBuilder::ContextBuilder() 0036 : m_isInternalFunctions(false), m_reportErrors(true), 0037 m_mapAst(false), m_hadUnresolvedIdentifiers(false), 0038 m_editor(nullptr), m_openNamespaces(nullptr) 0039 { 0040 } 0041 0042 ContextBuilder::~ContextBuilder() 0043 { 0044 } 0045 0046 EditorIntegrator* ContextBuilder::editor() const 0047 { 0048 return m_editor; 0049 } 0050 0051 ReferencedTopDUContext ContextBuilder::build(const IndexedString& url, AstNode* node, 0052 const ReferencedTopDUContext& updateContext_) 0053 { 0054 ReferencedTopDUContext updateContext(updateContext_); 0055 m_isInternalFunctions = url == internalFunctionFile(); 0056 if ( m_isInternalFunctions ) { 0057 m_reportErrors = false; 0058 } else if ( ICore::self() ) { 0059 m_reportErrors = ICore::self()->languageController()->completionSettings()->highlightSemanticProblems(); 0060 } 0061 0062 if (!updateContext) { 0063 DUChainReadLocker lock(DUChain::lock()); 0064 updateContext = DUChain::self()->chainForDocument(url); 0065 } 0066 if (updateContext) { 0067 qCDebug(DUCHAIN) << "re-compiling" << url.str(); 0068 DUChainWriteLocker lock(DUChain::lock()); 0069 updateContext->clearImportedParentContexts(); 0070 updateContext->parsingEnvironmentFile()->clearModificationRevisions(); 0071 updateContext->clearProblems(); 0072 updateContext->updateImportsCache(); 0073 } else { 0074 qCDebug(DUCHAIN) << "compiling" << url.str(); 0075 } 0076 ReferencedTopDUContext top = ContextBuilderBase::build(url, node, updateContext); 0077 0078 { 0079 DUChainWriteLocker lock(DUChain::lock()); 0080 top->updateImportsCache(); 0081 } 0082 0083 return top; 0084 } 0085 0086 bool ContextBuilder::hadUnresolvedIdentifiers() const 0087 { 0088 return m_hadUnresolvedIdentifiers; 0089 } 0090 0091 0092 void ContextBuilder::startVisiting(AstNode* node) 0093 { 0094 if (compilingContexts()) { 0095 TopDUContext* top = dynamic_cast<TopDUContext*>(currentContext()); 0096 Q_ASSERT(top); 0097 { 0098 DUChainWriteLocker lock(DUChain::lock()); 0099 top->updateImportsCache(); //Mark that we will use a cached import-structure 0100 } 0101 0102 bool hasImports; 0103 { 0104 DUChainReadLocker lock(DUChain::lock()); 0105 hasImports = !top->importedParentContexts().isEmpty(); 0106 } 0107 if (!hasImports && top->url() != internalFunctionFile()) { 0108 DUChainWriteLocker lock(DUChain::lock()); 0109 TopDUContext* import = DUChain::self()->chainForDocument(internalFunctionFile()); 0110 if (!import) { 0111 qWarning() << "importing internalFunctions failed" << currentContext()->url().str(); 0112 Q_ASSERT(false); 0113 } else { 0114 top->addImportedParentContext(import); 0115 top->updateImportsCache(); 0116 } 0117 } 0118 0119 } 0120 visitNode(node); 0121 if (m_openNamespaces) { 0122 closeNamespaces(m_openNamespaces); 0123 m_openNamespaces = nullptr; 0124 } 0125 } 0126 0127 DUContext* ContextBuilder::newContext(const RangeInRevision& range) 0128 { 0129 return new PhpDUContext<DUContext>(range, currentContext()); 0130 } 0131 0132 TopDUContext* ContextBuilder::newTopContext(const RangeInRevision& range, ParsingEnvironmentFile* file) 0133 { 0134 if (!file) { 0135 file = new ParsingEnvironmentFile(m_editor->parseSession()->currentDocument()); 0136 /// Indexed string for 'Php', identifies environment files from this language plugin 0137 static const IndexedString phpLangString("Php"); 0138 file->setLanguage(phpLangString); 0139 } 0140 TopDUContext* ret = new PhpDUContext<TopDUContext>(m_editor->parseSession()->currentDocument(), range, file); 0141 ret->setType(DUContext::Global); 0142 return ret; 0143 } 0144 0145 void ContextBuilder::setContextOnNode(AstNode* node, DUContext* ctx) 0146 { 0147 node->ducontext = ctx; 0148 } 0149 0150 DUContext* ContextBuilder::contextFromNode(AstNode* node) 0151 { 0152 return node->ducontext; 0153 } 0154 0155 RangeInRevision ContextBuilder::editorFindRange(AstNode* fromRange, AstNode* toRange) 0156 { 0157 return m_editor->findRange(fromRange, toRange ? toRange : fromRange); 0158 } 0159 0160 CursorInRevision ContextBuilder::startPos(AstNode* node) 0161 { 0162 return m_editor->findPosition(node->startToken, EditorIntegrator::FrontEdge); 0163 } 0164 0165 QualifiedIdentifier ContextBuilder::identifierForNode(IdentifierAst* id) 0166 { 0167 if (!id) 0168 return QualifiedIdentifier(); 0169 0170 return QualifiedIdentifier(stringForNode(id)); 0171 } 0172 0173 QualifiedIdentifier ContextBuilder::identifierForNode(SemiReservedIdentifierAst* id) 0174 { 0175 if (!id) 0176 return QualifiedIdentifier(); 0177 0178 return QualifiedIdentifier(stringForNode(id)); 0179 } 0180 0181 QualifiedIdentifier ContextBuilder::identifierForNode(VariableIdentifierAst* id) 0182 { 0183 if (!id) 0184 return QualifiedIdentifier(); 0185 QString ret(stringForNode(id)); 0186 ret = ret.mid(1); //cut off $ 0187 return QualifiedIdentifier(ret); 0188 } 0189 0190 IdentifierPair ContextBuilder::identifierPairForNode(IdentifierAst* id, bool isConstIdentifier) 0191 { 0192 if (!id) { 0193 return qMakePair(IndexedString(), QualifiedIdentifier()); 0194 } 0195 const QString ret = stringForNode(id); 0196 0197 if ( isConstIdentifier ) { 0198 return qMakePair(IndexedString(ret), QualifiedIdentifier(ret)); 0199 } else { 0200 return qMakePair(IndexedString(ret), QualifiedIdentifier(ret.toLower())); 0201 } 0202 } 0203 0204 IdentifierPair ContextBuilder::identifierPairForNode(SemiReservedIdentifierAst* id ) 0205 { 0206 if (!id) { 0207 return qMakePair(IndexedString(), QualifiedIdentifier()); 0208 } 0209 const QString ret = stringForNode(id); 0210 0211 return qMakePair(IndexedString(ret), QualifiedIdentifier(ret.toLower())); 0212 } 0213 0214 IdentifierPair ContextBuilder::identifierPairForNode(ReservedNonModifierIdentifierAst* id ) 0215 { 0216 if (!id) { 0217 return qMakePair(IndexedString(), QualifiedIdentifier()); 0218 } 0219 const QString ret = stringForNode(id); 0220 0221 return qMakePair(IndexedString(ret), QualifiedIdentifier(ret.toLower())); 0222 } 0223 0224 QString ContextBuilder::stringForNode(IdentifierAst* node) const 0225 { 0226 return m_editor->parseSession()->symbol(node->string); 0227 } 0228 0229 QString ContextBuilder::stringForNode(SemiReservedIdentifierAst* node) const 0230 { 0231 return m_editor->parseSession()->symbol(node->string); 0232 } 0233 0234 QString ContextBuilder::stringForNode(ReservedNonModifierIdentifierAst* node) const 0235 { 0236 return m_editor->parseSession()->symbol(node->string); 0237 } 0238 0239 QString ContextBuilder::stringForNode(VariableIdentifierAst* node) const 0240 { 0241 return m_editor->parseSession()->symbol(node->variable); 0242 } 0243 0244 void ContextBuilder::visitClassDeclarationStatement(ClassDeclarationStatementAst* node) 0245 { 0246 openContext(node, editorFindRange(node, node), DUContext::Class, identifierPairForNode(node->className).second); 0247 classContextOpened(currentContext()); //This callback is needed, so we can set the internal context and so find the declaration for the context (before closeDeclaration()) 0248 DefaultVisitor::visitClassDeclarationStatement(node); 0249 closeContext(); 0250 } 0251 0252 void ContextBuilder::classContextOpened(DUContext* context) 0253 { 0254 Q_UNUSED(context); 0255 } 0256 0257 void ContextBuilder::visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst* node) 0258 { 0259 openContext(node, editorFindRange(node, node), DUContext::Class, identifierPairForNode(node->interfaceName).second); 0260 classContextOpened(currentContext()); //This callback is needed, so we can set the internal context and so find the declaration for the context (before closeDeclaration()) 0261 DefaultVisitor::visitInterfaceDeclarationStatement(node); 0262 closeContext(); 0263 } 0264 0265 void ContextBuilder::visitTraitDeclarationStatement(TraitDeclarationStatementAst* node) 0266 { 0267 openContext(node, editorFindRange(node, node), DUContext::Class, identifierPairForNode(node->traitName).second); 0268 classContextOpened(currentContext()); //This callback is needed, so we can set the internal context and so find the declaration for the context (before closeDeclaration()) 0269 DefaultVisitor::visitTraitDeclarationStatement(node); 0270 closeContext(); 0271 } 0272 0273 void ContextBuilder::visitClassStatement(ClassStatementAst *node) 0274 { 0275 visitOptionalModifiers(node->modifiers); 0276 if (node->methodName) { 0277 //method declaration 0278 DUContext* parameters = openContext(node->parameters, DUContext::Function, identifierForNode(node->methodName)); 0279 Q_ASSERT(!parameters->inSymbolTable()); 0280 0281 visitParameterList(node->parameters); 0282 if (node->returnType) { 0283 visitReturnType(node->returnType); 0284 } 0285 closeContext(); 0286 0287 if ( !m_isInternalFunctions && node->methodBody ) { 0288 // the internal functions file has only empty method bodies, so skip them 0289 DUContext* body = openContext(node->methodBody, DUContext::Other, identifierForNode(node->methodName)); 0290 if (compilingContexts()) { 0291 DUChainWriteLocker lock(DUChain::lock()); 0292 body->addImportedParentContext(parameters); 0293 body->setInSymbolTable(false); 0294 } 0295 visitMethodBody(node->methodBody); 0296 closeContext(); 0297 } 0298 } else { 0299 //member-variable or const 0300 DefaultVisitor::visitClassStatement(node); 0301 } 0302 } 0303 0304 void ContextBuilder::visitFunctionDeclarationStatement(FunctionDeclarationStatementAst* node) 0305 { 0306 visitIdentifier(node->functionName); 0307 0308 DUContext* parameters = openContext(node->parameters, DUContext::Function, node->functionName); 0309 Q_ASSERT(!parameters->inSymbolTable()); 0310 0311 visitParameterList(node->parameters); 0312 if (node->returnType) { 0313 visitReturnType(node->returnType); 0314 } 0315 closeContext(); 0316 0317 if ( !m_isInternalFunctions && node->functionBody ) { 0318 // the internal functions file has only empty method bodies, so skip them 0319 DUContext* body = openContext(node->functionBody, DUContext::Other, node->functionName); 0320 if (compilingContexts()) { 0321 DUChainWriteLocker lock(DUChain::lock()); 0322 body->addImportedParentContext(parameters); 0323 body->setInSymbolTable(false); 0324 } 0325 visitInnerStatementList(node->functionBody); 0326 closeContext(); 0327 } 0328 } 0329 0330 void ContextBuilder::visitClosure(ClosureAst* node) 0331 { 0332 DUContext* parameters = openContext(node->parameters, DUContext::Function); 0333 Q_ASSERT(!parameters->inSymbolTable()); 0334 0335 visitParameterList(node->parameters); 0336 if (node->returnType) { 0337 visitReturnType(node->returnType); 0338 } 0339 closeContext(); 0340 0341 DUContext* imported = nullptr; 0342 if ( node->lexicalVars ) { 0343 imported = openContext(node->lexicalVars, DUContext::Other); 0344 Q_ASSERT(!imported->inSymbolTable()); 0345 0346 visitLexicalVarList(node->lexicalVars); 0347 closeContext(); 0348 } 0349 0350 if ( !m_isInternalFunctions && node->functionBody ) { 0351 // the internal functions file has only empty method bodies, so skip them 0352 DUContext* body = openContext(node->functionBody, DUContext::Other); 0353 if (compilingContexts()) { 0354 DUChainWriteLocker lock; 0355 body->addImportedParentContext(parameters); 0356 if (imported) { 0357 body->addImportedParentContext(imported, CursorInRevision::invalid(), true); 0358 } 0359 body->setInSymbolTable(false); 0360 } 0361 visitInnerStatementList(node->functionBody); 0362 closeContext(); 0363 } 0364 } 0365 0366 void ContextBuilder::visitNamespaceDeclarationStatement(NamespaceDeclarationStatementAst* node) 0367 { 0368 // close existing namespace context 0369 if (m_openNamespaces) { 0370 closeNamespaces(m_openNamespaces); 0371 m_openNamespaces = nullptr; 0372 } 0373 0374 if ( !node->namespaceNameSequence ) { 0375 if (node->body) { 0376 // global namespace 0377 DefaultVisitor::visitInnerStatementList(node->body); 0378 } 0379 return; 0380 } 0381 0382 { // open 0383 ///TODO: support \ as separator 0384 0385 RangeInRevision bodyRange; 0386 if (node->body) { 0387 bodyRange = editorFindRange(node->body, node->body); 0388 } else { 0389 bodyRange = RangeInRevision(m_editor->findPosition(node->endToken), currentContext()->topContext()->range().end); 0390 } 0391 const KDevPG::ListNode< IdentifierAst* >* it = node->namespaceNameSequence->front(); 0392 do { 0393 openNamespace(node, it->element, identifierPairForNode(it->element), bodyRange); 0394 } while(it->hasNext() && (it = it->next)); 0395 } 0396 0397 if (node->body) { 0398 DefaultVisitor::visitInnerStatementList(node->body); 0399 closeNamespaces(node); 0400 } else { 0401 m_openNamespaces = node; 0402 } 0403 } 0404 0405 void ContextBuilder::closeNamespaces(NamespaceDeclarationStatementAst* namespaces) 0406 { 0407 ///TODO: support \ as separator 0408 const KDevPG::ListNode< IdentifierAst* >* it = namespaces->namespaceNameSequence->front(); 0409 do { 0410 Q_ASSERT(currentContext()->type() == DUContext::Namespace); 0411 closeNamespace(namespaces, it->element, identifierPairForNode(it->element)); 0412 } while(it->hasNext() && (it = it->next)); 0413 } 0414 0415 void ContextBuilder::openNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier, const RangeInRevision& range) 0416 { 0417 if ( node == parent->namespaceNameSequence->back()->element ) { 0418 openContext(node, range, DUContext::Namespace, identifier.second); 0419 } else { 0420 openContext(node, range, DUContext::Namespace, identifier.second); 0421 } 0422 } 0423 0424 void ContextBuilder::closeNamespace(NamespaceDeclarationStatementAst* /*parent*/, IdentifierAst* /*node*/, const IdentifierPair& /*identifier*/) 0425 { 0426 closeContext(); 0427 } 0428 0429 void ContextBuilder::addBaseType(NamespacedIdentifierAst * identifier) 0430 { 0431 DUChainWriteLocker lock(DUChain::lock()); 0432 0433 Q_ASSERT(currentContext()->type() == DUContext::Class); 0434 0435 ClassDeclaration* currentClass = dynamic_cast<ClassDeclaration*>(currentContext()->owner()); 0436 0437 ClassDeclaration* baseClass = dynamic_cast<ClassDeclaration*>( 0438 findDeclarationImport(ClassDeclarationType, identifierForNamespace(identifier, m_editor)).data() ); 0439 0440 if (currentClass && baseClass) { 0441 if (DUContext* baseContext = baseClass->logicalInternalContext(nullptr)) { 0442 // prevent circular context imports which could lead to segfaults 0443 if (!baseContext->imports(currentContext()) && !currentContext()->imports(baseContext)) { 0444 currentContext()->addImportedParentContext(baseContext); 0445 BaseClassInstance base; 0446 base.baseClass = baseClass->indexedType(); 0447 base.access = Declaration::Public; 0448 base.virtualInheritance = false; 0449 currentClass->addBaseClass(base); 0450 } else if (m_reportErrors && baseClass->classType() != ClassDeclarationData::Interface) { 0451 reportError(i18n("Circular inheritance of %1 and %2", currentClass->toString(), baseClass->toString()), identifier); 0452 } 0453 } 0454 } 0455 if (!baseClass) { 0456 qCDebug(DUCHAIN) << "unresolved identifier"; 0457 m_hadUnresolvedIdentifiers = true; 0458 } 0459 } 0460 0461 0462 void ContextBuilder::visitUnaryExpression(UnaryExpressionAst* node) 0463 { 0464 DefaultVisitor::visitUnaryExpression(node); 0465 if (!compilingContexts()) { 0466 return; 0467 } 0468 IndexedString includeFile = getIncludeFileForNode(node, m_editor); 0469 0470 if ( !includeFile.isEmpty() ) { 0471 DUChainWriteLocker lock(DUChain::lock()); 0472 TopDUContext *top = DUChain::self()->chainForDocument(includeFile); 0473 if (top) { 0474 currentContext()->topContext()->addImportedParentContext(top); 0475 currentContext()->topContext()->parsingEnvironmentFile() 0476 ->addModificationRevisions(top->parsingEnvironmentFile()->allModificationRevisions()); 0477 } 0478 } 0479 } 0480 0481 void ContextBuilder::visitFunctionCallParameterListElement(FunctionCallParameterListElementAst* node) 0482 { 0483 DefaultVisitor::visitFunctionCallParameterListElement(node); 0484 0485 setContextOnNode(node, currentContext()); 0486 } 0487 0488 void ContextBuilder::reportError(const QString& errorMsg, AstNode* node, IProblem::Severity severity) 0489 { 0490 reportError(errorMsg, m_editor->findRange(node), severity); 0491 } 0492 0493 void ContextBuilder::reportError(const QString& errorMsg, QList< AstNode* > nodes, IProblem::Severity severity) 0494 { 0495 RangeInRevision range = RangeInRevision::invalid(); 0496 foreach ( AstNode* node, nodes ) { 0497 if ( !range.isValid() ) { 0498 range = m_editor->findRange(node); 0499 } else { 0500 range.end = m_editor->findPosition(node->endToken); 0501 } 0502 } 0503 reportError(errorMsg, range, severity); 0504 } 0505 0506 void ContextBuilder::reportError(const QString& errorMsg, RangeInRevision range, IProblem::Severity severity) 0507 { 0508 auto *p = new Problem(); 0509 p->setSeverity(severity); 0510 p->setSource(IProblem::DUChainBuilder); 0511 p->setDescription(errorMsg); 0512 p->setFinalLocation(DocumentRange(m_editor->parseSession()->currentDocument(), 0513 range.castToSimpleRange())); 0514 { 0515 DUChainWriteLocker lock(DUChain::lock()); 0516 qCDebug(DUCHAIN) << "Problem" << p->description() << p->finalLocation(); 0517 currentContext()->topContext()->addProblem(ProblemPointer(p)); 0518 } 0519 } 0520 0521 DeclarationPointer ContextBuilder::findDeclarationImport(DeclarationType declarationType, 0522 IdentifierAst* node) 0523 { 0524 QualifiedIdentifier id; 0525 if ( declarationType == ClassDeclarationType || declarationType == FunctionDeclarationType ) { 0526 id = identifierPairForNode(node).second; 0527 } else { 0528 id = identifierForNode(node); 0529 } 0530 return findDeclarationImportHelper(currentContext(), id, declarationType); 0531 } 0532 0533 DeclarationPointer ContextBuilder::findDeclarationImport(DeclarationType declarationType, 0534 SemiReservedIdentifierAst* node, 0535 DeclarationScope declarationScope) 0536 { 0537 QualifiedIdentifier id; 0538 if ( declarationType == ClassDeclarationType || declarationType == FunctionDeclarationType ) { 0539 id = identifierPairForNode(node).second; 0540 } else { 0541 id = identifierForNode(node); 0542 } 0543 0544 if (declarationScope == GlobalScope) { 0545 id.setExplicitlyGlobal(true); 0546 } 0547 0548 return findDeclarationImportHelper(currentContext(), id, declarationType); 0549 } 0550 0551 DeclarationPointer ContextBuilder::findDeclarationImport(DeclarationType declarationType, 0552 VariableIdentifierAst* node) 0553 { 0554 return findDeclarationImportHelper(currentContext(), identifierForNode(node), declarationType); 0555 } 0556 0557 DeclarationPointer ContextBuilder::findDeclarationImport(DeclarationType declarationType, 0558 const QualifiedIdentifier &identifier) 0559 { 0560 return findDeclarationImportHelper(currentContext(), identifier, declarationType); 0561 } 0562 0563 }