diff --git a/duchain/builders/contextbuilder.cpp b/duchain/builders/contextbuilder.cpp index 1d9d2a2..ebcbbfb 100644 --- a/duchain/builders/contextbuilder.cpp +++ b/duchain/builders/contextbuilder.cpp @@ -1,559 +1,565 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "contextbuilder.h" #include #include #include #include #include #include #include #include #include #include #include "../editorintegrator.h" #include "../helper.h" #include "../phpducontext.h" #include "../parser/parsesession.h" #include "../parser/phpast.h" #include using namespace KDevelop; namespace Php { ContextBuilder::ContextBuilder() : m_isInternalFunctions(false), m_reportErrors(true), m_mapAst(false), m_hadUnresolvedIdentifiers(false), m_editor(nullptr), m_openNamespaces(nullptr) { } ContextBuilder::~ContextBuilder() { } EditorIntegrator* ContextBuilder::editor() const { return m_editor; } ReferencedTopDUContext ContextBuilder::build(const IndexedString& url, AstNode* node, ReferencedTopDUContext updateContext) { m_isInternalFunctions = url == internalFunctionFile(); if ( m_isInternalFunctions ) { m_reportErrors = false; } else if ( ICore::self() ) { m_reportErrors = ICore::self()->languageController()->completionSettings()->highlightSemanticProblems(); } if (!updateContext) { DUChainReadLocker lock(DUChain::lock()); updateContext = DUChain::self()->chainForDocument(url); } if (updateContext) { qCDebug(DUCHAIN) << "re-compiling" << url.str(); DUChainWriteLocker lock(DUChain::lock()); updateContext->clearImportedParentContexts(); updateContext->parsingEnvironmentFile()->clearModificationRevisions(); updateContext->clearProblems(); updateContext->updateImportsCache(); } else { qCDebug(DUCHAIN) << "compiling" << url.str(); } ReferencedTopDUContext top = ContextBuilderBase::build(url, node, updateContext); { DUChainWriteLocker lock(DUChain::lock()); top->updateImportsCache(); } return top; } bool ContextBuilder::hadUnresolvedIdentifiers() const { return m_hadUnresolvedIdentifiers; } void ContextBuilder::startVisiting(AstNode* node) { if (compilingContexts()) { TopDUContext* top = dynamic_cast(currentContext()); Q_ASSERT(top); { DUChainWriteLocker lock(DUChain::lock()); top->updateImportsCache(); //Mark that we will use a cached import-structure } bool hasImports; { DUChainReadLocker lock(DUChain::lock()); hasImports = !top->importedParentContexts().isEmpty(); } if (!hasImports && top->url() != internalFunctionFile()) { DUChainWriteLocker lock(DUChain::lock()); TopDUContext* import = DUChain::self()->chainForDocument(internalFunctionFile()); if (!import) { qWarning() << "importing internalFunctions failed" << currentContext()->url().str(); Q_ASSERT(false); } else { top->addImportedParentContext(import); top->updateImportsCache(); } } } visitNode(node); if (m_openNamespaces) { closeNamespaces(m_openNamespaces); m_openNamespaces = nullptr; } } DUContext* ContextBuilder::newContext(const RangeInRevision& range) { return new PhpDUContext(range, currentContext()); } TopDUContext* ContextBuilder::newTopContext(const RangeInRevision& range, ParsingEnvironmentFile* file) { if (!file) { file = new ParsingEnvironmentFile(m_editor->parseSession()->currentDocument()); /// Indexed string for 'Php', identifies environment files from this language plugin static const IndexedString phpLangString("Php"); file->setLanguage(phpLangString); } TopDUContext* ret = new PhpDUContext(m_editor->parseSession()->currentDocument(), range, file); ret->setType(DUContext::Global); return ret; } void ContextBuilder::setContextOnNode(AstNode* node, DUContext* ctx) { node->ducontext = ctx; } DUContext* ContextBuilder::contextFromNode(AstNode* node) { return node->ducontext; } RangeInRevision ContextBuilder::editorFindRange(AstNode* fromRange, AstNode* toRange) { return m_editor->findRange(fromRange, toRange ? toRange : fromRange); } CursorInRevision ContextBuilder::startPos(AstNode* node) { return m_editor->findPosition(node->startToken, EditorIntegrator::FrontEdge); } QualifiedIdentifier ContextBuilder::identifierForNode(IdentifierAst* id) { if (!id) return QualifiedIdentifier(); return QualifiedIdentifier(stringForNode(id)); } QualifiedIdentifier ContextBuilder::identifierForNode(SemiReservedIdentifierAst* id) { if (!id) return QualifiedIdentifier(); return QualifiedIdentifier(stringForNode(id)); } QualifiedIdentifier ContextBuilder::identifierForNode(VariableIdentifierAst* id) { if (!id) return QualifiedIdentifier(); QString ret(stringForNode(id)); ret = ret.mid(1); //cut off $ return QualifiedIdentifier(ret); } IdentifierPair ContextBuilder::identifierPairForNode(IdentifierAst* id ) { if (!id) { return qMakePair(IndexedString(), QualifiedIdentifier()); } const QString ret = stringForNode(id); return qMakePair(IndexedString(ret), QualifiedIdentifier(ret.toLower())); } IdentifierPair ContextBuilder::identifierPairForNode(SemiReservedIdentifierAst* id ) { if (!id) { return qMakePair(IndexedString(), QualifiedIdentifier()); } const QString ret = stringForNode(id); return qMakePair(IndexedString(ret), QualifiedIdentifier(ret.toLower())); } IdentifierPair ContextBuilder::identifierPairForNode(ReservedNonModifierIdentifierAst* id ) { if (!id) { return qMakePair(IndexedString(), QualifiedIdentifier()); } const QString ret = stringForNode(id); return qMakePair(IndexedString(ret), QualifiedIdentifier(ret.toLower())); } QString ContextBuilder::stringForNode(IdentifierAst* node) const { return m_editor->parseSession()->symbol(node->string); } QString ContextBuilder::stringForNode(SemiReservedIdentifierAst* node) const { return m_editor->parseSession()->symbol(node->string); } QString ContextBuilder::stringForNode(ReservedNonModifierIdentifierAst* node) const { return m_editor->parseSession()->symbol(node->string); } QString ContextBuilder::stringForNode(VariableIdentifierAst* node) const { return m_editor->parseSession()->symbol(node->variable); } void ContextBuilder::visitClassDeclarationStatement(ClassDeclarationStatementAst* node) { openContext(node, editorFindRange(node, node), DUContext::Class, identifierPairForNode(node->className).second); classContextOpened(currentContext()); //This callback is needed, so we can set the internal context and so find the declaration for the context (before closeDeclaration()) DefaultVisitor::visitClassDeclarationStatement(node); closeContext(); } void ContextBuilder::classContextOpened(DUContext* context) { Q_UNUSED(context); } void ContextBuilder::visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst* node) { openContext(node, editorFindRange(node, node), DUContext::Class, identifierPairForNode(node->interfaceName).second); classContextOpened(currentContext()); //This callback is needed, so we can set the internal context and so find the declaration for the context (before closeDeclaration()) DefaultVisitor::visitInterfaceDeclarationStatement(node); closeContext(); } void ContextBuilder::visitTraitDeclarationStatement(TraitDeclarationStatementAst* node) { openContext(node, editorFindRange(node, node), DUContext::Class, identifierPairForNode(node->traitName).second); classContextOpened(currentContext()); //This callback is needed, so we can set the internal context and so find the declaration for the context (before closeDeclaration()) DefaultVisitor::visitTraitDeclarationStatement(node); closeContext(); } void ContextBuilder::visitClassStatement(ClassStatementAst *node) { visitOptionalModifiers(node->modifiers); if (node->methodName) { //method declaration DUContext* parameters = openContext(node->parameters, DUContext::Function, identifierForNode(node->methodName)); Q_ASSERT(!parameters->inSymbolTable()); visitParameterList(node->parameters); if (node->returnType) { visitReturnType(node->returnType); } closeContext(); if ( !m_isInternalFunctions && node->methodBody ) { // the internal functions file has only empty method bodies, so skip them DUContext* body = openContext(node->methodBody, DUContext::Other, identifierForNode(node->methodName)); if (compilingContexts()) { DUChainWriteLocker lock(DUChain::lock()); body->addImportedParentContext(parameters); body->setInSymbolTable(false); } visitMethodBody(node->methodBody); closeContext(); } } else { //member-variable or const DefaultVisitor::visitClassStatement(node); } } void ContextBuilder::visitFunctionDeclarationStatement(FunctionDeclarationStatementAst* node) { visitIdentifier(node->functionName); DUContext* parameters = openContext(node->parameters, DUContext::Function, node->functionName); Q_ASSERT(!parameters->inSymbolTable()); visitParameterList(node->parameters); if (node->returnType) { visitReturnType(node->returnType); } closeContext(); if ( !m_isInternalFunctions && node->functionBody ) { // the internal functions file has only empty method bodies, so skip them DUContext* body = openContext(node->functionBody, DUContext::Other, node->functionName); if (compilingContexts()) { DUChainWriteLocker lock(DUChain::lock()); body->addImportedParentContext(parameters); body->setInSymbolTable(false); } visitInnerStatementList(node->functionBody); closeContext(); } } void ContextBuilder::visitClosure(ClosureAst* node) { DUContext* parameters = openContext(node->parameters, DUContext::Function); Q_ASSERT(!parameters->inSymbolTable()); visitParameterList(node->parameters); if (node->returnType) { visitReturnType(node->returnType); } closeContext(); DUContext* imported = nullptr; if ( node->lexicalVars ) { imported = openContext(node->lexicalVars, DUContext::Other); Q_ASSERT(!imported->inSymbolTable()); visitLexicalVarList(node->lexicalVars); closeContext(); } if ( !m_isInternalFunctions && node->functionBody ) { // the internal functions file has only empty method bodies, so skip them DUContext* body = openContext(node->functionBody, DUContext::Other); if (compilingContexts()) { DUChainWriteLocker lock; body->addImportedParentContext(parameters); if (imported) { body->addImportedParentContext(imported, CursorInRevision::invalid(), true); } body->setInSymbolTable(false); } visitInnerStatementList(node->functionBody); closeContext(); } } void ContextBuilder::visitNamespaceDeclarationStatement(NamespaceDeclarationStatementAst* node) { // close existing namespace context if (m_openNamespaces) { closeNamespaces(m_openNamespaces); m_openNamespaces = nullptr; } if ( !node->namespaceNameSequence ) { if (node->body) { // global namespace DefaultVisitor::visitInnerStatementList(node->body); } return; } { // open ///TODO: support \ as separator RangeInRevision bodyRange; if (node->body) { bodyRange = editorFindRange(node->body, node->body); } else { bodyRange = RangeInRevision(m_editor->findPosition(node->endToken), currentContext()->topContext()->range().end); } const KDevPG::ListNode< IdentifierAst* >* it = node->namespaceNameSequence->front(); do { openNamespace(node, it->element, identifierPairForNode(it->element), bodyRange); } while(it->hasNext() && (it = it->next)); } if (node->body) { DefaultVisitor::visitInnerStatementList(node->body); closeNamespaces(node); } else { m_openNamespaces = node; } } void ContextBuilder::closeNamespaces(NamespaceDeclarationStatementAst* namespaces) { ///TODO: support \ as separator const KDevPG::ListNode< IdentifierAst* >* it = namespaces->namespaceNameSequence->front(); do { Q_ASSERT(currentContext()->type() == DUContext::Namespace); closeNamespace(namespaces, it->element, identifierPairForNode(it->element)); } while(it->hasNext() && (it = it->next)); } void ContextBuilder::openNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier, const RangeInRevision& range) { if ( node == parent->namespaceNameSequence->back()->element ) { openContext(node, range, DUContext::Namespace, identifier.second); } else { openContext(node, range, DUContext::Namespace, identifier.second); } } void ContextBuilder::closeNamespace(NamespaceDeclarationStatementAst* /*parent*/, IdentifierAst* /*node*/, const IdentifierPair& /*identifier*/) { closeContext(); } void ContextBuilder::addBaseType(NamespacedIdentifierAst * identifier) { DUChainWriteLocker lock(DUChain::lock()); Q_ASSERT(currentContext()->type() == DUContext::Class); ClassDeclaration* currentClass = dynamic_cast(currentContext()->owner()); ClassDeclaration* baseClass = dynamic_cast( findDeclarationImport(ClassDeclarationType, identifierForNamespace(identifier, m_editor)).data() ); if (currentClass && baseClass) { if (DUContext* baseContext = baseClass->logicalInternalContext(nullptr)) { // prevent circular context imports which could lead to segfaults if (!baseContext->imports(currentContext()) && !currentContext()->imports(baseContext)) { currentContext()->addImportedParentContext(baseContext); BaseClassInstance base; base.baseClass = baseClass->indexedType(); base.access = Declaration::Public; base.virtualInheritance = false; currentClass->addBaseClass(base); } else if (m_reportErrors && baseClass->classType() != ClassDeclarationData::Interface) { reportError(i18n("Circular inheritance of %1 and %2", currentClass->toString(), baseClass->toString()), identifier); } } } if (!baseClass) { qCDebug(DUCHAIN) << "unresolved identifier"; m_hadUnresolvedIdentifiers = true; } } void ContextBuilder::visitUnaryExpression(UnaryExpressionAst* node) { DefaultVisitor::visitUnaryExpression(node); if (!compilingContexts()) { return; } IndexedString includeFile = getIncludeFileForNode(node, m_editor); if ( !includeFile.isEmpty() ) { DUChainWriteLocker lock(DUChain::lock()); TopDUContext *top = DUChain::self()->chainForDocument(includeFile); if (top) { currentContext()->topContext()->addImportedParentContext(top); currentContext()->topContext()->parsingEnvironmentFile() ->addModificationRevisions(top->parsingEnvironmentFile()->allModificationRevisions()); } } } void ContextBuilder::reportError(const QString& errorMsg, AstNode* node, IProblem::Severity severity) { reportError(errorMsg, m_editor->findRange(node), severity); } void ContextBuilder::reportError(const QString& errorMsg, QList< AstNode* > nodes, IProblem::Severity severity) { RangeInRevision range = RangeInRevision::invalid(); foreach ( AstNode* node, nodes ) { if ( !range.isValid() ) { range = m_editor->findRange(node); } else { range.end = m_editor->findPosition(node->endToken); } } reportError(errorMsg, range, severity); } void ContextBuilder::reportError(const QString& errorMsg, RangeInRevision range, IProblem::Severity severity) { auto *p = new Problem(); p->setSeverity(severity); p->setSource(IProblem::DUChainBuilder); p->setDescription(errorMsg); p->setFinalLocation(DocumentRange(m_editor->parseSession()->currentDocument(), range.castToSimpleRange())); { DUChainWriteLocker lock(DUChain::lock()); qCDebug(DUCHAIN) << "Problem" << p->description() << p->finalLocation(); currentContext()->topContext()->addProblem(ProblemPointer(p)); } } DeclarationPointer ContextBuilder::findDeclarationImport(DeclarationType declarationType, IdentifierAst* node) { QualifiedIdentifier id; if ( declarationType == ClassDeclarationType || declarationType == FunctionDeclarationType ) { id = identifierPairForNode(node).second; } else { id = identifierForNode(node); } return findDeclarationImportHelper(currentContext(), id, declarationType); } DeclarationPointer ContextBuilder::findDeclarationImport(DeclarationType declarationType, - SemiReservedIdentifierAst* node) + SemiReservedIdentifierAst* node, + DeclarationScope declarationScope) { QualifiedIdentifier id; if ( declarationType == ClassDeclarationType || declarationType == FunctionDeclarationType ) { id = identifierPairForNode(node).second; } else { id = identifierForNode(node); } + + if (declarationScope == GlobalScope) { + id.setExplicitlyGlobal(true); + } + return findDeclarationImportHelper(currentContext(), id, declarationType); } DeclarationPointer ContextBuilder::findDeclarationImport(DeclarationType declarationType, VariableIdentifierAst* node) { return findDeclarationImportHelper(currentContext(), identifierForNode(node), declarationType); } DeclarationPointer ContextBuilder::findDeclarationImport(DeclarationType declarationType, const QualifiedIdentifier &identifier) { return findDeclarationImportHelper(currentContext(), identifier, declarationType); } } diff --git a/duchain/builders/contextbuilder.h b/duchain/builders/contextbuilder.h index 9a31d61..74ea509 100644 --- a/duchain/builders/contextbuilder.h +++ b/duchain/builders/contextbuilder.h @@ -1,134 +1,134 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef CONTEXTBUILDER_H #define CONTEXTBUILDER_H #include #include #include "phpdefaultvisitor.h" #include "phpduchainexport.h" #include "editorintegrator.h" #include "helper.h" namespace Php { class EditorIntegrator; class ParseSession; typedef KDevelop::AbstractContextBuilder ContextBuilderBase; /// first is the "pretty" identifier used for printing /// second comes the all-lowercase identifier used for storage typedef QPair IdentifierPair; class KDEVPHPDUCHAIN_EXPORT ContextBuilder: public ContextBuilderBase, public DefaultVisitor { public: ContextBuilder(); ~ContextBuilder() override; KDevelop::ReferencedTopDUContext build(const KDevelop::IndexedString& url, AstNode* node, KDevelop::ReferencedTopDUContext updateContext = KDevelop::ReferencedTopDUContext()) override; bool hadUnresolvedIdentifiers() const; EditorIntegrator* editor() const; protected: KDevelop::DUContext* newContext(const KDevelop::RangeInRevision& range) override; KDevelop::TopDUContext* newTopContext(const KDevelop::RangeInRevision& range, KDevelop::ParsingEnvironmentFile* file = nullptr) override; void startVisiting(AstNode* node) override; void setContextOnNode(AstNode* node, KDevelop::DUContext* ctx) override; KDevelop::DUContext* contextFromNode(AstNode* node) override; KDevelop::RangeInRevision editorFindRange(AstNode* fromRange, AstNode* toRange = nullptr) override; /// Find Cursor for start of a node, useful to limit findLocalDeclarations() searches. KDevelop::CursorInRevision startPos( AstNode* node); KDevelop::QualifiedIdentifier identifierForNode(IdentifierAst* id) override; KDevelop::QualifiedIdentifier identifierForNode(SemiReservedIdentifierAst* id); KDevelop::QualifiedIdentifier identifierForNode(VariableIdentifierAst* id); IdentifierPair identifierPairForNode(IdentifierAst* id); IdentifierPair identifierPairForNode(SemiReservedIdentifierAst* id); IdentifierPair identifierPairForNode(ReservedNonModifierIdentifierAst* id); QString stringForNode(IdentifierAst* node) const; QString stringForNode(SemiReservedIdentifierAst* node) const; QString stringForNode(ReservedNonModifierIdentifierAst* node) const; QString stringForNode(VariableIdentifierAst* node) const; void visitClassDeclarationStatement(ClassDeclarationStatementAst*) override; void visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst* node) override; void visitTraitDeclarationStatement(TraitDeclarationStatementAst* node) override; void visitClassStatement(ClassStatementAst *node) override; void visitFunctionDeclarationStatement(FunctionDeclarationStatementAst* node) override; void visitClosure(ClosureAst* node) override; void visitUnaryExpression(UnaryExpressionAst* node) override; /** * don't overload in other builders, use @c openNamespace and @c closeNamespace instead. */ void visitNamespaceDeclarationStatement(NamespaceDeclarationStatementAst* node) override; virtual void openNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier, const KDevelop::RangeInRevision& range); virtual void closeNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier); virtual void addBaseType(NamespacedIdentifierAst * identifier); virtual void classContextOpened(KDevelop::DUContext* context); /// Report @p errorMsg with the range of @p node /// @see void reportError(const QString& errorMsg, KDevelop::SimpleRange range); void reportError(const QString& errorMsg, AstNode* node, KDevelop::IProblem::Severity severity = KDevelop::IProblem::Error); /// Report @p errorMsg with the range encompassing all nodes in @p nodes /// @see void reportError(const QString& errorMsg, KDevelop::SimpleRange range); void reportError(const QString& errorMsg, QList nodes, KDevelop::IProblem::Severity severity = KDevelop::IProblem::Error); /// Report @p errorMsg with range @p range void reportError(const QString& errorMsg, KDevelop::RangeInRevision range, KDevelop::IProblem::Severity severity = KDevelop::IProblem::Error); KDevelop::DeclarationPointer findDeclarationImport(DeclarationType declarationType, IdentifierAst* node); - KDevelop::DeclarationPointer findDeclarationImport(DeclarationType declarationType, SemiReservedIdentifierAst* node); + KDevelop::DeclarationPointer findDeclarationImport(DeclarationType declarationType, SemiReservedIdentifierAst* node, DeclarationScope declarationScope = LocalScope); KDevelop::DeclarationPointer findDeclarationImport(DeclarationType declarationType, VariableIdentifierAst* node); KDevelop::DeclarationPointer findDeclarationImport(DeclarationType declarationType, const KDevelop::QualifiedIdentifier &identifier); /// internal functions file should not be checked for errors and can get some optimizations bool m_isInternalFunctions; /// Whether semantic problems should get reported bool m_reportErrors; ///TODO: push this into kdevplatform bool m_mapAst; bool m_hadUnresolvedIdentifiers; EditorIntegrator* m_editor; private: void closeNamespaces(NamespaceDeclarationStatementAst* namespaces); NamespaceDeclarationStatementAst* m_openNamespaces; }; } #endif diff --git a/duchain/builders/declarationbuilder.cpp b/duchain/builders/declarationbuilder.cpp index 219b02a..8d71c2e 100644 --- a/duchain/builders/declarationbuilder.cpp +++ b/duchain/builders/declarationbuilder.cpp @@ -1,1645 +1,1661 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "declarationbuilder.h" #include #include #include #include #include #include #include #include #include #include "../declarations/variabledeclaration.h" #include "../declarations/classmethoddeclaration.h" #include "../declarations/classdeclaration.h" #include "../declarations/functiondeclaration.h" #include "../declarations/namespacedeclaration.h" #include "../declarations/namespacealiasdeclaration.h" #include "../declarations/traitmethodaliasdeclaration.h" #include "../declarations/traitmemberaliasdeclaration.h" #include "../parser/phpast.h" #include "../parser/parsesession.h" #include "../helper.h" #include "../expressionvisitor.h" #include "predeclarationbuilder.h" #include #define ifDebug(x) using namespace KDevelop; namespace Php { DeclarationBuilder::FindVariableResults::FindVariableResults() : find(true) , isArray(false) , node(nullptr) { } void DeclarationBuilder::getVariableIdentifier(VariableAst* node, QualifiedIdentifier &identifier, QualifiedIdentifier &parent, AstNode* &targetNode, bool &arrayAccess) { parent = QualifiedIdentifier(); if ( node->variablePropertiesSequence ) { // at least one "->" in the assigment target // => find he parent of the target // => find the target (last object property) if ( node->variablePropertiesSequence->count() == 1 ) { // $parent->target ///TODO: $parent[0]->target = ... (we don't know the type of [0] yet, need proper array handling first) if ( node->var && node->var->baseVariable && node->var->baseVariable->var && !node->var->baseVariable->offsetItemsSequence ) { parent = identifierForNode( node->var->baseVariable->var->variable ); } } else { // $var->...->parent->target ///TODO: $var->...->parent[0]->target = ... (we don't know the type of [0] yet, need proper array handling first) const KDevPG::ListNode< VariableObjectPropertyAst* >* parentNode = node->variablePropertiesSequence->at( node->variablePropertiesSequence->count() - 2 ); if ( parentNode->element && parentNode->element->variableProperty && parentNode->element->variableProperty->objectProperty && parentNode->element->variableProperty->objectProperty->objectDimList && parentNode->element->variableProperty->objectProperty->objectDimList->variableName && !parentNode->element->variableProperty->objectProperty->objectDimList->offsetItemsSequence ) { parent = identifierForNode( parentNode->element->variableProperty->objectProperty->objectDimList->variableName->name ); } } if ( !parent.isEmpty() ) { const KDevPG::ListNode< VariableObjectPropertyAst* >* tNode = node->variablePropertiesSequence->at( node->variablePropertiesSequence->count() - 1 ); if ( tNode->element && tNode->element->variableProperty && tNode->element->variableProperty->objectProperty && tNode->element->variableProperty->objectProperty->objectDimList && tNode->element->variableProperty->objectProperty->objectDimList->variableName ) { arrayAccess = (bool) tNode->element->variableProperty->objectProperty->objectDimList->offsetItemsSequence; identifier = identifierForNode( tNode->element->variableProperty->objectProperty->objectDimList->variableName->name ); targetNode = tNode->element->variableProperty->objectProperty->objectDimList->variableName->name; } } } else { // simple assignment to $var if ( node->var && node->var->baseVariable && node->var->baseVariable->var ) { arrayAccess = (bool) node->var->baseVariable->offsetItemsSequence; identifier = identifierForNode( node->var->baseVariable->var->variable ); targetNode = node->var->baseVariable->var->variable; } } } ReferencedTopDUContext DeclarationBuilder::build(const IndexedString& url, AstNode* node, ReferencedTopDUContext updateContext) { //Run DeclarationBuilder twice, to find uses of declarations that are //declared after the use. ($a = new Foo; class Foo {}) { PreDeclarationBuilder prebuilder(&m_types, &m_functions, &m_namespaces, &m_upcomingClassVariables, m_editor); updateContext = prebuilder.build(url, node, updateContext); m_actuallyRecompiling = prebuilder.didRecompile(); } // now skip through some things the DeclarationBuilderBase (ContextBuilder) would do, // most significantly don't clear imported parent contexts m_isInternalFunctions = url == internalFunctionFile(); if ( m_isInternalFunctions ) { m_reportErrors = false; } else if ( ICore::self() ) { m_reportErrors = ICore::self()->languageController()->completionSettings()->highlightSemanticProblems(); } return ContextBuilderBase::build(url, node, updateContext); } void DeclarationBuilder::startVisiting(AstNode* node) { setRecompiling(m_actuallyRecompiling); setCompilingContexts(false); DeclarationBuilderBase::startVisiting(node); } void DeclarationBuilder::closeDeclaration() { if (currentDeclaration() && lastType()) { DUChainWriteLocker lock(DUChain::lock()); currentDeclaration()->setType(lastType()); } eventuallyAssignInternalContext(); DeclarationBuilderBase::closeDeclaration(); } void DeclarationBuilder::classContextOpened(DUContext* context) { DUChainWriteLocker lock(DUChain::lock()); currentDeclaration()->setInternalContext(context); } void DeclarationBuilder::visitClassDeclarationStatement(ClassDeclarationStatementAst * node) { ClassDeclaration* classDec = openTypeDeclaration(node->className, ClassDeclarationData::Class); openType(classDec->abstractType()); DeclarationBuilderBase::visitClassDeclarationStatement(node); { DUChainWriteLocker lock; classDec->updateCompletionCodeModelItem(); } closeType(); closeDeclaration(); m_upcomingClassVariables.clear(); QString className = classDec->prettyName().str(); if (isReservedClassName(className)) { reportError(i18n("Cannot use '%1' as class name as it is reserved", className), node->className); } } void DeclarationBuilder::visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst *node) { ClassDeclaration* interfaceDec = openTypeDeclaration(node->interfaceName, ClassDeclarationData::Interface); openType(interfaceDec->abstractType()); DeclarationBuilderBase::visitInterfaceDeclarationStatement(node); closeType(); closeDeclaration(); QString interfaceName = interfaceDec->prettyName().str(); if (isReservedClassName(interfaceName)) { reportError(i18n("Cannot use '%1' as class name as it is reserved", interfaceName), node->interfaceName); } } void DeclarationBuilder::visitTraitDeclarationStatement(TraitDeclarationStatementAst * node) { ClassDeclaration* traitDec = openTypeDeclaration(node->traitName, ClassDeclarationData::Trait); openType(traitDec->abstractType()); DeclarationBuilderBase::visitTraitDeclarationStatement(node); closeType(); closeDeclaration(); m_upcomingClassVariables.clear(); QString traitName = traitDec->prettyName().str(); if (isReservedClassName(traitName)) { reportError(i18n("Cannot use '%1' as class name as it is reserved", traitName), node->traitName); } } ClassDeclaration* DeclarationBuilder::openTypeDeclaration(IdentifierAst* name, ClassDeclarationData::ClassType type) { ClassDeclaration* classDec = m_types.value(name->string, nullptr); Q_ASSERT(classDec); isGlobalRedeclaration(identifierForNode(name), name, ClassDeclarationType); Q_ASSERT(classDec->classType() == type); Q_UNUSED(type); // seems like we have to do that manually, else the usebuilder crashes... setEncountered(classDec); openDeclarationInternal(classDec); return classDec; } bool DeclarationBuilder::isBaseMethodRedeclaration(const IdentifierPair &ids, ClassDeclaration *curClass, ClassStatementAst *node) { DUChainWriteLocker lock(DUChain::lock()); while (curClass->baseClassesSize() > 0) { StructureType::Ptr type; FOREACH_FUNCTION(const BaseClassInstance& base, curClass->baseClasses) { DUChainReadLocker lock(DUChain::lock()); type = base.baseClass.type(); if (!type) { continue; } ClassDeclaration *nextClass = dynamic_cast(type->declaration(currentContext()->topContext())); if (!nextClass || nextClass->classType() != ClassDeclarationData::Class) { type.reset(); continue; } curClass = nextClass; break; } if (!type) { break; } { if (!type->internalContext(currentContext()->topContext())) { continue; } foreach(Declaration * dec, type->internalContext(currentContext()->topContext())->findLocalDeclarations(ids.second.first(), startPos(node))) { if (dec->isFunctionDeclaration()) { ClassMethodDeclaration* func = dynamic_cast(dec); if (!func || !wasEncountered(func)) { continue; } // we cannot redeclare final classes ever if (func->isFinal()) { reportRedeclarationError(dec, node->methodName); return true; } // also we may not redeclare an already abstract method, we would have to implement it // TODO: original error message? // -> Can't inherit abstract function class::func() (previously declared in otherclass) else if (func->isAbstract() && node->modifiers->modifiers & ModifierAbstract) { reportRedeclarationError(dec, node->methodName); return true; } } } } } return false; } void DeclarationBuilder::visitClassStatement(ClassStatementAst *node) { setComment(formatComment(node, m_editor)); ClassDeclaration *parent = dynamic_cast(currentDeclaration()); Q_ASSERT(parent); if (node->methodName) { //method declaration IdentifierPair ids = identifierPairForNode(node->methodName); if (m_reportErrors) { // check for redeclarations Q_ASSERT(currentContext()->type() == DUContext::Class); bool localError = false; { DUChainWriteLocker lock(DUChain::lock()); foreach(Declaration * dec, currentContext()->findLocalDeclarations(ids.second.first(), startPos(node->methodName))) { if (wasEncountered(dec) && dec->isFunctionDeclaration() && !dynamic_cast(dec)) { reportRedeclarationError(dec, node->methodName); localError = true; break; } } } if (!localError) { // if we have no local error, check that we don't try to overwrite a final method of a baseclass isBaseMethodRedeclaration(ids, parent, node); } } { DUChainWriteLocker lock(DUChain::lock()); ClassMethodDeclaration* dec = openDefinition(ids.second, editorFindRange(node->methodName, node->methodName)); dec->setPrettyName(ids.first); dec->clearDefaultParameters(); dec->setKind(Declaration::Type); if (node->modifiers->modifiers & ModifierPublic) { dec->setAccessPolicy(Declaration::Public); } else if (node->modifiers->modifiers & ModifierProtected) { dec->setAccessPolicy(Declaration::Protected); } else if (node->modifiers->modifiers & ModifierPrivate) { dec->setAccessPolicy(Declaration::Private); } if (node->modifiers->modifiers & ModifierStatic) { dec->setStatic(true); } if (parent->classType() == ClassDeclarationData::Interface) { if (m_reportErrors) { if (node->modifiers->modifiers & ModifierFinal || node->modifiers->modifiers & ModifierAbstract) { reportError(i18n("Access type for interface method %1 must be omitted.", dec->toString()), node->modifiers); } if (!isEmptyMethodBody(node->methodBody)) { reportError(i18n("Interface function %1 cannot contain body.", dec->toString()), node->methodBody); } } // handle interface methods like abstract methods dec->setIsAbstract(true); } else { if (node->modifiers->modifiers & ModifierAbstract) { if (!m_reportErrors) { dec->setIsAbstract(true); } else { if (parent->classModifier() != ClassDeclarationData::Abstract && parent->classType() != ClassDeclarationData::Trait) { reportError(i18n("Class %1 contains abstract method %2 and must therefore be declared abstract " "or implement the method.", parent->identifier().toString(), dec->identifier().toString()), node->modifiers); } else if (!isEmptyMethodBody(node->methodBody)) { reportError(i18n("Abstract function %1 cannot contain body.", dec->toString()), node->methodBody); } else if (node->modifiers->modifiers & ModifierFinal) { reportError(i18n("Cannot use the final modifier on an abstract class member."), node->modifiers); } else { dec->setIsAbstract(true); } } } else if (node->modifiers->modifiers & ModifierFinal) { dec->setIsFinal(true); } if (m_reportErrors && !dec->isAbstract() && isEmptyMethodBody(node->methodBody)) { reportError(i18n("Non-abstract method %1 must contain body.", dec->toString()), node->methodBody); } } } DeclarationBuilderBase::visitClassStatement(node); closeDeclaration(); } else if (node->traitsSequence) { DeclarationBuilderBase::visitClassStatement(node); importTraitMethods(node); } else { if (node->modifiers) { m_currentModifers = node->modifiers->modifiers; if (m_reportErrors) { // have to report the errors here to get a good problem range if (m_currentModifers & ModifierFinal) { reportError(i18n("Properties cannot be declared final."), node->modifiers); } if (m_currentModifers & ModifierAbstract) { reportError(i18n("Properties cannot be declared abstract."), node->modifiers); } } } else { m_currentModifers = 0; } DeclarationBuilderBase::visitClassStatement(node); m_currentModifers = 0; } } void DeclarationBuilder::importTraitMethods(ClassStatementAst *node) { // Add trait members that don't need special handling const KDevPG::ListNode< NamespacedIdentifierAst* >* it = node->traitsSequence->front(); DUChainWriteLocker lock; forever { DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, identifierForNamespace(it->element, m_editor)); if (!dec || !dec->internalContext()) { break; } QVector declarations = dec.data()->internalContext()->localDeclarations(nullptr); QVector localDeclarations = currentContext()->localDeclarations(nullptr); ifDebug(qCDebug(DUCHAIN) << "Importing from" << dec.data()->identifier().toString() << "to" << currentContext()->localScopeIdentifier().toString();) foreach (Declaration* import, declarations) { Declaration* found = nullptr; foreach (Declaration* local, localDeclarations) { ifDebug(qCDebug(DUCHAIN) << "Comparing" << import->identifier().toString() << "with" << local->identifier().toString();) if (auto trait = dynamic_cast(local)) { if (trait->aliasedDeclaration().data() == import) { ifDebug(qCDebug(DUCHAIN) << "Already imported";) found = local; break; } if (local->identifier() == import->identifier()) { ClassMethodDeclaration* importMethod = dynamic_cast(import); if (trait->isOverriding(import->context()->indexedLocalScopeIdentifier())) { ifDebug(qCDebug(DUCHAIN) << "Is overridden";) found = local; break; } else if (importMethod) { reportError( i18n("Trait method %1 has not been applied, because there are collisions with other trait methods on %2") .arg(importMethod->prettyName().str(), dynamic_cast(currentDeclaration())->prettyName().str()) , it->element, IProblem::Error ); found = local; break; } } } if (auto trait = dynamic_cast(local)) { if (trait->aliasedDeclaration().data() == import) { ifDebug(qCDebug(DUCHAIN) << "Already imported";) found = local; break; } } if (local->identifier() == import->identifier()) { if (dynamic_cast(local) && dynamic_cast(import)) { found = local; break; } } } if (found) { setEncountered(found); continue; } ifDebug(qCDebug(DUCHAIN) << "Importing new declaration";) CursorInRevision cursor = m_editor->findRange(it->element).start; if (auto olddec = dynamic_cast(import)) { TraitMethodAliasDeclaration* newdec = openDefinition(olddec->qualifiedIdentifier(), RangeInRevision(cursor, cursor)); openAbstractType(olddec->abstractType()); newdec->setPrettyName(olddec->prettyName()); newdec->setAccessPolicy(olddec->accessPolicy()); newdec->setKind(Declaration::Type); newdec->setAliasedDeclaration(IndexedDeclaration(olddec)); newdec->setStatic(olddec->isStatic()); closeType(); closeDeclaration(); } else if (auto olddec = dynamic_cast(import)) { TraitMemberAliasDeclaration* newdec = openDefinition(olddec->qualifiedIdentifier(), RangeInRevision(cursor, cursor)); openAbstractType(olddec->abstractType()); newdec->setAccessPolicy(olddec->accessPolicy()); newdec->setKind(Declaration::Instance); newdec->setAliasedDeclaration(IndexedDeclaration(olddec)); newdec->setStatic(olddec->isStatic()); closeType(); closeDeclaration(); } } if ( it->hasNext() ) { it = it->next; } else { break; } } } void DeclarationBuilder::visitClassExtends(ClassExtendsAst *node) { addBaseType(node->identifier); } void DeclarationBuilder::visitClassImplements(ClassImplementsAst *node) { const KDevPG::ListNode *__it = node->implementsSequence->front(), *__end = __it; do { addBaseType(__it->element); __it = __it->next; } while (__it != __end); DeclarationBuilderBase::visitClassImplements(node); } void DeclarationBuilder::visitClassVariable(ClassVariableAst *node) { QualifiedIdentifier name = identifierForNode(node->variable); if (m_reportErrors) { // check for redeclarations DUChainWriteLocker lock(DUChain::lock()); Q_ASSERT(currentContext()->type() == DUContext::Class); foreach(Declaration * dec, currentContext()->findLocalDeclarations(name.first(), startPos(node))) { if (wasEncountered(dec) && !dec->isFunctionDeclaration() && !(dec->abstractType()->modifiers() & AbstractType::ConstModifier)) { reportRedeclarationError(dec, node); break; } } } openClassMemberDeclaration(node->variable, name); DeclarationBuilderBase::visitClassVariable(node); closeDeclaration(); } void DeclarationBuilder::openClassMemberDeclaration(AstNode* node, const QualifiedIdentifier &name) { DUChainWriteLocker lock(DUChain::lock()); // dirty hack: declarations of class members outside the class context would // make the class context encompass the newRange. This is not what we want. RangeInRevision oldRange = currentContext()->range(); RangeInRevision newRange = editorFindRange(node, node); openDefinition(name, newRange); ClassMemberDeclaration* dec = dynamic_cast(currentDeclaration()); Q_ASSERT(dec); if (m_currentModifers & ModifierPublic) { dec->setAccessPolicy(Declaration::Public); } else if (m_currentModifers & ModifierProtected) { dec->setAccessPolicy(Declaration::Protected); } else if (m_currentModifers & ModifierPrivate) { dec->setAccessPolicy(Declaration::Private); } if (m_currentModifers & ModifierStatic) { dec->setStatic(true); } dec->setKind(Declaration::Instance); currentContext()->setRange(oldRange); } void DeclarationBuilder::declareClassMember(DUContext *parentCtx, AbstractType::Ptr type, const QualifiedIdentifier& identifier, AstNode* node ) { if ( m_upcomingClassVariables.contains(identifier) ) { if (m_actuallyRecompiling) { DUChainWriteLocker lock; if (Declaration* dec = currentContext()->findDeclarationAt(startPos(node))) { if (dynamic_cast(dec)) { // invalidate declaration, it got added // see also bug https://bugs.kde.org/show_bug.cgi?id=241750 delete dec; } } } return; } DUChainWriteLocker lock(DUChain::lock()); // check for redeclaration of private or protected stuff { // only interesting context might be the class context when we are inside a method DUContext *ctx = currentContext()->parentContext(); foreach ( Declaration* dec, parentCtx->findDeclarations(identifier) ) { if ( ClassMemberDeclaration* cdec = dynamic_cast(dec) ) { if ( cdec->accessPolicy() == Declaration::Private && cdec->context() != ctx ) { reportError(i18n("Cannot redeclare private property %1 from this context.", cdec->toString()), node); return; } else if ( cdec->accessPolicy() == Declaration::Protected && cdec->context() != ctx && ( !ctx || !ctx->imports(cdec->context()) ) ) { reportError(i18n("Cannot redeclare protected property %1 from this context.", cdec->toString()), node); return; } if ( cdec->abstractType()->indexed() == type->indexed() ) { encounter(dec); return; } } } } // this member should be public and non-static m_currentModifers = ModifierPublic; injectContext(parentCtx); openClassMemberDeclaration(node, identifier); m_currentModifers = 0; //own closeDeclaration() that doesn't use lastType() currentDeclaration()->setType(type); eventuallyAssignInternalContext(); DeclarationBuilderBase::closeDeclaration(); closeInjectedContext(); } void DeclarationBuilder::visitConstantDeclaration(ConstantDeclarationAst *node) { DUChainWriteLocker lock(DUChain::lock()); if (m_reportErrors) { // check for redeclarations foreach(Declaration * dec, currentContext()->findLocalDeclarations(identifierForNode(node->identifier).first(), startPos(node->identifier))) { if (wasEncountered(dec) && !dec->isFunctionDeclaration() && dec->abstractType()->modifiers() & AbstractType::ConstModifier) { reportRedeclarationError(dec, node->identifier); break; } } } ClassMemberDeclaration* dec = openDefinition(identifierForNode(node->identifier), m_editor->findRange(node->identifier)); { DUChainWriteLocker lock(DUChain::lock()); dec->setAccessPolicy(Declaration::Public); dec->setStatic(true); dec->setKind(Declaration::Instance); } DeclarationBuilderBase::visitConstantDeclaration(node); closeDeclaration(); } void DeclarationBuilder::visitClassConstantDeclaration(ClassConstantDeclarationAst *node) { DUChainWriteLocker lock; if (m_reportErrors) { // Check for constants in traits if (isMatch(currentDeclaration(), ClassDeclarationType)) { ClassDeclaration *parent = dynamic_cast(currentDeclaration()); Q_ASSERT(parent); if (parent->classType() == ClassDeclarationData::Trait) { reportError(i18n("Traits cannot have constants."), node); } } // check for 'class' constant if (identifierForNode(node->identifier).toString().toLower() == QLatin1String("class")) { reportError(i18n("A class constant must not be called 'class'; it is reserved for class name fetching"), node); } // check for redeclarations foreach(Declaration * dec, currentContext()->findLocalDeclarations(identifierForNode(node->identifier).first(), startPos(node->identifier))) { if (wasEncountered(dec) && !dec->isFunctionDeclaration() && dec->abstractType()->modifiers() & AbstractType::ConstModifier) { reportRedeclarationError(dec, node->identifier); break; } } } ClassMemberDeclaration* dec = openDefinition(identifierForNode(node->identifier), m_editor->findRange(node->identifier)); dec->setAccessPolicy(Declaration::Public); dec->setStatic(true); dec->setKind(Declaration::Instance); lock.unlock(); DeclarationBuilderBase::visitClassConstantDeclaration(node); closeDeclaration(); } void DeclarationBuilder::visitTraitAliasStatement(TraitAliasStatementAst *node) { DUChainWriteLocker lock; DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, identifierForNamespace(node->importIdentifier->identifier, m_editor)); if (dec && dec.data()->internalContext()) { createTraitAliasDeclarations(node, dec); } lock.unlock(); DeclarationBuilderBase::visitTraitAliasStatement(node); } void DeclarationBuilder::createTraitAliasDeclarations(TraitAliasStatementAst *node, DeclarationPointer dec) { QualifiedIdentifier original = identifierPairForNode(node->importIdentifier->methodIdentifier).second; QList list = dec.data()->internalContext()->findLocalDeclarations(original.last(), dec.data()->internalContext()->range().start); QualifiedIdentifier alias; if (node->aliasIdentifier) { alias = identifierPairForNode(node->aliasIdentifier).second; } else if (node->aliasNonModifierIdentifier) { alias = identifierPairForNode(node->aliasNonModifierIdentifier).second; } else { alias = original; } if (!list.isEmpty()) { ClassMethodDeclaration* olddec = dynamic_cast(list.first()); TraitMethodAliasDeclaration* newdec; // no existing declaration found, create one if (node->aliasIdentifier || node->aliasNonModifierIdentifier) { if (node->aliasIdentifier) { newdec = openDefinition(alias, m_editor->findRange(node->aliasIdentifier)); newdec->setPrettyName(identifierPairForNode(node->aliasIdentifier).first); } else { newdec = openDefinition(alias, m_editor->findRange(node->aliasNonModifierIdentifier)); newdec->setPrettyName(identifierPairForNode(node->aliasNonModifierIdentifier).first); } newdec->setAccessPolicy(olddec->accessPolicy()); openAbstractType(olddec->abstractType()); if (node->modifiers) { if (node->modifiers->modifiers & ModifierPublic) { newdec->setAccessPolicy(Declaration::Public); } else if (node->modifiers->modifiers & ModifierProtected) { newdec->setAccessPolicy(Declaration::Protected); } else if (node->modifiers->modifiers & ModifierPrivate) { newdec->setAccessPolicy(Declaration::Private); } if (node->modifiers->modifiers & ModifierAbstract) { reportError(i18n("Cannot use 'abstract' as method modifier"), node->modifiers, IProblem::Error); } if (node->modifiers->modifiers & ModifierFinal) { reportError(i18n("Cannot use 'final' as method modifier"), node->modifiers, IProblem::Error); } if (node->modifiers->modifiers & ModifierStatic) { reportError(i18n("Cannot use 'static' as method modifier"), node->modifiers, IProblem::Error); } } } else { CursorInRevision cursor = m_editor->findRange(node->importIdentifier).start; newdec = openDefinition(alias, RangeInRevision(cursor, cursor)); newdec->setPrettyName(identifierPairForNode(node->importIdentifier->methodIdentifier).first); newdec->setAccessPolicy(olddec->accessPolicy()); openAbstractType(olddec->abstractType()); if (node->modifiers) { if (node->modifiers->modifiers & ModifierPublic) { newdec->setAccessPolicy(Declaration::Public); } else if (node->modifiers->modifiers & ModifierProtected) { newdec->setAccessPolicy(Declaration::Protected); } else if (node->modifiers->modifiers & ModifierPrivate) { newdec->setAccessPolicy(Declaration::Private); } if (node->modifiers->modifiers & ModifierAbstract) { reportError(i18n("Cannot use 'abstract' as method modifier"), node->modifiers, IProblem::Error); } if (node->modifiers->modifiers & ModifierFinal) { reportError(i18n("Cannot use 'final' as method modifier"), node->modifiers, IProblem::Error); } if (node->modifiers->modifiers & ModifierStatic) { reportError(i18n("Cannot use 'static' as method modifier"), node->modifiers, IProblem::Error); } } } newdec->setKind(Declaration::Type); newdec->setAliasedDeclaration(IndexedDeclaration(olddec)); newdec->setStatic(olddec->isStatic()); QVector ids; if (node->conflictIdentifierSequence) { const KDevPG::ListNode< NamespacedIdentifierAst* >* it = node->conflictIdentifierSequence->front(); forever { DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, identifierForNamespace(it->element, m_editor)); if (dec) { ids.append(IndexedQualifiedIdentifier(dec.data()->qualifiedIdentifier())); } if ( it->hasNext() ) { it = it->next; } else { break; } } newdec->setOverrides(ids); } closeType(); closeDeclaration(); } } void DeclarationBuilder::visitParameterList(ParameterListAst* node) { PushValue push(m_functionDeclarationPreviousArgument, nullptr); DeclarationBuilderBase::visitParameterList(node); } void DeclarationBuilder::visitParameter(ParameterAst *node) { AbstractFunctionDeclaration* funDec = dynamic_cast(currentDeclaration()); Q_ASSERT(funDec); if (node->defaultValue) { QString symbol = m_editor->parseSession()->symbol(node->defaultValue); funDec->addDefaultParameter(IndexedString(symbol)); if (node->isVariadic != -1) { reportError(i18n("Variadic parameter cannot have a default value"), node->defaultValue); } else if (node->parameterType && node->parameterType->typehint && isClassTypehint(node->parameterType->typehint, m_editor) && symbol.compare(QLatin1String("null"), Qt::CaseInsensitive) != 0) { reportError(i18n("Default value for parameters with a class type hint can only be NULL."), node->defaultValue); } else if (node->parameterType && node->parameterType->typehint && node->parameterType->typehint->genericType && symbol.compare(QLatin1String("null"), Qt::CaseInsensitive) != 0) { NamespacedIdentifierAst* typehintNode = node->parameterType->typehint->genericType; const KDevPG::ListNode< IdentifierAst* >* it = typehintNode->namespaceNameSequence->back(); QString typehintName = m_editor->parseSession()->symbol(it->element); if (typehintName.compare(QLatin1String("object"), Qt::CaseInsensitive) == 0) { reportError(i18n("Default value for parameters with an object type can only be NULL."), node->defaultValue); } } } else { funDec->addDefaultParameter(IndexedString{}); } { // create variable declaration for argument DUChainWriteLocker lock(DUChain::lock()); RangeInRevision newRange = editorFindRange(node->variable, node->variable); VariableDeclaration *dec = openDefinition(identifierForNode(node->variable), newRange); dec->setKind(Declaration::Instance); dec->setVariadic(node->isVariadic != -1); } DeclarationBuilderBase::visitParameter(node); if (node->parameterType && node->parameterType->typehint && isClassTypehint(node->parameterType->typehint, m_editor)) { NamespacedIdentifierAst* typehintNode = node->parameterType->typehint->genericType; const KDevPG::ListNode< IdentifierAst* >* it = typehintNode->namespaceNameSequence->back(); QString className = m_editor->parseSession()->symbol(it->element); if (isReservedClassName(className)) { reportError(i18n("Cannot use '%1' as class name as it is reserved", className), typehintNode); } } if (m_functionDeclarationPreviousArgument && m_functionDeclarationPreviousArgument->isVariadic != -1) { reportError(i18n("Only the last parameter can be variadic."), m_functionDeclarationPreviousArgument); } closeDeclaration(); m_functionDeclarationPreviousArgument = node; } void DeclarationBuilder::visitFunctionDeclarationStatement(FunctionDeclarationStatementAst* node) { isGlobalRedeclaration(identifierForNode(node->functionName), node->functionName, FunctionDeclarationType); FunctionDeclaration* dec = m_functions.value(node->functionName->string, nullptr); Q_ASSERT(dec); // seems like we have to set that, else the usebuilder crashes DeclarationBuilderBase::setEncountered(dec); openDeclarationInternal(dec); openType(dec->abstractType()); DeclarationBuilderBase::visitFunctionDeclarationStatement(node); closeType(); closeDeclaration(); } void DeclarationBuilder::visitReturnType(ReturnTypeAst* node) { if (node->typehint && isClassTypehint(node->typehint, m_editor)) { NamespacedIdentifierAst* typehintNode = node->typehint->genericType; const KDevPG::ListNode< IdentifierAst* >* it = typehintNode->namespaceNameSequence->back(); QString className = m_editor->parseSession()->symbol(it->element); if (isReservedClassName(className)) { reportError(i18n("Cannot use '%1' as class name as it is reserved", className), typehintNode); } } } void DeclarationBuilder::visitClosure(ClosureAst* node) { setComment(formatComment(node, editor())); { DUChainWriteLocker lock; FunctionDeclaration *dec = openDefinition(QualifiedIdentifier(), editor()->findRange(node->startToken)); dec->setKind(Declaration::Type); dec->clearDefaultParameters(); } DeclarationBuilderBase::visitClosure(node); closeDeclaration(); } void DeclarationBuilder::visitLexicalVar(LexicalVarAst* node) { DeclarationBuilderBase::visitLexicalVar(node); QualifiedIdentifier id = identifierForNode(node->variable); DUChainWriteLocker lock; if ( recompiling() ) { // sadly we can't use findLocalDeclarations() here, since it un-aliases declarations foreach ( Declaration* dec, currentContext()->localDeclarations() ) { if ( dynamic_cast(dec) && dec->identifier() == id.first() ) { // don't redeclare but reuse the existing declaration encounter(dec); return; } } } // no existing declaration found, create one foreach(Declaration* aliasedDeclaration, currentContext()->findDeclarations(id)) { if (aliasedDeclaration->kind() == Declaration::Instance) { AliasDeclaration* dec = openDefinition(id, editor()->findRange(node->variable)); dec->setAliasedDeclaration(aliasedDeclaration); closeDeclaration(); break; } } } bool DeclarationBuilder::isGlobalRedeclaration(const QualifiedIdentifier &identifier, AstNode* node, DeclarationType type) { if (!m_reportErrors) { return false; } ///TODO: method redeclaration etc. if (type != ClassDeclarationType && type != FunctionDeclarationType && type != ConstantDeclarationType) { // the other types can be redeclared return false; } DUChainWriteLocker lock(DUChain::lock()); QList declarations = currentContext()->topContext()->findDeclarations( identifier, startPos(node) ); foreach(Declaration* dec, declarations) { if (wasEncountered(dec) && isMatch(dec, type)) { reportRedeclarationError(dec, node); return true; } } return false; } void DeclarationBuilder::reportRedeclarationError(Declaration* declaration, AstNode* node) { if (declaration->range().contains(startPos(node))) { // make sure this is not a wrongly reported redeclaration error return; } if (declaration->context()->topContext()->url() == internalFunctionFile()) { reportError(i18n("Cannot redeclare PHP internal %1.", declaration->toString()), node); } else if (auto trait = dynamic_cast(declaration)) { reportError( i18n("%1 and %2 define the same property (%3) in the composition of %1. This might be incompatible, to improve maintainability consider using accessor methods in traits instead.") .arg(dynamic_cast(currentDeclaration())->prettyName().str(), dynamic_cast(trait->aliasedDeclaration().data()->context()->owner())->prettyName().str(), dynamic_cast(trait)->identifier().toString()), node, IProblem::Warning ); } else { ///TODO: try to shorten the filename by removing the leading path to the current project reportError( i18n("Cannot redeclare %1, already declared in %2 on line %3.", declaration->toString(), declaration->context()->topContext()->url().str(), declaration->range().start.line + 1 ), node ); } } void DeclarationBuilder::visitOuterTopStatement(OuterTopStatementAst* node) { //docblock of an AssignmentExpression setComment(formatComment(node, m_editor)); m_lastTopStatementComment = m_editor->parseSession()->docComment(node->startToken); DeclarationBuilderBase::visitOuterTopStatement(node); } void DeclarationBuilder::visitAssignmentExpression(AssignmentExpressionAst* node) { if ( node->assignmentExpressionEqual ) { PushValue restore(m_findVariable); DeclarationBuilderBase::visitAssignmentExpression(node); } else { DeclarationBuilderBase::visitAssignmentExpression(node); } } void DeclarationBuilder::visitVariable(VariableAst* node) { if ( m_findVariable.find ) { getVariableIdentifier(node, m_findVariable.identifier, m_findVariable.parentIdentifier, m_findVariable.node, m_findVariable.isArray); m_findVariable.find = false; } DeclarationBuilderBase::visitVariable(node); } void DeclarationBuilder::declareVariable(DUContext* parentCtx, AbstractType::Ptr type, const QualifiedIdentifier& identifier, AstNode* node) { DUChainWriteLocker lock(DUChain::lock()); // we must not re-assign $this in a class context /// Qualified identifier for 'this' static const QualifiedIdentifier thisQId(QStringLiteral("this")); if ( identifier == thisQId && currentContext()->parentContext() && currentContext()->parentContext()->type() == DUContext::Class ) { // checks if imports \ArrayAccess ClassDeclaration* currentClass = dynamic_cast(currentContext()->parentContext()->owner()); ClassDeclaration* arrayAccess = nullptr; auto imports = currentContext()->parentContext()->importedParentContexts(); for( const DUContext::Import& ctx : imports ) { DUContext* import = ctx.context(topContext()); if(import->type() == DUContext::Class) { ClassDeclaration* importedClass = dynamic_cast(import->owner()); if(importedClass) { if(importedClass->prettyName().str() == "ArrayAccess" && importedClass->classType() == ClassDeclarationData::ClassType::Interface && !import->parentContext()->owner()) { arrayAccess = importedClass; } } } } IntegralType* thisVar = static_cast(type.data()); // check if this is used as array if(arrayAccess && currentClass && thisVar && thisVar->dataType() == AbstractType::TypeArray) { uint noOfFunc = 0; auto declarations = currentContext()->parentContext()->localDeclarations(); // check if class implements all 4 functions for(auto &dec : declarations) { if(dec->isFunctionDeclaration()) { QualifiedIdentifier func = dec->qualifiedIdentifier(); QString funcname = func.last().identifier().str(); if(funcname == "offsetexists" || funcname == "offsetget" || funcname == "offsetset" || funcname == "offsetunset") { noOfFunc++; } } } if(noOfFunc < 4) { // check if class is not abstract if(currentClass->classModifier() != ClassDeclarationData::ClassModifier::Abstract) { reportError(i18n("Class %1 contains %2 abstract methods and must therefore be declared abstract or implement the remaining methods.",currentClass->prettyName().str(),4-noOfFunc), QList() << node); } } return; } reportError(i18n("Cannot re-assign $this."), QList() << node); return; } const RangeInRevision newRange = editorFindRange(node, node); // check if this variable is already declared { QList< Declaration* > decs = parentCtx->findDeclarations(identifier.first(), startPos(node), nullptr, DUContext::DontSearchInParent); if ( !decs.isEmpty() ) { QList< Declaration* >::const_iterator it = decs.constEnd() - 1; while ( true ) { // we expect that the list of declarations has the newest declaration at back if ( dynamic_cast( *it ) ) { if (!wasEncountered(*it)) { encounter(*it); // force new range https://bugs.kde.org/show_bug.cgi?id=262189, // might be wrong when we had syntax errors in there before (*it)->setRange(newRange); } if ( (*it)->abstractType() && !(*it)->abstractType()->equals(type.data()) ) { // if it's currently mixed and we now get something more definite, use that instead if ( ReferenceType::Ptr rType = ReferenceType::Ptr::dynamicCast((*it)->abstractType()) ) { if ( IntegralType::Ptr integral = IntegralType::Ptr::dynamicCast(rType->baseType()) ) { if ( integral->dataType() == IntegralType::TypeMixed ) { // referenced mixed to referenced @p type ReferenceType::Ptr newType(new ReferenceType()); newType->setBaseType(type); (*it)->setType(newType); return; } } } if ( IntegralType::Ptr integral = IntegralType::Ptr::dynamicCast((*it)->abstractType()) ) { if ( integral->dataType() == IntegralType::TypeMixed ) { // mixed to @p type (*it)->setType(type); return; } } // else make it unsure UnsureType::Ptr unsure = UnsureType::Ptr::dynamicCast((*it)->abstractType()); // maybe it's referenced? ReferenceType::Ptr rType = ReferenceType::Ptr::dynamicCast((*it)->abstractType()); if ( !unsure && rType ) { unsure = UnsureType::Ptr::dynamicCast(rType->baseType()); } if ( !unsure ) { unsure = UnsureType::Ptr(new UnsureType()); if ( rType ) { unsure->addType(rType->baseType()->indexed()); } else { unsure->addType((*it)->indexedType()); } } unsure->addType(type->indexed()); if ( rType ) { rType->setBaseType(AbstractType::Ptr(unsure.data())); (*it)->setType(rType); } else { (*it)->setType(unsure); } } return; } if ( it == decs.constBegin() ) { break; } --it; } } } VariableDeclaration *dec = openDefinition(identifier, newRange); dec->setKind(Declaration::Instance); if (!m_lastTopStatementComment.isEmpty()) { QRegExp rx("(\\*|///)\\s*@superglobal"); if (rx.indexIn(m_lastTopStatementComment) != -1) { dec->setSuperglobal(true); } } //own closeDeclaration() that doesn't use lastType() dec->setType(type); // variable declarations are not namespaced in PHP if (currentContext()->type() == DUContext::Namespace) { dec->setContext(currentContext()->topContext()); } eventuallyAssignInternalContext(); DeclarationBuilderBase::closeDeclaration(); } DUContext* getClassContext(const QualifiedIdentifier &identifier, DUContext* currentCtx) { /// Qualified identifier for 'this' static const QualifiedIdentifier thisQId(QStringLiteral("this")); if ( identifier == thisQId ) { if ( currentCtx->parentContext() && currentCtx->parentContext()->type() == DUContext::Class ) { return currentCtx->parentContext(); } } else { DUChainReadLocker lock(DUChain::lock()); foreach( Declaration* parent, currentCtx->topContext()->findDeclarations(identifier) ) { if ( StructureType::Ptr ctype = parent->type() ) { return ctype->internalContext(currentCtx->topContext()); } } ///TODO: if we can't find anything here we might have to use the findDeclarationImport helper } return nullptr; } ///TODO: we need to handle assignment to array-members properly /// currently we just make sure the array is declared, but don't know /// anything about its contents void DeclarationBuilder::visitAssignmentExpressionEqual(AssignmentExpressionEqualAst *node) { DeclarationBuilderBase::visitAssignmentExpressionEqual(node); if ( !m_findVariable.identifier.isEmpty() && currentAbstractType()) { //create new declaration assignments to not-yet declared variables and class members AbstractType::Ptr type; if ( m_findVariable.isArray ) { // implicit array declaration type = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); } else { type = currentAbstractType(); } if ( !m_findVariable.parentIdentifier.isEmpty() ) { // assignment to class members if ( DUContext* ctx = getClassContext(m_findVariable.parentIdentifier, currentContext()) ) { declareClassMember(ctx, type, m_findVariable.identifier, m_findVariable.node); } } else { // assigment to other variables declareVariable(currentContext(), type, m_findVariable.identifier, m_findVariable.node ); } } } void DeclarationBuilder::visitFunctionCall(FunctionCallAst* node) { QualifiedIdentifier id; if (!m_isInternalFunctions) { FunctionType::Ptr oldFunction = m_currentFunctionType; DeclarationPointer dec; if ( node->stringFunctionName ) { dec = findDeclarationImport(FunctionDeclarationType, node->stringFunctionName); + + if (!dec) { + dec = findDeclarationImport(FunctionDeclarationType, node->stringFunctionName, GlobalScope); + } } else if ( node->stringFunctionNameOrClass ) { id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); dec = findDeclarationImport(FunctionDeclarationType, id); + + if (!dec) { + id.setExplicitlyGlobal(true); + dec = findDeclarationImport(FunctionDeclarationType, id); + } } else { ///TODO: node->varFunctionName } if ( dec ) { m_currentFunctionType = dec->type(); } else { m_currentFunctionType = nullptr; } DeclarationBuilderBase::visitFunctionCall(node); m_currentFunctionType = oldFunction; } else { // optimize for internal function file DeclarationBuilderBase::visitFunctionCall(node); } if (node->stringFunctionNameOrClass && !node->stringFunctionName && !node->varFunctionName) { if (id.toString(RemoveExplicitlyGlobalPrefix) == QLatin1String("define") && node->stringParameterList && node->stringParameterList->parametersSequence && node->stringParameterList->parametersSequence->count() > 0) { //constant, defined through define-function //find name of the constant (first argument of the function call) CommonScalarAst* scalar = findCommonScalar(node->stringParameterList->parametersSequence->at(0)->element); if (scalar && scalar->string != -1) { QString constant = m_editor->parseSession()->symbol(scalar->string); constant = constant.mid(1, constant.length() - 2); RangeInRevision newRange = editorFindRange(scalar, scalar); AbstractType::Ptr type; if (node->stringParameterList->parametersSequence->count() > 1) { type = getTypeForNode(node->stringParameterList->parametersSequence->at(1)->element); Q_ASSERT(type); type->setModifiers(type->modifiers() | AbstractType::ConstModifier); } // TODO: else report error? DUChainWriteLocker lock; // find fitting context to put define in, // pick first namespace or global context otherwise DUContext* ctx = currentContext(); while (ctx->type() != DUContext::Namespace && ctx->parentContext()) { ctx = ctx->parentContext(); } injectContext(ctx); //constants are always global QualifiedIdentifier identifier(constant); isGlobalRedeclaration(identifier, scalar, ConstantDeclarationType); Declaration* dec = openDefinition(identifier, newRange); dec->setKind(Declaration::Instance); if (type) { dec->setType(type); injectType(type); } closeDeclaration(); closeInjectedContext(); } } } } void DeclarationBuilder::visitFunctionCallParameterList(FunctionCallParameterListAst* node) { PushValue push(m_functionCallPreviousArgument, nullptr); PushValue pos(m_functionCallParameterPos, 0); DeclarationBuilderBase::visitFunctionCallParameterList(node); } void DeclarationBuilder::visitFunctionCallParameterListElement(FunctionCallParameterListElementAst* node) { PushValue restore(m_findVariable); DeclarationBuilderBase::visitFunctionCallParameterListElement(node); if ( m_findVariable.node && m_currentFunctionType && m_currentFunctionType->arguments().count() > m_functionCallParameterPos) { ReferenceType::Ptr refType = m_currentFunctionType->arguments() .at(m_functionCallParameterPos).cast(); if ( refType ) { // this argument is referenced, so if the node contains undeclared variables we have // to declare them with a NULL type, see also: // http://de.php.net/manual/en/language.references.whatdo.php // declare with NULL type, just like PHP does declareFoundVariable(AbstractType::Ptr(new IntegralType(IntegralType::TypeNull))); } } if (m_functionCallPreviousArgument && m_functionCallPreviousArgument->isVariadic != -1 && node->isVariadic == -1) { reportError(i18n("Cannot use positional argument after argument unpacking"), node); } m_functionCallPreviousArgument = node; ++m_functionCallParameterPos; } void DeclarationBuilder::visitAssignmentListElement(AssignmentListElementAst* node) { PushValue restore(m_findVariable); DeclarationBuilderBase::DefaultVisitor::visitAssignmentListElement(node); if ( m_findVariable.node ) { ///TODO: get a proper type here, if possible declareFoundVariable(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); } } void DeclarationBuilder::declareFoundVariable(AbstractType::Ptr type) { Q_ASSERT(m_findVariable.node); ///TODO: support something like: foo($var[0]) if ( !m_findVariable.isArray ) { DUContext *ctx = nullptr; if ( m_findVariable.parentIdentifier.isEmpty() ) { ctx = currentContext(); } else { ctx = getClassContext(m_findVariable.parentIdentifier, currentContext()); } if ( ctx ) { bool isDeclared = false; { DUChainWriteLocker lock(DUChain::lock()); RangeInRevision range = m_editor->findRange(m_findVariable.node); foreach ( Declaration* dec, ctx->findDeclarations(m_findVariable.identifier) ) { if ( dec->kind() == Declaration::Instance ) { if (!wasEncountered(dec) || (dec->context() == ctx && range < dec->range())) { // just like a "redeclaration", hence we must update the range // TODO: do the same for all other uses of "encounter"? dec->setRange(editorFindRange(m_findVariable.node)); encounter(dec); } isDeclared = true; break; } } } if ( !isDeclared && m_findVariable.parentIdentifier.isEmpty() ) { // check also for global vars isDeclared = findDeclarationImport(GlobalVariableDeclarationType, m_findVariable.identifier); } if ( !isDeclared ) { // couldn't find the dec, declare it if ( !m_findVariable.parentIdentifier.isEmpty() ) { declareClassMember(ctx, type, m_findVariable.identifier, m_findVariable.node); } else { declareVariable(ctx, type, m_findVariable.identifier, m_findVariable.node); } } } } } void DeclarationBuilder::visitStatement(StatementAst* node) { DeclarationBuilderBase::visitStatement(node); if (node->foreachVariable) { PushValue restore(m_findVariable); visitForeachVariable(node->foreachVariable); if (m_findVariable.node) { declareFoundVariable(lastType()); } } if (node->foreachVarAsVar) { PushValue restore(m_findVariable); visitForeachVariable(node->foreachVarAsVar); if (m_findVariable.node) { declareFoundVariable(lastType()); } } if (node->foreachExprAsVar) { PushValue restore(m_findVariable); visitVariable(node->foreachExprAsVar); if (m_findVariable.node) { declareFoundVariable(lastType()); } } } void DeclarationBuilder::visitStaticVar(StaticVarAst* node) { DeclarationBuilderBase::visitStaticVar(node); DUChainWriteLocker lock(DUChain::lock()); openDefinition(identifierForNode(node->var), editorFindRange(node->var, node->var)); currentDeclaration()->setKind(Declaration::Instance); closeDeclaration(); } void DeclarationBuilder::visitGlobalVar(GlobalVarAst* node) { DeclarationBuilderBase::visitGlobalVar(node); if (node->var) { QualifiedIdentifier id = identifierForNode(node->var); if ( recompiling() ) { DUChainWriteLocker lock(DUChain::lock()); // sadly we can't use findLocalDeclarations() here, since it un-aliases declarations foreach ( Declaration* dec, currentContext()->localDeclarations() ) { if ( dynamic_cast(dec) && dec->identifier() == id.first() ) { // don't redeclare but reuse the existing declaration encounter(dec); return; } } } // no existing declaration found, create one DeclarationPointer aliasedDeclaration = findDeclarationImport(GlobalVariableDeclarationType, node->var); if (aliasedDeclaration) { DUChainWriteLocker lock(DUChain::lock()); AliasDeclaration* dec = openDefinition(id, m_editor->findRange(node->var)); dec->setAliasedDeclaration(aliasedDeclaration.data()); closeDeclaration(); } } } void DeclarationBuilder::visitCatchItem(CatchItemAst *node) { DeclarationBuilderBase::visitCatchItem(node); DUChainWriteLocker lock(DUChain::lock()); openDefinition(identifierForNode(node->var), editorFindRange(node->var, node->var)); currentDeclaration()->setKind(Declaration::Instance); closeDeclaration(); } void DeclarationBuilder::visitUnaryExpression(UnaryExpressionAst* node) { DeclarationBuilderBase::visitUnaryExpression(node); IndexedString includeFile = getIncludeFileForNode(node, m_editor); if ( !includeFile.isEmpty() ) { DUChainWriteLocker lock; TopDUContext* includedCtx = DUChain::self()->chainForDocument(includeFile); if ( !includedCtx ) { // invalid include return; } QualifiedIdentifier identifier(includeFile.str()); foreach ( Declaration* dec, includedCtx->findDeclarations(identifier, CursorInRevision(0, 1)) ) { if ( dec->kind() == Declaration::Import ) { encounter(dec); return; } } injectContext(includedCtx); openDefinition(identifier, RangeInRevision(0, 0, 0, 0)); currentDeclaration()->setKind(Declaration::Import); eventuallyAssignInternalContext(); DeclarationBuilderBase::closeDeclaration(); closeInjectedContext(); } } void DeclarationBuilder::openNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier, const RangeInRevision& range) { NamespaceDeclaration* dec = m_namespaces.value(node->string, nullptr); Q_ASSERT(dec); DeclarationBuilderBase::setEncountered(dec); openDeclarationInternal(dec); DeclarationBuilderBase::openNamespace(parent, node, identifier, range); } void DeclarationBuilder::closeNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier) { DeclarationBuilderBase::closeNamespace(parent, node, identifier); closeDeclaration(); } void DeclarationBuilder::visitUseNamespace(UseNamespaceAst* node) { DUChainWriteLocker lock; if ( currentContext()->type() != DUContext::Namespace && !node->aliasIdentifier && node->identifier->namespaceNameSequence->count() == 1 ) { reportError(i18n("The use statement with non-compound name '%1' has no effect.", identifierForNode(node->identifier->namespaceNameSequence->front()->element).toString()), node->identifier, IProblem::Warning); return; } IdentifierAst* idNode = node->aliasIdentifier ? node->aliasIdentifier : node->identifier->namespaceNameSequence->back()->element; IdentifierPair id = identifierPairForNode(idNode); ///TODO: case insensitive! QualifiedIdentifier qid = identifierForNamespace(node->identifier, m_editor); - ///TODO: find out why this must be done (see mail to kdevelop-devel on jan 18th 2011) - qid.setExplicitlyGlobal( false ); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, qid); + if (!dec && !qid.explicitlyGlobal()) { + QualifiedIdentifier globalQid = qid; + globalQid.setExplicitlyGlobal(true); + dec = findDeclarationImport(ClassDeclarationType, globalQid); + } + if (dec) { // Check for a name conflict DeclarationPointer dec2 = findDeclarationImport(ClassDeclarationType, id.second); if (dec2 && dec2->context()->scopeIdentifier() == currentContext()->scopeIdentifier() && dec2->context()->topContext() == currentContext()->topContext() && dec2->identifier().toString() == id.second.toString()) { reportError(i18n("Cannot use '%1' as '%2' because the name is already in use.", dec.data()->identifier().toString(), id.second.toString()), node->identifier, IProblem::Error); return; } AliasDeclaration* decl = openDefinition(id.second, m_editor->findRange(idNode)); decl->setAliasedDeclaration(dec.data()); } else { + // NamespaceAliasDeclarations can't use a global import identifier + qid.setExplicitlyGlobal(false); + NamespaceAliasDeclaration* decl = openDefinition(id.second, m_editor->findRange(idNode)); decl->setImportIdentifier( qid ); decl->setPrettyName( id.first ); decl->setKind(Declaration::NamespaceAlias); } closeDeclaration(); if (node->aliasIdentifier) { QString aliasName = m_editor->parseSession()->symbol(node->aliasIdentifier); if (isReservedClassName(aliasName)) { reportError(i18n("Cannot use %1 as %2 because '%2' is a special class name", qid.toString(), aliasName), node->aliasIdentifier); } } } void DeclarationBuilder::visitVarExpression(VarExpressionAst* node) { DeclarationBuilderBase::visitVarExpression(node); if (node->isGenerator != -1 && currentContext()->type() != DUContext::Other) { reportError(i18n("The 'yield' expression can only be used inside a function"), node); } } void DeclarationBuilder::updateCurrentType() { DUChainWriteLocker lock(DUChain::lock()); currentDeclaration()->setAbstractType(currentAbstractType()); } void DeclarationBuilder::supportBuild(AstNode* node, DUContext* context) { // generally we are the second pass through the doc (see PreDeclarationBuilder) // so notify our base about it setCompilingContexts(false); DeclarationBuilderBase::supportBuild(node, context); } void DeclarationBuilder::closeContext() { if (currentContext()->type() == DUContext::Function) { Q_ASSERT(currentDeclaration()); currentDeclaration()->setInternalFunctionContext(currentContext()); } // We don't want the first pass to clean up stuff, since // there is lots of stuff we visit/encounter here first. // So we clean things up here. setCompilingContexts(true); DeclarationBuilderBase::closeContext(); setCompilingContexts(false); } void DeclarationBuilder::encounter(Declaration* dec) { // when we are recompiling, it's important to mark decs as encountered // and update their comments if ( recompiling() && !wasEncountered(dec) ) { dec->setComment(comment()); setEncountered(dec); } } bool DeclarationBuilder::isReservedClassName(QString className) { return className.compare(QLatin1String("string"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("bool"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("int"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("float"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("iterable"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("object"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("null"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("true"), Qt::CaseInsensitive) == 0 || className.compare(QLatin1String("false"), Qt::CaseInsensitive) == 0; } } diff --git a/duchain/builders/typebuilder.cpp b/duchain/builders/typebuilder.cpp index 43040c8..3965e46 100644 --- a/duchain/builders/typebuilder.cpp +++ b/duchain/builders/typebuilder.cpp @@ -1,605 +1,608 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "typebuilder.h" #include #include #include #include #include #include #include "../declarations/classdeclaration.h" #include "../types/indexedcontainer.h" #include "../types/integraltypeextended.h" #include "../types/structuretype.h" #include #include "editorintegrator.h" #include "parsesession.h" #include "phpdebugvisitor.h" #include "expressionparser.h" #include "expressionvisitor.h" #include "../declarations/classmethoddeclaration.h" #include using namespace KDevelop; namespace Php { TypeBuilder::TypeBuilder() : TypeBuilderBase() , m_gotTypeFromDocComment(false) , m_gotReturnTypeFromDocComment(false) { } TypeBuilder::~TypeBuilder() { } AbstractType::Ptr TypeBuilder::parseType(QString type, AstNode* node) { uint iType = 0; type = type.trimmed(); if (!type.compare(QLatin1String("int"), Qt::CaseInsensitive) || !type.compare(QLatin1String("integer"), Qt::CaseInsensitive)) { iType = IntegralType::TypeInt; } else if (!type.compare(QLatin1String("float"), Qt::CaseInsensitive) || !type.compare(QLatin1String("double"), Qt::CaseInsensitive)) { iType = IntegralType::TypeFloat; } else if (!type.compare(QLatin1String("bool"), Qt::CaseInsensitive) || !type.compare(QLatin1String("boolean"), Qt::CaseInsensitive) || !type.compare(QLatin1String("false"), Qt::CaseInsensitive) || !type.compare(QLatin1String("true"), Qt::CaseInsensitive)) { iType = IntegralType::TypeBoolean; } else if (!type.compare(QLatin1String("string"), Qt::CaseInsensitive)) { iType = IntegralType::TypeString; } else if (!type.compare(QLatin1String("mixed"), Qt::CaseInsensitive)) { iType = IntegralType::TypeMixed; } else if (!type.compare(QLatin1String("array"), Qt::CaseInsensitive)) { iType = IntegralType::TypeArray; } else if (!type.compare(QLatin1String("resource"), Qt::CaseInsensitive)) { return AbstractType::Ptr(new IntegralTypeExtended(IntegralTypeExtended::TypeResource)); } else if (!type.compare(QLatin1String("null"), Qt::CaseInsensitive)) { iType = IntegralType::TypeNull; } else if (!type.compare(QLatin1String("void"), Qt::CaseInsensitive)) { iType = IntegralType::TypeVoid; } else if (!type.compare(QLatin1String("self"), Qt::CaseInsensitive) || !type.compare(QLatin1String("this"), Qt::CaseInsensitive) || !type.compare(QLatin1String("static"), Qt::CaseInsensitive)) { DUChainReadLocker lock(DUChain::lock()); if ( currentContext()->type() == DUContext::Class && currentContext()->owner() ) { return currentContext()->owner()->abstractType(); } } else { if (!type.compare(QLatin1String("object"), Qt::CaseInsensitive)) { return AbstractType::Ptr(new IntegralTypeExtended(IntegralTypeExtended::TypeObject)); } //don't use openTypeFromName as it uses cursor for findDeclarations DeclarationPointer decl = findDeclarationImport(ClassDeclarationType, QualifiedIdentifier(type.toLower())); if (decl && decl->abstractType()) { return decl->abstractType(); } if (type.contains('|')) { QList types; foreach (const QString& t, type.split('|')) { AbstractType::Ptr subType = parseType(t, node); if (!(IntegralType::Ptr::dynamicCast(subType) && IntegralType::Ptr::staticCast(subType)->dataType() == IntegralType::TypeMixed)) { types << parseType(t, node); } } if (!type.isEmpty()) { UnsureType::Ptr ret(new UnsureType()); foreach (const AbstractType::Ptr& t, types) { ret->addType(t->indexed()); } //qCDebug(DUCHAIN) << type << ret->toString(); return AbstractType::Ptr::staticCast(ret); } } iType = IntegralType::TypeMixed; } AbstractType::Ptr ret(new IntegralType(iType)); //qCDebug(DUCHAIN) << type << ret->toString(); return ret; } AbstractType::Ptr TypeBuilder::injectParseType(QString type, AstNode* node) { AbstractType::Ptr ret = parseType(type, node); injectType(ret); //qCDebug(DUCHAIN) << type << ret->toString(); return ret; } /** * Find all (or only one - see @p docCommentName) values for a given needle * in a doc-comment. Needle has to start a line in the doccomment, * i.e.: * * * @docCommentName value * * or * * /// @docCommentName value */ QStringList findInDocComment(const QString &docComment, const QString &docCommentName, const bool onlyOne) { QStringList matches; // optimization that does not require potentially slow regexps // old code was something like this: /* if (!docComment.isEmpty()) { QRegExp rx("\\*\\s+@param\\s([^\\s]*)"); int pos = 0; while ((pos = rx.indexIn(docComment, pos)) != -1) { ret << parseType(rx.cap(1), node); pos += rx.matchedLength(); } } */ for ( int i = 0, size = docComment.size(); i < size; ++i ) { if ( docComment[i].isSpace() || docComment[i] == '*' || docComment[i] == '/' ) { // skip whitespace and comment-marker at beginning of line continue; } else if ( docComment[i] == '@' && docComment.midRef(i + 1, docCommentName.size()) == docCommentName ) { // find @return or similar i += docCommentName.size() + 1; // skip whitespace (at least one is required) if ( i >= size || !docComment[i].isSpace() ) { // skip to next line i = docComment.indexOf('\n', i); if ( i == -1 ) { break; } continue; } else if ( docComment[i] == '\n' ) { continue; } ++i; // at least one whitespace while ( i < size && docComment[i].isSpace() ) { ++i; } // finally get the typename int pos = i; while ( pos < size && !docComment[pos].isSpace() ) { ++pos; } if ( pos > i ) { matches << docComment.mid(i, pos - i); if ( onlyOne ) { break; } else { i = pos; } } } // skip to next line i = docComment.indexOf('\n', i); if ( i == -1 ) { break; } } return matches; } AbstractType::Ptr TypeBuilder::parseDocComment(AstNode* node, const QString& docCommentName) { m_gotTypeFromDocComment = false; const QString& docComment = editor()->parseSession()->docComment(node->startToken); if ( !docComment.isEmpty() ) { const QStringList& matches = findInDocComment(docComment, docCommentName, true); if ( !matches.isEmpty() ) { AbstractType::Ptr type; if (matches.first() == QLatin1String("$this")) { DUChainReadLocker lock(DUChain::lock()); if (currentContext()->owner()) { type = currentContext()->owner()->abstractType(); } } else { type = injectParseType(matches.first(), node); } if (type) { m_gotTypeFromDocComment = true; } return type; } } return AbstractType::Ptr(); } QList TypeBuilder::parseDocCommentParams(AstNode* node) { QList ret; QString docComment = editor()->parseSession()->docComment(node->startToken); if ( !docComment.isEmpty() ) { const QStringList& matches = findInDocComment(docComment, QStringLiteral("param"), false); if ( !matches.isEmpty() ) { ret.reserve(matches.size()); foreach ( const QString& type, matches ) { ret << parseType(type, node); } } } return ret; } AbstractType::Ptr TypeBuilder::getTypeForNode(AstNode* node) { AbstractType::Ptr type; if (node) { type = parseDocComment(node, QStringLiteral("var")); //we fully trust in @var typehint and don't try to evaluate ourself if (!type) { node->ducontext = currentContext(); ExpressionParser ep; ep.setCreateProblems(true); ExpressionEvaluationResult res = ep.evaluateType(node, editor()); if (res.hadUnresolvedIdentifiers()) { m_hadUnresolvedIdentifiers = true; } type = res.type(); } } if (!type) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)); } return type; } void TypeBuilder::visitClassDeclarationStatement(ClassDeclarationStatementAst* node) { // the predeclaration builder should have set up a type already // and the declarationbuilder should have set that as current type Q_ASSERT(hasCurrentType() && currentType()); TypeBuilderBase::visitClassDeclarationStatement(node); } void TypeBuilder::visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst* node) { // the predeclaration builder should have set up a type already // and the declarationbuilder should have set that as current type Q_ASSERT(hasCurrentType() && currentType()); TypeBuilderBase::visitInterfaceDeclarationStatement(node); } void TypeBuilder::visitTraitDeclarationStatement(TraitDeclarationStatementAst* node) { // the predeclaration builder should have set up a type already // and the declarationbuilder should have set that as current type Q_ASSERT(hasCurrentType() && currentType()); TypeBuilderBase::visitTraitDeclarationStatement(node); } void TypeBuilder::visitClassStatement(ClassStatementAst *node) { if (node->methodName) { //method declaration m_currentFunctionParams = parseDocCommentParams(node); FunctionType::Ptr functionType = FunctionType::Ptr(new FunctionType()); openType(functionType); openContextType(functionType); AbstractType::Ptr phpdocReturnType = parseDocComment(node, QStringLiteral("return")); functionType->setReturnType(returnType(node->returnType, phpdocReturnType, editor(), currentContext())); m_gotReturnTypeFromDocComment = functionType->returnType(); updateCurrentType(); TypeBuilderBase::visitClassStatement(node); if (currentType() && !currentType()->returnType()) { currentType()->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); } closeContextType(); closeType(); } else { //member-variable parseDocComment(node, QStringLiteral("var")); TypeBuilderBase::visitClassStatement(node); if (m_gotTypeFromDocComment) { clearLastType(); m_gotTypeFromDocComment = false; } } } void TypeBuilder::visitClassVariable(ClassVariableAst *node) { if (!m_gotTypeFromDocComment) { openAbstractType(getTypeForNode(node->value)); TypeBuilderBase::visitClassVariable(node); closeType(); } else { TypeBuilderBase::visitClassVariable(node); } } void TypeBuilder::visitConstantDeclaration(ConstantDeclarationAst* node) { if (!m_gotTypeFromDocComment || !currentAbstractType()) { AbstractType::Ptr type = getTypeForNode(node->scalar); type->setModifiers(type->modifiers() | AbstractType::ConstModifier); openAbstractType(type); TypeBuilderBase::visitConstantDeclaration(node); closeType(); } else { currentAbstractType()->setModifiers(currentAbstractType()->modifiers() & AbstractType::ConstModifier); TypeBuilderBase::visitConstantDeclaration(node); } } void TypeBuilder::visitClassConstantDeclaration(ClassConstantDeclarationAst* node) { if (!m_gotTypeFromDocComment || !currentAbstractType()) { AbstractType::Ptr type = getTypeForNode(node->scalar); type->setModifiers(type->modifiers() | AbstractType::ConstModifier); openAbstractType(type); TypeBuilderBase::visitClassConstantDeclaration(node); closeType(); } else { currentAbstractType()->setModifiers(currentAbstractType()->modifiers() & AbstractType::ConstModifier); TypeBuilderBase::visitClassConstantDeclaration(node); } } void TypeBuilder::visitParameter(ParameterAst *node) { AbstractType::Ptr phpDocTypehint; if (m_currentFunctionParams.count() > currentType()->arguments().count()) { phpDocTypehint = m_currentFunctionParams.at(currentType()->arguments().count()); } AbstractType::Ptr type = parameterType(node, phpDocTypehint, editor(), currentContext()); openAbstractType(type); TypeBuilderBase::visitParameter(node); closeType(); DUChainWriteLocker lock(DUChain::lock()); currentType()->addArgument(type); } void TypeBuilder::visitFunctionDeclarationStatement(FunctionDeclarationStatementAst* node) { m_currentFunctionParams = parseDocCommentParams(node); // the predeclarationbuilder should have already built the type // and the declarationbuilder should have set it to open Q_ASSERT(hasCurrentType()); FunctionType::Ptr type = currentType(); Q_ASSERT(type); openContextType(type); AbstractType::Ptr phpdocReturnType = parseDocComment(node, QStringLiteral("return")); type->setReturnType(returnType(node->returnType, phpdocReturnType, editor(), currentContext())); m_gotReturnTypeFromDocComment = type->returnType(); updateCurrentType(); TypeBuilderBase::visitFunctionDeclarationStatement(node); if (!type->returnType()) { type->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); } closeContextType(); } void TypeBuilder::visitClosure(ClosureAst* node) { m_currentFunctionParams = parseDocCommentParams(node); FunctionType::Ptr type = FunctionType::Ptr(new FunctionType()); openType(type); openContextType(type); AbstractType::Ptr phpdocReturnType = parseDocComment(node, QStringLiteral("return")); type->setReturnType(returnType(node->returnType, phpdocReturnType, editor(), currentContext())); m_gotReturnTypeFromDocComment = type->returnType(); updateCurrentType(); TypeBuilderBase::visitClosure(node); if (!type->returnType()) { type->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); } closeContextType(); closeType(); } void TypeBuilder::visitAssignmentExpression(AssignmentExpressionAst* node) { // performance: only try to find type when we are actually in an assignment expr if (node->assignmentExpression || node->assignmentExpressionEqual) { openAbstractType(getTypeForNode(node)); } TypeBuilderBase::visitAssignmentExpression(node); if (node->assignmentExpression || node->assignmentExpressionEqual) { closeType(); } } void TypeBuilder::visitStaticVar(StaticVarAst *node) { openAbstractType(getTypeForNode(node->value)); TypeBuilderBase::visitStaticVar(node); closeType(); } void TypeBuilder::visitStatement(StatementAst* node) { TypeBuilderBase::visitStatement(node); if ( !m_gotReturnTypeFromDocComment && node->returnExpr && hasCurrentType() && currentType()) { FunctionType::Ptr ft = currentType(); // qCDebug(DUCHAIN) << "return" << (ft->returnType() ? ft->returnType()->toString() : "none") << lastType()->toString(); AbstractType::Ptr type = getTypeForNode(node->returnExpr); if (type) { // ignore references for return values, PHP does so as well if ( ReferenceType::Ptr rType = ReferenceType::Ptr::dynamicCast(type) ) { type = rType->baseType(); } if (ft->returnType() && !ft->returnType()->equals(type.data())) { bool existingTypeIsCallable = ft->returnType().cast() && ft->returnType().cast()->dataType() == IntegralTypeExtended::TypeCallable; bool newTypeIsCallable = type.cast() && type.cast()->dataType() == IntegralTypeExtended::TypeCallable; if (ft->returnType().cast() && ft->returnType().cast()->dataType() == IntegralType::TypeMixed) { //don't add TypeMixed to the list, just ignore ft->setReturnType(type); } else if ((existingTypeIsCallable && type.cast()) || (newTypeIsCallable && ft->returnType().cast())) { //If one type is "callable" and the other a real function, the result is just a "callable". ft->setReturnType(AbstractType::Ptr(new IntegralTypeExtended(IntegralTypeExtended::TypeCallable))); } else { UnsureType::Ptr retT; if (ft->returnType().cast()) { //qCDebug(DUCHAIN) << "we already have an unsure type"; retT = ft->returnType().cast(); if (type.cast()) { //qCDebug(DUCHAIN) << "add multiple to returnType"; FOREACH_FUNCTION(const IndexedType& t, type.cast()->types) { retT->addType(t); } } else { //qCDebug(DUCHAIN) << "add to returnType"; retT->addType(type->indexed()); } } else { if (type.cast()) { retT = type.cast(); } else { retT = new UnsureType(); retT->addType(type->indexed()); } retT->addType(ft->returnType()->indexed()); } ft->setReturnType(AbstractType::Ptr::staticCast(retT)); } } else { ft->setReturnType(type); } updateCurrentType(); } } AstNode *foreachNode = nullptr; if (node->foreachVar) { foreachNode = node->foreachVar; } else if (node->foreachExpr) { foreachNode = node->foreachExpr; } else if (node->foreachExprAsVar) { foreachNode = node->foreachExprAsVar; } if (foreachNode) { ExpressionVisitor v(editor()); foreachNode->ducontext = currentContext(); v.visitNode(foreachNode); DUChainReadLocker lock(DUChain::lock()); bool foundType = false; if (StructureType::Ptr type = StructureType::Ptr::dynamicCast(v.result().type())) { ClassDeclaration *classDec = dynamic_cast(type->declaration(currentContext()->topContext())); if (!classDec) { ///FIXME: this is just a hack for https://bugs.kde.org/show_bug.cgi?id=269369 /// a proper fix needs full fledged two-pass, i.e. get rid of PreDeclarationBuilder // 0 == global lookup and the delcaration is found again... classDec = dynamic_cast(type->declaration(nullptr)); } if (classDec) { /// Qualified identifier for 'iterator' - static const QualifiedIdentifier iteratorQId(QStringLiteral("iterator")); + static QualifiedIdentifier iteratorQId(QStringLiteral("iterator")); + iteratorQId.setExplicitlyGlobal(true); ClassDeclaration* iteratorDecl = dynamic_cast( findDeclarationImport(ClassDeclarationType, iteratorQId).data() ); Q_ASSERT(iteratorDecl); if (classDec->isPublicBaseClass(iteratorDecl, currentContext()->topContext())) { /// Qualified identifier for 'current' static const QualifiedIdentifier currentQId(QStringLiteral("current")); auto classContext = classDec->internalContext(); if (classContext) { foreach (Declaration *d, classContext->findDeclarations(currentQId)) { if (!dynamic_cast(d)) continue; Q_ASSERT(d->type()); injectType(d->type()->returnType()); foundType = true; // qCDebug(DUCHAIN) << "that's it: " << d->type()->returnType()->toString(); } } } } } if (!foundType) { injectType(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); } } } void TypeBuilder::visitCatchItem(Php::CatchItemAst *node) { TypeBuilderBase::visitCatchItem(node); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, identifierForNamespace(node->catchClass, m_editor)); if (dec && dec->abstractType()) { openAbstractType(dec->abstractType()); closeType(); } } void TypeBuilder::visitVarExpression(Php::VarExpressionAst *node) { if (hasCurrentContextType() && node->isGenerator != -1 && !m_gotReturnTypeFromDocComment) { FunctionType::Ptr ft = FunctionType::Ptr::dynamicCast(currentContextType()); - DeclarationPointer generatorDecl = findDeclarationImport(ClassDeclarationType, QualifiedIdentifier("generator")); + static QualifiedIdentifier generatorQId(QStringLiteral("generator")); + generatorQId.setExplicitlyGlobal(true); + DeclarationPointer generatorDecl = findDeclarationImport(ClassDeclarationType, generatorQId); if (ft && generatorDecl) { AbstractType::Ptr generatorType = generatorDecl->abstractType(); if (generatorType) { ft->setReturnType(generatorType); } } updateCurrentType(); } TypeBuilderBase::visitVarExpression(node); } void TypeBuilder::updateCurrentType() { // do nothing } } diff --git a/duchain/builders/usebuilder.cpp b/duchain/builders/usebuilder.cpp index 9cde21e..3a6fe93 100644 --- a/duchain/builders/usebuilder.cpp +++ b/duchain/builders/usebuilder.cpp @@ -1,284 +1,308 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "usebuilder.h" #include #include "editorintegrator.h" #include "expressionvisitor.h" #include "parsesession.h" #include using namespace KDevelop; namespace Php { class UseExpressionVisitor : public ExpressionVisitor { public: UseExpressionVisitor(EditorIntegrator* editor, UseBuilder* useBuilder) : ExpressionVisitor(editor), m_builder(useBuilder) { } protected: void usingDeclaration(AstNode* node, const DeclarationPointer& decl) override { m_builder->newCheckedUse(node, decl); } private: UseBuilder* m_builder; }; UseBuilder::UseBuilder( EditorIntegrator* editor ) { m_editor = editor; } ReferencedTopDUContext UseBuilder::build ( const IndexedString& url, AstNode* node, ReferencedTopDUContext updateContext ) { // just for safety purposes: running the UseBuilder on the internal function file // will lead to undefined behavior due to the amount of optimization it has received // (esp. in the contextbuilder) Q_ASSERT(url != internalFunctionFile()); return UseBuilderBase::build ( url, node, updateContext ); } void UseBuilder::visitParameter(ParameterAst *node) { if (node->parameterType && node->parameterType->typehint && isClassTypehint(node->parameterType->typehint, m_editor)) { buildNamespaceUses(node->parameterType->typehint->genericType); } if (node->defaultValue) { visitNodeWithExprVisitor(node->defaultValue); } } void UseBuilder::visitClassImplements(ClassImplementsAst *node) { if (node->implementsSequence) { const KDevPG::ListNode *__it = node->implementsSequence->front(), *__end = __it; do { buildNamespaceUses(__it->element); __it = __it->next; } while (__it != __end); } } void UseBuilder::visitClassExtends(ClassExtendsAst *node) { buildNamespaceUses(node->identifier); } void UseBuilder::visitClassStatement(ClassStatementAst *node) { if (!node->traitsSequence) { UseBuilderBase::visitClassStatement(node); return; } const KDevPG::ListNode< NamespacedIdentifierAst* >* it = node->traitsSequence->front(); forever { buildNamespaceUses(it->element, ClassDeclarationType); if ( it->hasNext() ) { it = it->next; } else { break; } } if (node->imports) { visitTraitAliasDeclaration(node->imports); } UseBuilderBase::visitClassStatement(node); } void UseBuilder::visitTraitAliasStatement(TraitAliasStatementAst *node) { if (node->conflictIdentifierSequence) { const KDevPG::ListNode< NamespacedIdentifierAst* >* it = node->conflictIdentifierSequence->front(); forever { buildNamespaceUses(it->element, ClassDeclarationType); if ( it->hasNext() ) { it = it->next; } else { break; } } } DUChainWriteLocker lock; DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, identifierForNamespace(node->importIdentifier->identifier, m_editor)); if (dec) { QualifiedIdentifier original = identifierPairForNode(node->importIdentifier->methodIdentifier).second; QList list = dec.data()->internalContext()->findLocalDeclarations(original.last(), dec.data()->internalContext()->range().start); if (!list.isEmpty()) { UseBuilderBase::newUse(node->importIdentifier->methodIdentifier, DeclarationPointer(list.first())); } } lock.unlock(); visitTraitAliasIdentifier(node->importIdentifier); } void UseBuilder::visitTraitAliasIdentifier(TraitAliasIdentifierAst *node) { buildNamespaceUses(node->identifier, ClassDeclarationType); } void UseBuilder::visitExpr(ExprAst* node) { visitNodeWithExprVisitor(node); } void UseBuilder::visitGlobalVar(GlobalVarAst* node) { if (node->var) { DeclarationPointer dec = findDeclarationImport(GlobalVariableDeclarationType, node->var); if (dec) { newCheckedUse(node->var, dec); } } } void UseBuilder::visitStaticScalar(StaticScalarAst* node) { if (currentContext()->type() == DUContext::Class) { visitNodeWithExprVisitor(node); } } void UseBuilder::visitStatement(StatementAst *node) { if (node->foreachVar) { visitNodeWithExprVisitor(node->foreachVar); } else if (node->unsetVariablesSequence) { visitNodeWithExprVisitor(node); } if (node->foreachExprAsVar) { visitNodeWithExprVisitor(node->foreachExprAsVar); } if (node->foreachVarAsVar) { visitNodeWithExprVisitor(node->foreachVarAsVar); } if (node->foreachVariable) { visitNodeWithExprVisitor(node->foreachVariable); } UseBuilderBase::visitStatement(node); } void UseBuilder::visitCatchItem(CatchItemAst *node) { if (node->catchClass) { buildNamespaceUses(node->catchClass, ClassDeclarationType); } UseBuilderBase::visitCatchItem(node); } void UseBuilder::newCheckedUse(AstNode* node, const DeclarationPointer& declaration, bool reportNotFound) { if ( declaration && declaration->comment().contains("@deprecated") ) { reportError(i18n("Usage of %1 is deprecated.", declaration->toString()), node, IProblem::Hint); } else if ( !declaration && reportNotFound ) { reportError(i18n("Declaration not found: %1", m_editor->parseSession()->symbol(node)), node, IProblem::Hint); } UseBuilderBase::newUse(node, declaration); } void UseBuilder::visitUnaryExpression( UnaryExpressionAst* node ) { IndexedString includeFile = getIncludeFileForNode(node, m_editor); if ( !includeFile.isEmpty() ) { QualifiedIdentifier identifier(includeFile.str()); DUChainWriteLocker lock(DUChain::lock()); foreach ( Declaration* dec, currentContext()->topContext()->findDeclarations(identifier) ) { if ( dec->kind() == Declaration::Import ) { newUse(node->includeExpression, DeclarationPointer(dec)); return; } } } } void UseBuilder::visitUseNamespace(UseNamespaceAst* node) { buildNamespaceUses(node->identifier, NamespaceDeclarationType); } void UseBuilder::buildNamespaceUses(NamespacedIdentifierAst* node, DeclarationType lastType) { - const QualifiedIdentifier identifier = identifierForNamespace(node, m_editor); + QualifiedIdentifier identifier = identifierForNamespace(node, m_editor); + QualifiedIdentifier curId; + + // check if we need to resolve the namespaced identifier globally or locally + DeclarationPointer tempDec = findDeclarationImport(ClassDeclarationType, identifier); + + // if we couldn't find a class declaration, it might be a partial namespace identifier + if (!tempDec) { + tempDec = findDeclarationImport(NamespaceDeclarationType, identifier); + } + + if (!tempDec && !identifier.explicitlyGlobal()) { + identifier.setExplicitlyGlobal(true); + tempDec = findDeclarationImport(ClassDeclarationType, identifier); + + if (!tempDec) { + tempDec = findDeclarationImport(NamespaceDeclarationType, identifier); + } + + // Can't resolve either globally or locally, so revert back to original + if (!tempDec) { + identifier.setExplicitlyGlobal(false); + } + } + curId.setExplicitlyGlobal(identifier.explicitlyGlobal()); Q_ASSERT(identifier.count() == node->namespaceNameSequence->count()); for ( int i = 0; i < identifier.count() - 1; ++i ) { curId.push(identifier.at(i)); AstNode* n = node->namespaceNameSequence->at(i)->element; DeclarationPointer dec = findDeclarationImport(NamespaceDeclarationType, curId); if (!dec || dec->range() != editorFindRange(n, n)) { newCheckedUse(n, dec, true); } } newCheckedUse(node->namespaceNameSequence->back()->element, findDeclarationImport(lastType, identifier), lastType == ClassDeclarationType || lastType == ConstantDeclarationType || lastType == FunctionDeclarationType || lastType == NamespaceDeclarationType); } void UseBuilder::openNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier, const RangeInRevision& range) { if (node != parent->namespaceNameSequence->back()->element) { DeclarationPointer dec = findDeclarationImport(NamespaceDeclarationType, identifier.second); if (!dec || dec->range() != editorFindRange(node, node)) { newCheckedUse(node, dec); } } UseBuilderBase::openNamespace(parent, node, identifier, range); } void UseBuilder::visitNodeWithExprVisitor(AstNode* node) { UseExpressionVisitor v(m_editor, this); node->ducontext = currentContext(); v.visitNode(node); if (v.result().hadUnresolvedIdentifiers()) { m_hadUnresolvedIdentifiers = true; } } void UseBuilder::visitReturnType(ReturnTypeAst* node) { if (node->typehint && isClassTypehint(node->typehint, m_editor)) { buildNamespaceUses(node->typehint->genericType); } } } diff --git a/duchain/expressionvisitor.cpp b/duchain/expressionvisitor.cpp index 8eadf61..e00b1c0 100644 --- a/duchain/expressionvisitor.cpp +++ b/duchain/expressionvisitor.cpp @@ -1,894 +1,902 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "expressionvisitor.h" #include "parsesession.h" #include "editorintegrator.h" #include "helper.h" #include "declarations/variabledeclaration.h" #include "declarations/classdeclaration.h" #include #include #include #include #include #include #include #include #include "duchaindebug.h" #define ifDebug(x) using namespace KDevelop; namespace Php { ExpressionVisitor::ExpressionVisitor(EditorIntegrator* editor) : m_editor(editor), m_createProblems(false), m_offset(CursorInRevision::invalid()), m_currentContext(nullptr), m_isAssignmentExpressionEqual(false), m_inDefine(false) { } DeclarationPointer ExpressionVisitor::processVariable(VariableIdentifierAst* variable) { Q_ASSERT(m_currentContext); CursorInRevision position = m_editor->findPosition(variable->variable, EditorIntegrator::BackEdge); if ( m_offset.isValid() ) { position.line += m_offset.line; position.column += m_offset.column; } DeclarationPointer ret; Identifier identifier = identifierForNode(variable).last(); ifDebug(qCDebug(DUCHAIN) << "processing variable" << identifier.toString() << position.castToSimpleCursor();) DUChainReadLocker lock; if (identifier.nameEquals(Identifier(QStringLiteral("this")))) { if (m_currentContext->parentContext() && m_currentContext->parentContext()->type() == DUContext::Class && m_currentContext->parentContext()->owner()) { ret = m_currentContext->parentContext()->owner(); } } else { //DontSearchInParent-flag because (1) in Php global variables aren't available in function //context and (2) a function body consists of a single context (so this is no problem) ret = findVariableDeclaration(m_currentContext, identifier, position, DUContext::DontSearchInParent); } if (!ret && m_currentContext->type() == DUContext::Namespace) { ret = findVariableDeclaration(m_currentContext, identifier, position, DUContext::NoSearchFlags); } if (!ret) { //look for a function argument ///TODO: why doesn't m_currentContext->findDeclarations() work? /// evaluate if the stuff below is fast enough (faster?) than findDeclarations() ///see r1028306 foreach(const DUContext::Import &import, m_currentContext->importedParentContexts() ) { if ( !import.isDirect() || import.position > position ) { continue; } DUContext* ctx = import.context(m_currentContext->topContext()); if ( ctx->type() == DUContext::Function ) { QList args = ctx->findLocalDeclarations(identifier); if ( !args.isEmpty() ) { ret = args.first(); break; } } } } if (!ret) { //look for a superglobal variable foreach(Declaration* dec, m_currentContext->topContext()->findDeclarations(identifier, position)) { VariableDeclaration* varDec = dynamic_cast(dec); if (varDec && varDec->isSuperglobal()) { ret = dec; break; } } } lock.unlock(); if ( !m_isAssignmentExpressionEqual || identifier.nameEquals( Identifier(QStringLiteral("this")) ) // might be something like $s = $s . $s; in which case we have to add a use for the first $s || (ret && ret->range().end < position) ) { // also don't report uses for the place of declaration if (!ret || ret->range().end != position) { usingDeclaration(variable, ret); } } ifDebug(qCDebug(DUCHAIN) << "found declaration:" << (ret ? ret->toString() : QString("no declaration found"));) return ret; } void ExpressionVisitor::visitNode(AstNode *node) { if (node && node->ducontext) { m_currentContext = node->ducontext; } Q_ASSERT(m_currentContext); DefaultVisitor::visitNode(node); } void ExpressionVisitor::visitAssignmentExpression(AssignmentExpressionAst *node) { if (node->assignmentExpressionEqual) { m_isAssignmentExpressionEqual = true; } visitNode(node->expression); m_isAssignmentExpressionEqual = false; visitNode(node->assignmentExpressionEqual); visitNode(node->assignmentExpression); if (node->operation == OperationPlus || node->operation == OperationMinus || node->operation == OperationMul || node->operation == OperationDiv || node->operation == OperationExp) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else if (node->operation == OperationConcat) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } void ExpressionVisitor::visitArrayIndexSpecifier(ArrayIndexSpecifierAst* node) { DefaultVisitor::visitArrayIndexSpecifier(node); // it's an array item but we don't support it really, so just assign type mixed and be done m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); } void ExpressionVisitor::visitCompoundVariableWithSimpleIndirectReference(CompoundVariableWithSimpleIndirectReferenceAst *node) { if (node->variable) { m_result.setDeclaration(processVariable(node->variable)); } DefaultVisitor::visitCompoundVariableWithSimpleIndirectReference(node); } void ExpressionVisitor::visitVariable(VariableAst* node) { if ( node->variablePropertiesSequence && node->variablePropertiesSequence->front() && node->variablePropertiesSequence->front()->element && node->variablePropertiesSequence->front()->element->variableProperty && node->variablePropertiesSequence->front()->element->variableProperty->objectProperty ) { // make sure we mark $foo as a use in $foo->... bool isAssignmentExpressionEqual = m_isAssignmentExpressionEqual; m_isAssignmentExpressionEqual = false; DefaultVisitor::visitVariable(node); m_isAssignmentExpressionEqual = isAssignmentExpressionEqual; } else { DefaultVisitor::visitVariable(node); } } void ExpressionVisitor::visitVarExpression(VarExpressionAst *node) { DefaultVisitor::visitVarExpression(node); if (node->isGenerator != -1) { DeclarationPointer generatorDecl = findDeclarationImport(ClassDeclarationType, QualifiedIdentifier("generator")); if (generatorDecl) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); if (hasCurrentClosureReturnType()) { FunctionType::Ptr closureType = currentClosureReturnType().cast(); closureType->setReturnType(generatorDecl->abstractType()); } } } } void ExpressionVisitor::visitVarExpressionNewObject(VarExpressionNewObjectAst *node) { DefaultVisitor::visitVarExpressionNewObject(node); if (node->className->staticIdentifier != -1) { static const QualifiedIdentifier id(QStringLiteral("static")); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->className, dec); m_result.setDeclaration(dec); } else if (node->className->identifier) { const QualifiedIdentifier id = identifierForNamespace(node->className->identifier, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->className->identifier->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->className->identifier, id); m_result.setDeclaration(dec); } } void ExpressionVisitor::visitVarExpressionArray(VarExpressionArrayAst *node) { DefaultVisitor::visitVarExpressionArray(node); m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeArray))); } void ExpressionVisitor::visitClosure(ClosureAst* node) { auto* closureType = new FunctionType; closureType->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); openClosureReturnType(AbstractType::Ptr(closureType)); if (node->functionBody) { visitInnerStatementList(node->functionBody); } if (node->returnType && node->returnType->typehint && isClassTypehint(node->returnType->typehint, m_editor)) { NamespacedIdentifierAst* objectType = node->returnType->typehint->genericType; QualifiedIdentifier id = identifierForNamespace(objectType, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(objectType->namespaceNameSequence->back()->element, dec); buildNamespaceUses(objectType, id); } //Override found type with return typehint or phpdoc return typehint AbstractType::Ptr type = returnType(node->returnType, {}, m_editor, m_currentContext); if (type) { closureType->setReturnType(type); } if (node->parameters->parametersSequence) { const KDevPG::ListNode< ParameterAst* >* it = node->parameters->parametersSequence->front(); forever { AbstractType::Ptr type = parameterType(it->element, {}, m_editor, m_currentContext); closureType->addArgument(type); if (it->element->parameterType && it->element->parameterType->typehint && isClassTypehint(it->element->parameterType->typehint, m_editor)) { NamespacedIdentifierAst* objectType = it->element->parameterType->typehint->genericType; QualifiedIdentifier id = identifierForNamespace(objectType, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(objectType->namespaceNameSequence->back()->element, dec); buildNamespaceUses(objectType, id); } if (it->element->defaultValue) { visitExpr(it->element->defaultValue); } if ( it->hasNext() ) { it = it->next; } else { break; } } } if (node->lexicalVars && node->lexicalVars->lexicalVarsSequence) { const KDevPG::ListNode< LexicalVarAst* >* it = node->lexicalVars->lexicalVarsSequence->front(); DUChainWriteLocker lock; forever { DeclarationPointer found; foreach(Declaration* dec, m_currentContext->findDeclarations(identifierForNode(it->element->variable))) { if (dec->kind() == Declaration::Instance) { found = dec; break; } } usingDeclaration(it->element->variable, found); if ( it->hasNext() ) { it = it->next; } else { break; } } } m_result.setType(AbstractType::Ptr(closureType)); closeClosureReturnType(); } void ExpressionVisitor::visitFunctionCallParameterList( FunctionCallParameterListAst* node ) { QList decs = m_result.allDeclarations(); AbstractType::Ptr type = m_result.type(); DefaultVisitor::visitFunctionCallParameterList( node ); m_result.setDeclarations(decs); m_result.setType(type); } void ExpressionVisitor::visitFunctionCallParameterListElement(FunctionCallParameterListElementAst* node) { DefaultVisitor::visitFunctionCallParameterListElement(node); if (m_inDefine) m_inDefine = false; //reset after first parameter passed, the second argument can be a class name } void ExpressionVisitor::visitFunctionCall(FunctionCallAst* node) { if (node->stringFunctionNameOrClass && !node->stringFunctionName && !node->varFunctionName) { QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); if (id.toString(RemoveExplicitlyGlobalPrefix) == QLatin1String("define") && node->stringParameterList && node->stringParameterList->parametersSequence && node->stringParameterList->parametersSequence->count() > 0) { //in a define() call the first argument is the constant name. we don't want to look for a class name to build uses m_inDefine = true; } } DefaultVisitor::visitFunctionCall(node); m_inDefine = false; if (node->stringFunctionNameOrClass) { if (node->stringFunctionName) { //static function call foo::bar() DUContext* context = findClassContext(node->stringFunctionNameOrClass); if (context) { DUChainReadLocker lock(DUChain::lock()); QualifiedIdentifier methodName(stringForNode(node->stringFunctionName).toLower()); m_result.setDeclarations(context->findDeclarations(methodName)); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node->stringFunctionName, m_result.allDeclarations().last()); FunctionType::Ptr function = m_result.allDeclarations().last()->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } } else { m_result.setHadUnresolvedIdentifiers(true); usingDeclaration(node->stringFunctionName, DeclarationPointer()); m_result.setType(AbstractType::Ptr()); } } else if (node->varFunctionName) { //static function call foo::$bar() } else if (node->expr) { //static function call foo::{expr}() const QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->stringFunctionNameOrClass->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->stringFunctionNameOrClass, id); m_result.setDeclaration(dec); } else { //global function call foo(); - const QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); + QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); DeclarationPointer dec = findDeclarationImport(FunctionDeclarationType, id); + if (!dec) { + id.setExplicitlyGlobal(true); + dec = findDeclarationImport(FunctionDeclarationType, id); + } ifDebug(qCDebug(DUCHAIN) << "function call of" << (dec ? dec->toString() : QString("function not found"));) m_result.setDeclaration(dec); usingDeclaration(node->stringFunctionNameOrClass->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->stringFunctionNameOrClass, id); if (dec) { FunctionType::Ptr function = dec->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } else { m_result.setHadUnresolvedIdentifiers(true); } } } } ///TODO: DUContext pointer? DUContext* ExpressionVisitor::findClassContext(IdentifierAst* className) { DUContext* context = nullptr; DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, className); usingDeclaration(className, declaration); if (declaration) { DUChainReadLocker lock(DUChain::lock()); context = declaration->internalContext(); if (!context && m_currentContext->parentContext() && m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //className is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } return context; } ///TODO: DUContext pointer? DUContext* ExpressionVisitor::findClassContext(NamespacedIdentifierAst* className) { DUContext* context = nullptr; const QualifiedIdentifier id = identifierForNamespace(className, m_editor); DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(className->namespaceNameSequence->back()->element, declaration); buildNamespaceUses(className, id); if (declaration) { DUChainReadLocker lock(DUChain::lock()); context = declaration->internalContext(); if (!context && m_currentContext->parentContext() && m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //className is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } return context; } void ExpressionVisitor::visitConstantOrClassConst(ConstantOrClassConstAst *node) { DefaultVisitor::visitConstantOrClassConst(node); if (node->classConstant) { //class constant Foo::BAR DUContext* context = findClassContext(node->constant); if (context) { DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(context->findDeclarations(Identifier(m_editor->parseSession()->symbol(node->classConstant)))); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node->classConstant, m_result.allDeclarations().last()); } else { usingDeclaration(node->classConstant, DeclarationPointer()); } if (!stringForNode(node->classConstant).compare(QLatin1String("class"), Qt::CaseInsensitive)) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } else { m_result.setType(AbstractType::Ptr()); } } else { QString str(stringForNode(node->constant).toLower()); if (str == QLatin1String("true") || str == QLatin1String("false")) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } else if (str == QLatin1String("null")) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeNull))); } else { //constant (created with declare('foo', 'bar')) or const Foo = 1; QualifiedIdentifier id = identifierForNamespace(node->constant, m_editor, true); DeclarationPointer declaration = findDeclarationImport(ConstantDeclarationType, id); + if (!declaration) { + id.setExplicitlyGlobal(true); + declaration = findDeclarationImport(ConstantDeclarationType, id); + } if (!declaration) { ///TODO: is this really wanted? //it could also be a global function call, without () declaration = findDeclarationImport(FunctionDeclarationType, id); } m_result.setDeclaration(declaration); usingDeclaration(node->constant->namespaceNameSequence->back()->element, declaration); buildNamespaceUses(node->constant, id); } } } void ExpressionVisitor::visitScalar(ScalarAst *node) { DefaultVisitor::visitScalar(node); if (node->commonScalar) { uint type = IntegralType::TypeVoid; switch (node->commonScalar->scalarType) { case ScalarTypeInt: type = IntegralType::TypeInt; break; case ScalarTypeFloat: type = IntegralType::TypeFloat; break; case ScalarTypeString: type = IntegralType::TypeString; break; } m_result.setType(AbstractType::Ptr(new IntegralType(type))); } else if (node->varname != -1) { //STRING_VARNAME-Token, probably the type of varname should be used m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } else if (node->encapsList) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } if (!m_inDefine && node->commonScalar && node->commonScalar->scalarType == ScalarTypeString) { QString str = m_editor->parseSession()->symbol(node->commonScalar); QRegExp exp("^['\"]([A-Za-z0-9_]+)['\"]$"); if (exp.exactMatch(str)) { //that *could* be a class name QualifiedIdentifier id(exp.cap(1).toLower()); DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, id); if (declaration) { usingDeclaration(node->commonScalar, declaration); } else { m_result.setHadUnresolvedIdentifiers(true); } } } } void ExpressionVisitor::visitStaticScalar(StaticScalarAst *node) { if (node->ducontext) { m_currentContext = node->ducontext; } Q_ASSERT(m_currentContext); DefaultVisitor::visitStaticScalar(node); uint type = IntegralType::TypeVoid; if (node->value) { switch (node->value->scalarType) { case ScalarTypeInt: type = IntegralType::TypeInt; break; case ScalarTypeFloat: type = IntegralType::TypeFloat; break; case ScalarTypeString: type = IntegralType::TypeString; break; } } else if (node->plusValue || node->minusValue) { type = IntegralType::TypeInt; } else if (node->array != -1) { type = IntegralType::TypeArray; } if (type != IntegralType::TypeVoid) { m_result.setType(AbstractType::Ptr(new IntegralType(type))); } } void ExpressionVisitor::visitEncapsVar(EncapsVarAst *node) { DefaultVisitor::visitEncapsVar(node); if (node->variable) { // handle $foo DeclarationPointer dec = processVariable(node->variable); if (dec && node->propertyIdentifier) { // handle property in $foo->bar DeclarationPointer foundDec; DUChainReadLocker lock(DUChain::lock()); if ( StructureType::Ptr structType = dec->type() ) { if ( ClassDeclaration* cdec = dynamic_cast(structType->declaration(m_currentContext->topContext())) ) { ///TODO: share code with visitVariableProperty DUContext* ctx = cdec->internalContext(); if (!ctx && m_currentContext->parentContext()) { if (m_currentContext->parentContext()->localScopeIdentifier() == cdec->qualifiedIdentifier()) { //class is currentClass (internalContext is not yet set) ctx = m_currentContext->parentContext(); } } if (ctx) { foreach( Declaration* pdec, ctx->findDeclarations(identifierForNode(node->propertyIdentifier)) ) { if ( !pdec->isFunctionDeclaration() ) { foundDec = pdec; break; } } } } } lock.unlock(); usingDeclaration(node->propertyIdentifier, foundDec); } } } void ExpressionVisitor::visitVariableProperty(VariablePropertyAst *node) { ifDebug(qCDebug(DUCHAIN) << "node:" << m_editor->parseSession()->symbol(node) << (node->isFunctionCall != -1 ? QString("is function call") : QString("is no function call"));) if (node->objectProperty && node->objectProperty->objectDimList) { //handle $foo->bar() and $foo->baz, $foo is m_result.type() AbstractType::Ptr type = m_result.type(); //If the variable type is unsure, try to see if it contains a StructureType. If so, use that // (since the other types do not allow accessing properties) if (type && type.cast()) { UnsureType::Ptr unsureType = type.cast(); int numStructureType = 0; StructureType::Ptr structureType; for (unsigned int i = 0; itypesSize(); ++i) { StructureType::Ptr subType = unsureType->types()[i].type(); if (subType) { structureType = subType; ++numStructureType; } } //Only use the found structureType if there's exactly *one* such type if (numStructureType == 1) { Q_ASSERT(structureType); type = AbstractType::Ptr(structureType); } } if (type && StructureType::Ptr::dynamicCast(type)) { DUChainReadLocker lock(DUChain::lock()); Declaration* declaration = StructureType::Ptr::staticCast(type)->declaration(m_currentContext->topContext()); if (declaration) { ifDebug(qCDebug(DUCHAIN) << "parent:" << declaration->toString();) DUContext* context = declaration->internalContext(); if (!context && m_currentContext->parentContext()) { if (m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //class is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } if (context) { QualifiedIdentifier propertyId; if ( node->isFunctionCall != -1 ) { propertyId = QualifiedIdentifier(stringForNode(node->objectProperty->objectDimList->variableName->name).toLower()); } else { propertyId = identifierForNode(node->objectProperty->objectDimList->variableName->name); } ifDebug(qCDebug(DUCHAIN) << "property id:" << propertyId.toString();) QList decs; foreach ( Declaration* dec, context->findDeclarations(propertyId) ) { if ( node->isFunctionCall != -1 ) { if ( dec->isFunctionDeclaration() ) { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found:" << dec->toString();) } } else { if ( !dec->isFunctionDeclaration() ) { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found:" << dec->toString();) } } } m_result.setDeclarations(decs); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { if ( !m_isAssignmentExpressionEqual ) { usingDeclaration(node->objectProperty->objectDimList->variableName, m_result.allDeclarations().last()); } if (node->isFunctionCall != -1) { FunctionType::Ptr function = m_result.allDeclarations().last()->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } } else { if ( !m_isAssignmentExpressionEqual ) { usingDeclaration(node->objectProperty->objectDimList->variableName, DeclarationPointer()); } m_result.setType(AbstractType::Ptr()); } } else { m_result.setType(AbstractType::Ptr()); } } else { m_result.setType(AbstractType::Ptr()); } } } DefaultVisitor::visitVariableProperty(node); } void ExpressionVisitor::visitStaticMember(StaticMemberAst* node) { //don't call DefaultVisitor::visitStaticMember(node); //because we would end up in visitCompoundVariableWithSimpleIndirectReference if (node->variable->variable->variable) { DUContext* context = findClassContext(node->className); if (context) { DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(context->findDeclarations(identifierForNode(node->variable->variable->variable))); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node->variable->variable->variable, m_result.allDeclarations().last()); } else { usingDeclaration(node->variable->variable->variable, DeclarationPointer()); } } else { usingDeclaration(node->className, DeclarationPointer()); m_result.setType(AbstractType::Ptr()); } if (node->variable->offsetItemsSequence) { const KDevPG::ListNode< DimListItemAst* >* it = node->variable->offsetItemsSequence->front(); do { visitDimListItem(it->element); } while(it->hasNext() && (it = it->next)); } } } void ExpressionVisitor::visitUnaryExpression(UnaryExpressionAst* node) { DefaultVisitor::visitUnaryExpression(node); if (node->castType) { uint type = 0; switch (node->castType) { case CastInt: type = IntegralType::TypeInt; break; case CastDouble: type = IntegralType::TypeFloat; break; case CastString: type = IntegralType::TypeString; break; case CastArray: type = IntegralType::TypeArray; break; case CastObject: { /// Qualified identifier for 'stdclass' static const QualifiedIdentifier stdclassQId(QStringLiteral("stdclass")); DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(m_currentContext->findDeclarations(stdclassQId)); break; } case CastBool: type = IntegralType::TypeBoolean; break; case CastUnset: //TODO break; } if (type) { m_result.setType(AbstractType::Ptr(new IntegralType(type))); } } } void ExpressionVisitor::visitAdditiveExpressionRest(AdditiveExpressionRestAst* node) { DefaultVisitor::visitAdditiveExpressionRest(node); if (node->operation == OperationPlus || node->operation == OperationMinus) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else if (node->operation == OperationConcat) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } void ExpressionVisitor::visitRelationalExpression(RelationalExpressionAst *node) { DefaultVisitor::visitRelationalExpression(node); if (node->instanceofType && node->instanceofType->identifier) { const QualifiedIdentifier id = identifierForNamespace(node->instanceofType->identifier, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->instanceofType->identifier->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->instanceofType->identifier, id); m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } } void ExpressionVisitor::visitRelationalExpressionRest(RelationalExpressionRestAst *node) { DefaultVisitor::visitRelationalExpressionRest(node); m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } void ExpressionVisitor::visitEqualityExpressionRest(EqualityExpressionRestAst *node) { DefaultVisitor::visitEqualityExpressionRest(node); if (node->operation && node->operation == OperationSpaceship) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } } void ExpressionVisitor::visitStatement(StatementAst *node) { DefaultVisitor::visitStatement(node); if (node->returnExpr) { FunctionType::Ptr closureType = currentClosureReturnType().cast(); if (closureType) { closureType->setReturnType(m_result.type()); } } } QString ExpressionVisitor::stringForNode(AstNode* id) { if (!id) return QString(); return m_editor->parseSession()->symbol(id); } QualifiedIdentifier ExpressionVisitor::identifierForNode(IdentifierAst* id) { if (!id) return QualifiedIdentifier(); return QualifiedIdentifier(stringForNode(id)); } QString ExpressionVisitor::stringForNode(VariableIdentifierAst* id) { if (!id) return QString(); QString ret(m_editor->parseSession()->symbol(id->variable)); ret = ret.mid(1); //cut off $ return ret; } QualifiedIdentifier ExpressionVisitor::identifierForNode(VariableIdentifierAst* id) { if (!id) return QualifiedIdentifier(); return QualifiedIdentifier(stringForNode(id)); } void ExpressionVisitor::setCreateProblems(bool v) { m_createProblems = v; } void ExpressionVisitor::setOffset(const CursorInRevision& offset) { m_offset = offset; } void ExpressionVisitor::buildNamespaceUses(NamespacedIdentifierAst* namespaces, const QualifiedIdentifier& identifier) { QualifiedIdentifier curId; curId.setExplicitlyGlobal(identifier.explicitlyGlobal()); Q_ASSERT(identifier.count() == namespaces->namespaceNameSequence->count()); for ( int i = 0; i < identifier.count() - 1; ++i ) { curId.push(identifier.at(i)); AstNode* node = namespaces->namespaceNameSequence->at(i)->element; DeclarationPointer dec = findDeclarationImport(NamespaceDeclarationType, curId); usingDeclaration(node, dec); } } DeclarationPointer ExpressionVisitor::findDeclarationImport(DeclarationType declarationType, IdentifierAst* node) { // methods and class names are case insensitive QualifiedIdentifier id; if ( declarationType == ClassDeclarationType || declarationType == FunctionDeclarationType ) { id = QualifiedIdentifier(stringForNode(node).toLower()); } else { id = identifierForNode(node); } return findDeclarationImport(declarationType, id); } DeclarationPointer ExpressionVisitor::findDeclarationImport(DeclarationType declarationType, VariableIdentifierAst* node) { return findDeclarationImport(declarationType, identifierForNode(node)); } DeclarationPointer ExpressionVisitor::findDeclarationImport( DeclarationType declarationType, const QualifiedIdentifier& identifier) { return findDeclarationImportHelper(m_currentContext, identifier, declarationType); } Declaration* ExpressionVisitor::findVariableDeclaration(DUContext* context, Identifier identifier, CursorInRevision position, DUContext::SearchFlag flag) { QList decls = context->findDeclarations(identifier, position, nullptr, flag); for (int i = decls.count() - 1; i >= 0; i--) { Declaration *dec = decls.at(i); if (dec->kind() == Declaration::Instance && dynamic_cast(dec)) { return dec; } } return nullptr; } } diff --git a/duchain/helper.cpp b/duchain/helper.cpp index ebc04a5..27b819d 100644 --- a/duchain/helper.cpp +++ b/duchain/helper.cpp @@ -1,602 +1,599 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "helper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "editorintegrator.h" #include "../parser/parsesession.h" #include "phpast.h" #include "phpdefaultvisitor.h" #include "declarations/classdeclaration.h" #include "declarations/classmethoddeclaration.h" #include "declarations/functiondeclaration.h" #include "types/indexedcontainer.h" #include "types/integraltypeextended.h" #include "expressionparser.h" #include "expressionvisitor.h" #include "duchaindebug.h" #define ifDebug(x) using namespace KDevelop; namespace Php { bool isMatch(Declaration* declaration, DeclarationType declarationType) { if (declarationType == ClassDeclarationType && dynamic_cast(declaration) ) { return true; } else if (declarationType == FunctionDeclarationType && dynamic_cast(declaration) ) { return true; } else if (declarationType == ConstantDeclarationType && declaration->abstractType() && declaration->abstractType()->modifiers() & AbstractType::ConstModifier && (!declaration->context() || declaration->context()->type() != DUContext::Class) ) { return true; } else if (declarationType == GlobalVariableDeclarationType && declaration->kind() == Declaration::Instance && !(declaration->abstractType() && declaration->abstractType()->modifiers() & AbstractType::ConstModifier) ) { return true; } else if (declarationType == NamespaceDeclarationType && (declaration->kind() == Declaration::Namespace || declaration->kind() == Declaration::NamespaceAlias || dynamic_cast(declaration)) ) { return true; } return false; } bool isClassTypehint(GenericTypeHintAst* genericType, EditorIntegrator *editor) { Q_ASSERT(genericType); if (genericType->callableType != -1) { return false; } else if (genericType->arrayType != -1) { return false; } else if (genericType->genericType) { NamespacedIdentifierAst* node = genericType->genericType; const KDevPG::ListNode< IdentifierAst* >* it = node->namespaceNameSequence->front(); QString typehint = editor->parseSession()->symbol(it->element); if (typehint.compare(QLatin1String("bool"), Qt::CaseInsensitive) == 0) { return false; } else if (typehint.compare(QLatin1String("float"), Qt::CaseInsensitive) == 0) { return false; } else if (typehint.compare(QLatin1String("int"), Qt::CaseInsensitive) == 0) { return false; } else if (typehint.compare(QLatin1String("string"), Qt::CaseInsensitive) == 0) { return false; } else if (typehint.compare(QLatin1String("iterable"), Qt::CaseInsensitive) == 0) { return false; } else if (typehint.compare(QLatin1String("object"), Qt::CaseInsensitive) == 0) { return false; } else { return true; } } else { return false; } } DeclarationPointer findDeclarationImportHelper(DUContext* currentContext, const QualifiedIdentifier& id, DeclarationType declarationType) { /// Qualified identifier for 'self' static const QualifiedIdentifier selfQId(QStringLiteral("self")); /// Qualified identifier for 'parent' static const QualifiedIdentifier parentQId(QStringLiteral("parent")); /// Qualified identifier for 'static' static const QualifiedIdentifier staticQId(QStringLiteral("static")); - ifDebug(qCDebug(DUCHAIN) << id.toString() << declarationType;) + QualifiedIdentifier lookup; + + if (id.explicitlyGlobal()) { + ifDebug(qCDebug(DUCHAIN) << id.toString() << declarationType;) + + lookup = id; + lookup.setExplicitlyGlobal(false); + } else { + lookup = identifierWithNamespace(id, currentContext); + + ifDebug(qCDebug(DUCHAIN) << lookup.toString() << declarationType;) + } + if (declarationType == ClassDeclarationType && id == selfQId) { DUChainReadLocker lock(DUChain::lock()); if (currentContext->type() == DUContext::Class) { return DeclarationPointer(currentContext->owner()); } else if (currentContext->parentContext() && currentContext->parentContext()->type() == DUContext::Class) { return DeclarationPointer(currentContext->parentContext()->owner()); } else { return DeclarationPointer(); } } else if (declarationType == ClassDeclarationType && id == staticQId) { DUChainReadLocker lock; if (currentContext->type() == DUContext::Class) { return DeclarationPointer(currentContext->owner()); } else if (currentContext->parentContext() && currentContext->parentContext()->type() == DUContext::Class) { return DeclarationPointer(currentContext->parentContext()->owner()); } else { return DeclarationPointer(); } } else if (declarationType == ClassDeclarationType && id == parentQId) { //there can be just one Class-Context imported DUChainReadLocker lock; DUContext* classCtx = nullptr; if (currentContext->type() == DUContext::Class) { classCtx = currentContext; } else if (currentContext->parentContext() && currentContext->parentContext()->type() == DUContext::Class) { classCtx = currentContext->parentContext(); } if (classCtx) { foreach(const DUContext::Import &i, classCtx->importedParentContexts()) { DUContext* ctx = i.context(classCtx->topContext()); if (ctx && ctx->type() == DUContext::Class) { return DeclarationPointer(ctx->owner()); } } } return DeclarationPointer(); } else { DUChainReadLocker lock; - QList foundDeclarations = currentContext->topContext()->findDeclarations(id); - if (foundDeclarations.isEmpty()) { - // If it's not in the top context, try the current context (namespaces...) - // this fixes the bug: https://bugs.kde.org/show_bug.cgi?id=322274 - foundDeclarations = currentContext->findDeclarations(id); - } - if (foundDeclarations.isEmpty()) { - // If it is neither in the top not the current context it might be defined in a different context - // Look up with fully qualified identifier - foundDeclarations = currentContext->topContext()->findDeclarations(identifierWithNamespace(id, currentContext)); - } + QList foundDeclarations = currentContext->topContext()->findDeclarations(lookup); foreach(Declaration *declaration, foundDeclarations) { if (isMatch(declaration, declarationType)) { return DeclarationPointer(declaration); } } if ( currentContext->url() == internalFunctionFile() ) { // when compiling php internal functions, we don't need to ask the persistent symbol table for anything return DeclarationPointer(); } lock.unlock(); if (declarationType != GlobalVariableDeclarationType) { ifDebug(qCDebug(DUCHAIN) << "No declarations found with findDeclarations, trying through PersistentSymbolTable";) DeclarationPointer decl; - decl = findDeclarationInPST(currentContext, id, declarationType); - - if (!decl) - { - decl = findDeclarationInPST(currentContext, identifierWithNamespace(id, currentContext), declarationType); - } + decl = findDeclarationInPST(currentContext, lookup, declarationType); if (decl) { ifDebug(qCDebug(DUCHAIN) << "PST declaration exists";) } else { ifDebug(qCDebug(DUCHAIN) << "PST declaration does not exist";) } return decl; } } ifDebug(qCDebug(DUCHAIN) << "returning 0";) return DeclarationPointer(); } DeclarationPointer findDeclarationInPST(DUContext* currentContext, QualifiedIdentifier id, DeclarationType declarationType) { ifDebug(qCDebug(DUCHAIN) << "PST: " << id.toString() << declarationType;) uint nr; const IndexedDeclaration* declarations = nullptr; DUChainWriteLocker wlock; PersistentSymbolTable::self().declarations(id, nr, declarations); ifDebug(qCDebug(DUCHAIN) << "found declarations:" << nr;) /// Indexed string for 'Php', identifies environment files from this language plugin static const IndexedString phpLangString("Php"); for (uint i = 0; i < nr; ++i) { ParsingEnvironmentFilePointer env = DUChain::self()->environmentFileForDocument(declarations[i].indexedTopContext()); if(!env) { ifDebug(qCDebug(DUCHAIN) << "skipping declaration, missing meta-data";) continue; } if(env->language() != phpLangString) { ifDebug(qCDebug(DUCHAIN) << "skipping declaration, invalid language" << env->language().str();) continue; } if (!declarations[i].declaration()) { ifDebug(qCDebug(DUCHAIN) << "skipping declaration, doesn't have declaration";) continue; } else if (!isMatch(declarations[i].declaration(), declarationType)) { ifDebug(qCDebug(DUCHAIN) << "skipping declaration, doesn't match with declarationType";) continue; } TopDUContext* top = declarations[i].declaration()->context()->topContext(); /* * NOTE: * To enable PHPUnit test classes, this check has been disabled. * Formerly it only loaded declarations from open projects, but PHPUnit declarations * belong to no project. * * If this behavior is unwanted, reinstate the check. * Miha Cancula */ /* if (ICore::self() && !ICore::self()->projectController()->projects().isEmpty()) { bool loadedProjectContainsUrl = false; foreach(IProject *project, ICore::self()->projectController()->projects()) { if (project->fileSet().contains(top->url())) { loadedProjectContainsUrl = true; break; } } if (!loadedProjectContainsUrl) { ifDebug(qCDebug(DUCHAIN) << "skipping declaration, not in loaded project";) continue; } } */ currentContext->topContext()->addImportedParentContext(top); currentContext->topContext()->parsingEnvironmentFile() ->addModificationRevisions(top->parsingEnvironmentFile()->allModificationRevisions()); currentContext->topContext()->updateImportsCache(); ifDebug(qCDebug(DUCHAIN) << "using" << declarations[i].declaration()->toString() << top->url();) wlock.unlock(); return DeclarationPointer(declarations[i].declaration()); } wlock.unlock(); ifDebug(qCDebug(DUCHAIN) << "returning 0";) return DeclarationPointer(); } QByteArray formatComment(AstNode* node, EditorIntegrator* editor) { return KDevelop::formatComment(editor->parseSession()->docComment(node->startToken).toUtf8()); } //Helper visitor to extract a commonScalar node //used to get the value of an function call argument class ScalarExpressionVisitor : public DefaultVisitor { public: ScalarExpressionVisitor() : m_node(nullptr) {} CommonScalarAst* node() const { return m_node; } private: void visitCommonScalar(CommonScalarAst* node) override { m_node = node; } CommonScalarAst* m_node; }; CommonScalarAst* findCommonScalar(AstNode* node) { ScalarExpressionVisitor visitor; visitor.visitNode(node); return visitor.node(); } static bool includeExists(const Path &include) { const QString path = include.pathOrUrl(); { DUChainReadLocker lock; if (DUChain::self()->chainForDocument(IndexedString(path))) { return true; } } if ( include.isLocalFile() ) { return QFile::exists(path); } else { return false; } } static IndexedString findIncludeFile(const QString &includePath, const IndexedString ¤tDocument) { if ( includePath.isEmpty() ) { return IndexedString(); } // check remote files if ( includePath.startsWith(QLatin1String("http://"), Qt::CaseInsensitive) || includePath.startsWith(QLatin1String("ftp://"), Qt::CaseInsensitive) ) { // always expect remote includes to exist return IndexedString(includePath); } const Path currentPath(currentDocument.str()); // look for file relative to current url Path include = Path(currentPath.parent(), includePath); if ( includeExists(include) ) { return IndexedString(include.pathOrUrl()); } // in the first round look for a project that is a parent of the current document // in the next round look for any project for (int i = 0; i < 2; ++i) { foreach(IProject* project, ICore::self()->projectController()->projects()) { if ( !i && !project->path().isParentOf(currentPath)) { continue; } include = Path(project->path(), includePath); if ( includeExists(include) ) { return IndexedString(include.pathOrUrl()); } } } //TODO configurable include paths return IndexedString(); } IndexedString getIncludeFileForNode(UnaryExpressionAst* node, EditorIntegrator* editor) { if ( node->includeExpression ) { //find name of the constant (first argument of the function call) CommonScalarAst* scalar = findCommonScalar(node->includeExpression); if (scalar && scalar->string != -1) { QString str = editor->parseSession()->symbol(scalar->string); str = str.mid(1, str.length() - 2); if ( str == QLatin1String(".") || str == QLatin1String("..") || str.endsWith('/') ) { return IndexedString(); } return findIncludeFile(str, editor->parseSession()->currentDocument()); } } return IndexedString(); } QString prettyName(Declaration* dec) { if (!dec) { return {}; } else if ( dec->context() && dec->context()->type() == DUContext::Class && dec->isFunctionDeclaration() ) { ClassMethodDeclaration* classMember = dynamic_cast(dec); Q_ASSERT(classMember); return classMember->prettyName().str(); } else if ( dec->isFunctionDeclaration() ) { FunctionDeclaration* func = dynamic_cast(dec); Q_ASSERT(func); return func->prettyName().str(); } else if ( dec->internalContext() && dec->internalContext()->type() == DUContext::Class ) { ClassDeclaration* classDec = dynamic_cast(dec); Q_ASSERT(classDec); return classDec->prettyName().str(); } else { return dec->identifier().toString(); } } const KDevelop::IndexedString& internalFunctionFile() { static const KDevelop::IndexedString internalFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevphpsupport/phpfunctions.php"))); return internalFile; } const KDevelop::IndexedString& phpLanguageString() { static const KDevelop::IndexedString phpLangString("Php"); return phpLangString; } const IndexedString& internalTestFile() { static const KDevelop::IndexedString phpUnitFile(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevphpsupport/phpunitdeclarations.php"))); return phpUnitFile; } QualifiedIdentifier identifierForNamespace(NamespacedIdentifierAst* node, EditorIntegrator* editor, bool lastIsConstIdentifier) { QualifiedIdentifier id; if (node->isGlobal != -1) { id.setExplicitlyGlobal(true); } const KDevPG::ListNode< IdentifierAst* >* it = node->namespaceNameSequence->front(); do { if (lastIsConstIdentifier && !it->hasNext()) { id.push(Identifier(editor->parseSession()->symbol(it->element))); } else { id.push(Identifier(editor->parseSession()->symbol(it->element).toLower())); } } while (it->hasNext() && (it = it->next)); return id; } QualifiedIdentifier identifierWithNamespace(const QualifiedIdentifier& base, DUContext* context) { DUChainReadLocker lock; auto scope = context; while (scope && scope->type() != DUContext::Namespace) { scope = scope->parentContext(); } if (scope) { return scope->scopeIdentifier() + base; } else { return base; } } template AbstractType::Ptr determineTypehint(const T* genericType, EditorIntegrator *editor, DUContext* currentContext) { Q_ASSERT(genericType); AbstractType::Ptr type; if (genericType->typehint) { if (genericType->typehint->callableType != -1) { type = AbstractType::Ptr(new IntegralTypeExtended(IntegralTypeExtended::TypeCallable)); } else if (genericType->typehint->arrayType != -1) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); } else if (genericType->typehint->genericType) { NamespacedIdentifierAst* node = genericType->typehint->genericType; const KDevPG::ListNode< IdentifierAst* >* it = node->namespaceNameSequence->front(); QString typehint = editor->parseSession()->symbol(it->element); if (typehint.compare(QLatin1String("bool"), Qt::CaseInsensitive) == 0) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean)); } else if (typehint.compare(QLatin1String("float"), Qt::CaseInsensitive) == 0) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeFloat)); } else if (typehint.compare(QLatin1String("int"), Qt::CaseInsensitive) == 0) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeInt)); } else if (typehint.compare(QLatin1String("string"), Qt::CaseInsensitive) == 0) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeString)); } else if (typehint.compare(QLatin1String("object"), Qt::CaseInsensitive) == 0) { type = AbstractType::Ptr(new IntegralTypeExtended(IntegralTypeExtended::TypeObject)); } else if (typehint.compare(QLatin1String("iterable"), Qt::CaseInsensitive) == 0) { DeclarationPointer traversableDecl = findDeclarationImportHelper(currentContext, QualifiedIdentifier("traversable"), ClassDeclarationType); if (traversableDecl) { UnsureType::Ptr unsure(new UnsureType()); AbstractType::Ptr arrayType = AbstractType::Ptr(new IntegralType(IntegralType::TypeArray)); unsure->addType(arrayType->indexed()); unsure->addType(traversableDecl->abstractType()->indexed()); type = AbstractType::Ptr(unsure); } } else { //don't use openTypeFromName as it uses cursor for findDeclarations DeclarationPointer decl = findDeclarationImportHelper(currentContext, identifierForNamespace(genericType->typehint->genericType, editor), ClassDeclarationType); if (decl) { type = decl->abstractType(); } } } } if (type && genericType->isNullable != -1) { AbstractType::Ptr nullType = AbstractType::Ptr(new IntegralType(IntegralType::TypeNull)); if (type.cast()) { UnsureType::Ptr unsure = type.cast(); unsure->addType(nullType->indexed()); } else { UnsureType::Ptr unsure(new UnsureType()); unsure->addType(type->indexed()); unsure->addType(nullType->indexed()); type = AbstractType::Ptr(unsure); } } return type; } AbstractType::Ptr parameterType(const ParameterAst* node, AbstractType::Ptr phpDocTypehint, EditorIntegrator* editor, DUContext* currentContext) { AbstractType::Ptr type; if (node->parameterType) { type = determineTypehint(node->parameterType, editor, currentContext); } if (node->defaultValue) { ExpressionVisitor v(editor); node->defaultValue->ducontext = currentContext; v.visitNode(node->defaultValue); AbstractType::Ptr defaultValueType = v.result().type(); /* * If a typehint is already set, default values can only be the same type or `null` in PHP * If it's the same type, the type is already correctly set, * so we can ignore this case. * If it's null (which cannot be a typehint), add it as UnsureType */ if (type && defaultValueType.cast() && defaultValueType.cast()->dataType() == IntegralType::TypeNull) { if (type.cast()) { UnsureType::Ptr unsure = type.cast(); AbstractType::Ptr nullType = AbstractType::Ptr(new IntegralType(IntegralType::TypeNull)); unsure->addType(defaultValueType->indexed()); } else { UnsureType::Ptr unsure(new UnsureType()); unsure->addType(type->indexed()); unsure->addType(defaultValueType->indexed()); type = AbstractType::Ptr(unsure); } } else { //Otherwise, let the default value dictate the parameter type type = defaultValueType; } } if (!type) { if (phpDocTypehint) { type = phpDocTypehint; } else { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)); } } if ( node->isRef != -1 ) { ReferenceType::Ptr p( new ReferenceType() ); p->setBaseType( type ); type = p.cast(); } if (node->isVariadic != -1) { auto *container = new IndexedContainer(); const IndexedString *containerType = new IndexedString("array"); container->addEntry(type); container->setPrettyName(*containerType); type = AbstractType::Ptr(container); } Q_ASSERT(type); return type; } AbstractType::Ptr returnType(const ReturnTypeAst* node, AbstractType::Ptr phpDocTypehint, EditorIntegrator* editor, DUContext* currentContext) { AbstractType::Ptr type; if (node) { if (node->voidType != -1) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid)); } else { type = determineTypehint(node, editor, currentContext); } } if (!type) { type = phpDocTypehint; } return type; } } diff --git a/duchain/helper.h b/duchain/helper.h index 28e8c77..792f676 100644 --- a/duchain/helper.h +++ b/duchain/helper.h @@ -1,98 +1,103 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library 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 Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #ifndef PHPDUCHAINHELPER_H #define PHPDUCHAINHELPER_H #include "phpduchainexport.h" #include #include #include #include namespace KDevelop { class Declaration; class DUContext; } namespace Php { struct UnaryExpressionAst; struct AstNode; struct CommonScalarAst; struct NamespacedIdentifierAst; struct ParameterAst; struct GenericTypeHintAst; struct ReturnTypeAst; class EditorIntegrator; enum DeclarationType { ClassDeclarationType, FunctionDeclarationType, ConstantDeclarationType, GlobalVariableDeclarationType, NamespaceDeclarationType }; +enum DeclarationScope { + GlobalScope, + LocalScope +}; + KDEVPHPDUCHAIN_EXPORT bool isMatch(KDevelop::Declaration* declaration, DeclarationType declarationType); KDEVPHPDUCHAIN_EXPORT bool isClassTypehint(GenericTypeHintAst* parameterType, EditorIntegrator *editor); KDEVPHPDUCHAIN_EXPORT KDevelop::DeclarationPointer findDeclarationImportHelper(KDevelop::DUContext* currentContext, const KDevelop::QualifiedIdentifier& id, DeclarationType declarationType); KDEVPHPDUCHAIN_EXPORT KDevelop::DeclarationPointer findDeclarationInPST(KDevelop::DUContext* currentContext, KDevelop::QualifiedIdentifier id, DeclarationType declarationType); KDEVPHPDUCHAIN_EXPORT QByteArray formatComment(AstNode* node, EditorIntegrator* editor); KDEVPHPDUCHAIN_EXPORT CommonScalarAst* findCommonScalar(AstNode* node); KDEVPHPDUCHAIN_EXPORT KDevelop::IndexedString getIncludeFileForNode(UnaryExpressionAst* node, EditorIntegrator* editor); KDEVPHPDUCHAIN_EXPORT QString prettyName(KDevelop::Declaration* dec); KDEVPHPDUCHAIN_EXPORT const KDevelop::IndexedString& internalFunctionFile(); KDEVPHPDUCHAIN_EXPORT const KDevelop::IndexedString& internalTestFile(); /// Indexed string for 'Php', identifies environment files from this language plugin KDEVPHPDUCHAIN_EXPORT const KDevelop::IndexedString& phpLanguageString(); /** * Get proper QualifiedIdentifier for a NamespacedIdentifierAst. * * Identifier will be all lowercase except for the last identifier if @p lastIsConstIdentifier is set to true. */ KDEVPHPDUCHAIN_EXPORT KDevelop::QualifiedIdentifier identifierForNamespace(NamespacedIdentifierAst* node, EditorIntegrator* editor, bool lastIsConstIdentifier = false); /** * Get proper QualifiedIdentifier for a basic identifier. */ KDEVPHPDUCHAIN_EXPORT KDevelop::QualifiedIdentifier identifierWithNamespace(const KDevelop::QualifiedIdentifier& base, KDevelop::DUContext* context); KDEVPHPDUCHAIN_EXPORT KDevelop::AbstractType::Ptr parameterType(const ParameterAst* node, KDevelop::AbstractType::Ptr phpDocTypehint, EditorIntegrator* editor, KDevelop::DUContext *currentContext); KDEVPHPDUCHAIN_EXPORT KDevelop::AbstractType::Ptr returnType(const ReturnTypeAst* node, KDevelop::AbstractType::Ptr phpDocTypehint, EditorIntegrator* editor, KDevelop::DUContext *currentContext); } #endif diff --git a/duchain/tests/duchain_multiplefiles.cpp b/duchain/tests/duchain_multiplefiles.cpp index 2856521..d04d7c7 100644 --- a/duchain/tests/duchain_multiplefiles.cpp +++ b/duchain/tests/duchain_multiplefiles.cpp @@ -1,320 +1,357 @@ /* This file is part of KDevelop Copyright 2010 Niko Sams Copyright 2011 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchain_multiplefiles.h" #include #include #include #include #include #include #include #include #include #include QTEST_MAIN(Php::TestDUChainMultipleFiles) using namespace KDevelop; using namespace Php; void TestDUChainMultipleFiles::initTestCase() { DUChainTestBase::initTestCase(); TestCore* core = dynamic_cast(ICore::self()); Q_ASSERT(core); m_projectController = new TestProjectController(core); core->setProjectController(m_projectController); } void TestDUChainMultipleFiles::testImportsGlobalFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f1(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); } void TestDUChainMultipleFiles::testImportsBaseClassNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); QVERIFY(ICore::self()->languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testNonExistingBaseClass() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f1(QStringLiteral("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testImportsGlobalFunctionNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); } void TestDUChainMultipleFiles::testNonExistingGlobalFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testImportsStaticFunctionNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); } void TestDUChainMultipleFiles::testNonExistingStaticFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testForeachImportedIdentifier() { // see https://bugs.kde.org/show_bug.cgi?id=269369 TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); // build dependency TestFile f1(QStringLiteral("bar(); foreach($i as $a => $b) {} } \n" " public function bar() { $a = new SomeIterator(); return $a; }\n" " }\n"), QStringLiteral("php"), project); for(int i = 0; i < 2; ++i) { if (i > 0) { features = static_cast(features | TopDUContext::ForceUpdate); } f2.parse(features); QVERIFY(f2.waitForParsed()); QTest::qWait(100); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(f2.topContext()->childContexts().size(), 1); DUContext* ACtx = f2.topContext()->childContexts().first(); QVERIFY(ACtx); QCOMPARE(ACtx->childContexts().size(), 4); Declaration* iDec = ACtx->childContexts().at(1)->localDeclarations().first(); QVERIFY(iDec); Declaration* SomeIteratorDec = f1.topContext()->localDeclarations().first(); QVERIFY(SomeIteratorDec); if (i == 0) { QEXPECT_FAIL("", "needs a full two-pass (i.e. get rid of PreDeclarationBuilder)", Continue); } QVERIFY(iDec->abstractType()->equals(SomeIteratorDec->abstractType().constData())); QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0))); } } void TestDUChainMultipleFiles::testUpdateForeach() { TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f(QStringLiteral(" $k) {}\n"), QStringLiteral("php"), project); f.parse(features); QVERIFY(f.waitForParsed()); QVERIFY(f.topContext()); { DUChainWriteLocker lock; QVERIFY(f.topContext()->problems().isEmpty()); QCOMPARE(f.topContext()->findDeclarations(Identifier("k")).count(), 1); Declaration* kDec = f.topContext()->findDeclarations(Identifier(QStringLiteral("k"))).first(); QCOMPARE(kDec->rangeInCurrentRevision().start().line(), 1); QCOMPARE(kDec->rangeInCurrentRevision().start().column(), 0); QCOMPARE(kDec->uses().count(), 1); QCOMPARE(kDec->uses().begin()->count(), 1); QCOMPARE(kDec->uses().begin()->begin()->start.line, 2); } // delete $k = null; line f.setFileContents(QStringLiteral(" $k) {}\n")); f.parse(static_cast(features | TopDUContext::ForceUpdate)); QVERIFY(f.waitForParsed()); QVERIFY(f.topContext()); { DUChainWriteLocker lock; QVERIFY(f.topContext()->problems().isEmpty()); QCOMPARE(f.topContext()->findDeclarations(Identifier("k")).count(), 1); Declaration* kDec = f.topContext()->findDeclarations(Identifier(QStringLiteral("k"))).first(); QCOMPARE(kDec->rangeInCurrentRevision().start().line(), 1); QCOMPARE(kDec->rangeInCurrentRevision().start().column(), 25); QCOMPARE(kDec->uses().count(), 0); } } void TestDUChainMultipleFiles::testTodoExtractorReparse() { TestFile file(QStringLiteral("baz();"), QStringLiteral("php")); QVERIFY(KDevelop::ICore::self()->languageController()->completionSettings()->todoMarkerWords().contains("TODO")); auto features = TopDUContext::AllDeclarationsContextsAndUses; for (int i = 0; i < 2; ++i) { if (i == 1) { file.setFileContents(QStringLiteral("asdf();")); features = static_cast(features | TopDUContext::ForceUpdate); } file.parse(features); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QCOMPARE(top->problems().size(), 1); QCOMPARE(top->problems().at(0)->description(), QString("TODO")); QCOMPARE(top->problems().at(0)->range(), RangeInRevision(2, 3, 2, 7)); } } void TestDUChainMultipleFiles::testIteratorForeachReparse() { TestFile file(QStringLiteral("(features | TopDUContext::ForceUpdate); } file.parse(features); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QVERIFY(top->localDeclarations().size() == 2); QCOMPARE(top->localDeclarations().at(0)->qualifiedIdentifier(), QualifiedIdentifier("a")); IntegralType::Ptr type = top->localDeclarations().at(0)->type(); QVERIFY(type); //Should actually parse as an TypeInt, but this does not work. QVERIFY(type->dataType() == IntegralType::TypeMixed); } } + +void TestDUChainMultipleFiles::testNamespacedIdentifierInPST() { + auto features = TopDUContext::AllDeclarationsAndContexts; + + TestProject* project = new TestProject; + m_projectController->closeAllProjects(); + m_projectController->addProject(project); + + TestFile f1(QStringLiteral("class_a = new \\Test\\A(); }}"), QStringLiteral("php"), project); + f2.parse(features); + QVERIFY(f2.waitForParsed()); + + TestFile f3(QStringLiteral("class_a = new Test\\A(); }}"), QStringLiteral("php"), project); + f3.parse(features); + QVERIFY(f3.waitForParsed()); + + DUChainWriteLocker lock(DUChain::lock()); + QVERIFY(f1.topContext()); + QVERIFY(f2.topContext()); + QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0))); + QVERIFY(f3.topContext()); + QVERIFY(!f3.topContext()->imports(f1.topContext(), CursorInRevision(0, 0))); +} diff --git a/duchain/tests/duchain_multiplefiles.h b/duchain/tests/duchain_multiplefiles.h index 85534e2..c39d698 100644 --- a/duchain/tests/duchain_multiplefiles.h +++ b/duchain/tests/duchain_multiplefiles.h @@ -1,55 +1,56 @@ /* This file is part of KDevelop Copyright 2010 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TESTDUCHAINMULTIPLEFILES_H #define TESTDUCHAINMULTIPLEFILES_H #include "duchaintestbase.h" namespace KDevelop { class TestCore; class TestProjectController; } namespace Php { class TestDUChainMultipleFiles : public DUChainTestBase { Q_OBJECT private slots: void initTestCase(); void testImportsGlobalFunction(); void testImportsBaseClassNotYetParsed(); void testNonExistingBaseClass(); void testImportsGlobalFunctionNotYetParsed(); void testNonExistingGlobalFunction(); void testImportsStaticFunctionNotYetParsed(); void testNonExistingStaticFunction(); void testForeachImportedIdentifier(); void testUpdateForeach(); void testTodoExtractorReparse(); void testIteratorForeachReparse(); + void testNamespacedIdentifierInPST(); private: KDevelop::TestProjectController* m_projectController; }; } #endif