diff --git a/duchain/builders/declarationbuilder.cpp b/duchain/builders/declarationbuilder.cpp index d5a7b6f..6ac024e 100644 --- a/duchain/builders/declarationbuilder.cpp +++ b/duchain/builders/declarationbuilder.cpp @@ -1,1728 +1,1754 @@ /*************************************************************************** * 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, const ReferencedTopDUContext& updateContext_) { ReferencedTopDUContext updateContext(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->constsSequence) { + 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("Cannot use 'final' as constant modifier"), node->modifiers); + } + if (m_currentModifers & ModifierStatic) { + reportError(i18n("Cannot use 'static' as constant modifier"), node->modifiers); + } + if (m_currentModifers & ModifierAbstract) { + reportError(i18n("Cannot use 'abstract' as constant modifier"), node->modifiers); + } + } + } else { + m_currentModifers = 0; + } + DeclarationBuilderBase::visitClassStatement(node); + m_currentModifers = 0; } 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()); // 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); + if (m_currentModifers & ModifierProtected) { + dec->setAccessPolicy(Declaration::Protected); + } else if (m_currentModifers & ModifierPrivate) { + dec->setAccessPolicy(Declaration::Private); + } else { + 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); bool alreadyDeclared = false; // check for already declared class members if (!m_findVariable.identifier.isEmpty() && !m_findVariable.parentIdentifier.isEmpty()) { DUContext* ctx = getClassContext(m_findVariable.parentIdentifier, currentContext()); if (ctx) { DUChainReadLocker lock(DUChain::lock()); ifDebug(qCDebug(DUCHAIN) << "checking if already declared: " << m_findVariable.identifier.toString();) QList decs; foreach ( Declaration* dec, currentContext()->findDeclarations(m_findVariable.identifier) ) { if ( dec->kind() == Declaration::Kind::Instance) { if (dec->range() == editorFindRange(m_findVariable.node)) { // Don't reuse previous declarations as the type might have changed. continue; } ClassMemberDeclaration *classDec = dynamic_cast(dec); if (classDec && classDec->accessPolicy() == Declaration::Private) { if (currentContext()->parentContext() == dec->context()) { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found class property:" << dec->toString();) } } else { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found class property:" << dec->toString();) } } } lock.unlock(); if (!decs.isEmpty()) { Declaration * dec = decs.last(); if (dec) { encounter(dec); alreadyDeclared = true; IntegralType::Ptr type = dec->type(); if (type && type->dataType() == IntegralType::TypeNull) { DUChainWriteLocker wlock; dec->setAbstractType(currentAbstractType()); } } } lock.lock(); if (decs.isEmpty() && (!currentContext()->parentContext() || !currentContext()->parentContext()->imports(ctx))) { ifDebug(qCDebug(DUCHAIN) << "nothing found in current context, look in class context of parent";) QList decs; foreach ( Declaration* dec, ctx->findDeclarations(m_findVariable.identifier) ) { if ( dec->kind() == Declaration::Kind::Instance) { if (dec->range() == editorFindRange(m_findVariable.node)) { // Don't reuse previous declarations as the type might have changed. continue; } ifDebug(qCDebug(DUCHAIN) << "found class property:" << dec->toString();) decs << dec; } } lock.unlock(); if (!decs.isEmpty()) { Declaration * dec = decs.last(); if (dec) { alreadyDeclared = true; IntegralType::Ptr type = dec->type(); // check for redeclaration of private or protected stuff DUContext *parentCtx = currentContext()->parentContext(); ClassMemberDeclaration *classDec = dynamic_cast(dec); if (classDec && classDec->accessPolicy() == Declaration::Private && classDec->context() != parentCtx) { reportError(i18n("Cannot access private property %1", classDec->toString()), m_findVariable.node); } else if (classDec && classDec->accessPolicy() == Declaration::Protected && classDec->context() != parentCtx) { reportError(i18n("Cannot access protected property %1", classDec->toString()), m_findVariable.node); } else if (type && type->dataType() == IntegralType::TypeNull) { DUChainWriteLocker wlock; dec->setAbstractType(currentAbstractType()); } } } } } } if ( !alreadyDeclared && !m_findVariable.identifier.isEmpty() && currentAbstractType()) { ifDebug(qCDebug(DUCHAIN) << "not yet declared: " << m_findVariable.identifier.toString();) //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); 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/tests/duchain.cpp b/duchain/tests/duchain.cpp index 1eb5c0e..8621721 100644 --- a/duchain/tests/duchain.cpp +++ b/duchain/tests/duchain.cpp @@ -1,4116 +1,4187 @@ /* This file is part of KDevelop Copyright 2008 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. */ #include "duchain.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "helper.h" #include "../declarations/classdeclaration.h" #include "../declarations/classmethoddeclaration.h" #include "../declarations/functiondeclaration.h" #include "../declarations/variabledeclaration.h" #include "../types/structuretype.h" #include "../types/integraltypeextended.h" #include "../types/indexedcontainer.h" #include using namespace KDevelop; using namespace Php; QTEST_MAIN(Php::TestDUChain) TestDUChain::TestDUChain() { } void TestDUChain::declareFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->context(), top); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); QCOMPARE(top->childContexts().at(0)->type(), DUContext::Function); QCOMPARE(top->childContexts().at(1)->type(), DUContext::Other); } void TestDUChain::declareBaseTypeFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->context(), top); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); QCOMPARE(top->childContexts().at(0)->type(), DUContext::Function); QCOMPARE(top->childContexts().at(1)->type(), DUContext::Other); } void TestDUChain::declareSemiReservedFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 5); //class A Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); //$i Declaration* decVar = top->localDeclarations().at(2); QCOMPARE(decVar->identifier(), Identifier("i")); qDebug() << decVar->abstractType()->toString(); UnsureType::Ptr unsureType = decVar->type(); QVERIFY(unsureType); QCOMPARE(unsureType->typesSize(), 3u); // = new A(); QCOMPARE(unsureType->types()[0].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(unsureType->types()[0].abstractType()->equals(dec->abstractType().data())); // = new B(); //class B dec = top->localDeclarations().at(1); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 2); QCOMPARE(unsureType->types()[1].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(unsureType->types()[1].abstractType()->equals(dec->abstractType().data())); // = 'foo'; QVERIFY(unsureType->types()[2].abstractType().cast()); QVERIFY(unsureType->types()[2].abstractType().cast()->dataType() == IntegralType::TypeString); //$j decVar = top->localDeclarations().at(3); QCOMPARE(decVar->identifier(), Identifier("j")); StructureType::Ptr classType = decVar->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(classType->equals(dec->abstractType().data())); // $a decVar = top->localDeclarations().at(4); QCOMPARE(decVar->identifier(), Identifier("a")); QVERIFY(decVar->type()); } void TestDUChain::varTypehint() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); //$i Declaration* decVar = top->localDeclarations().at(1); QCOMPARE(decVar->identifier(), Identifier("i")); StructureType::Ptr classType = decVar->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(classType->equals(dec->abstractType().data())); } void TestDUChain::declareClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->kind(), Declaration::Type); QCOMPARE(dec->toString(), QString("class A")); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassA); qDebug() << contextClassA->localScopeIdentifier().toString(); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 8); QCOMPARE(contextClassA->childContexts().first()->localScopeIdentifier(), QualifiedIdentifier("foo")); DUContext* contextMethodBodyFoo = contextClassA->childContexts().at(1); QCOMPARE(contextMethodBodyFoo->localScopeIdentifier(), QualifiedIdentifier("foo")); QCOMPARE(contextMethodBodyFoo->importedParentContexts().count(), 1); QCOMPARE(contextMethodBodyFoo->childContexts().count(), 0); QVERIFY(contextMethodBodyFoo->importedParentContexts().first().context(top) == contextClassA->childContexts().first()); //foo() dec = contextClassA->localDeclarations().at(0); ClassFunctionDeclaration* funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->kind(), Declaration::Type); QCOMPARE(funDec->identifier(), Identifier("foo")); QCOMPARE(funDec->accessPolicy(), Declaration::Public); QCOMPARE(funDec->isStatic(), false); { // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); } //bar() dec = contextClassA->localDeclarations().at(1); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("bar")); QCOMPARE(funDec->accessPolicy(), Declaration::Protected); QCOMPARE(funDec->isStatic(), true); //baz() dec = contextClassA->localDeclarations().at(2); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("baz")); QCOMPARE(funDec->accessPolicy(), Declaration::Private); QCOMPARE(funDec->isStatic(), false); //boo() dec = contextClassA->localDeclarations().at(3); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("boo")); QCOMPARE(funDec->accessPolicy(), Declaration::Public); } void TestDUChain::declareBaseTypeClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("problems().count(), 1); } void TestDUChain::declareClassWithSemiReservedMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->kind(), Declaration::Type); QCOMPARE(dec->toString(), QString("class A")); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassA); qDebug() << contextClassA->localScopeIdentifier().toString(); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 4); QCOMPARE(contextClassA->childContexts().first()->localScopeIdentifier(), QualifiedIdentifier("switch")); DUContext* contextMethodBodyFoo = contextClassA->childContexts().at(1); QCOMPARE(contextMethodBodyFoo->localScopeIdentifier(), QualifiedIdentifier("switch")); QCOMPARE(contextMethodBodyFoo->importedParentContexts().count(), 1); QCOMPARE(contextMethodBodyFoo->childContexts().count(), 0); QVERIFY(contextMethodBodyFoo->importedParentContexts().first().context(top) == contextClassA->childContexts().first()); //switch() dec = contextClassA->localDeclarations().at(0); ClassFunctionDeclaration* funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->kind(), Declaration::Type); QCOMPARE(funDec->identifier(), Identifier("switch")); QCOMPARE(funDec->accessPolicy(), Declaration::Public); QCOMPARE(funDec->isStatic(), false); { // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); } //public() dec = contextClassA->localDeclarations().at(1); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("public")); QCOMPARE(funDec->accessPolicy(), Declaration::Protected); QCOMPARE(funDec->isStatic(), true); } void TestDUChain::declareClassWithBaseTypeMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->kind(), Declaration::Type); QCOMPARE(dec->toString(), QString("class A")); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassA); qDebug() << contextClassA->localScopeIdentifier().toString(); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 4); QCOMPARE(contextClassA->childContexts().first()->localScopeIdentifier(), QualifiedIdentifier("string")); DUContext* contextMethodBodyFoo = contextClassA->childContexts().at(1); QCOMPARE(contextMethodBodyFoo->localScopeIdentifier(), QualifiedIdentifier("string")); QCOMPARE(contextMethodBodyFoo->importedParentContexts().count(), 1); QCOMPARE(contextMethodBodyFoo->childContexts().count(), 0); QVERIFY(contextMethodBodyFoo->importedParentContexts().first().context(top) == contextClassA->childContexts().first()); //string() dec = contextClassA->localDeclarations().at(0); ClassFunctionDeclaration* funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->kind(), Declaration::Type); QCOMPARE(funDec->identifier(), Identifier("string")); QCOMPARE(funDec->accessPolicy(), Declaration::Public); QCOMPARE(funDec->isStatic(), false); { // no return means void as return type FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(dec->abstractType()); QVERIFY(ftype); IntegralType::Ptr itype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(itype->dataType() == IntegralType::TypeVoid); } //iterable() dec = contextClassA->localDeclarations().at(1); funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->identifier(), Identifier("iterable")); QCOMPARE(funDec->accessPolicy(), Declaration::Protected); QCOMPARE(funDec->isStatic(), true); } void TestDUChain::classMemberVar() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassA); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 0); QCOMPARE(contextClassA->localDeclarations().count(), 4); //$foo ClassMemberDeclaration* var = dynamic_cast(contextClassA->localDeclarations().first()); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("foo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeNull); //$bar var = dynamic_cast(contextClassA->localDeclarations().at(1)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("bar")); QCOMPARE(var->accessPolicy(), Declaration::Protected); QCOMPARE(var->isStatic(), false); StructureType::Ptr type = var->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("a")); //$baz var = dynamic_cast(contextClassA->localDeclarations().at(2)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("baz")); QCOMPARE(var->accessPolicy(), Declaration::Private); QCOMPARE(var->isStatic(), true); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeString); //$boo var = dynamic_cast(contextClassA->localDeclarations().at(3)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("boo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeInt); } void TestDUChain::classMemberVarAfterUse() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("a = 1; } public $a = 1; }"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 1); QVERIFY(top->problems().isEmpty()); DUContext* contextClassB = top->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 1); Declaration* dec = top->localDeclarations().first(); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("b")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(dec->logicalInternalContext(top), contextClassB); QCOMPARE(contextClassB->localScopeIdentifier(), QualifiedIdentifier("b")); QCOMPARE(contextClassB->childContexts().count(), 2); QCOMPARE(contextClassB->localDeclarations().count(), 2); //$foo ClassMemberDeclaration* var = dynamic_cast(contextClassB->localDeclarations().at(1)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("a")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeInt); QVERIFY(var->range() == RangeInRevision(0, 54, 0, 56)); } void TestDUChain::classMemberVarDocBlockType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); DUContext* contextClassA = top->childContexts().at(0)->childContexts().first(); DUContext* contextClassB = top->childContexts().at(1)->childContexts().first(); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->childContexts().first()->localDeclarations().first(); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("test::a")); QCOMPARE(dec->isDefinition(), true); QCOMPARE(contextClassA->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(contextClassA->childContexts().count(), 0); QCOMPARE(contextClassA->localDeclarations().count(), 0); QCOMPARE(contextClassB->localScopeIdentifier(), QualifiedIdentifier("b")); QCOMPARE(contextClassB->childContexts().count(), 0); QCOMPARE(contextClassB->localDeclarations().count(), 1); //$foo ClassMemberDeclaration* var = dynamic_cast(contextClassB->localDeclarations().first()); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("foo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); StructureType::Ptr type = var->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("test::a")); } void TestDUChain::returnTypeGenerator_data() { QTest::addColumn("code"); //Note: in practice, Generator is defined by php, but this class is not loaded in this test, so define it ourselves QTest::newRow("simple yield expression") << QStringLiteral(" 1; }\n"); QTest::newRow("yield equality expression") << QStringLiteral("> 1; }\n"); QTest::newRow("yield bit expression") << QStringLiteral(" 'value'; }\n"); } void TestDUChain::returnTypeGenerator() { QFETCH(QString, code); TopDUContext* top = parse(code.toUtf8(), DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); StructureType::Ptr retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("generator")); } void TestDUChain::returnTypeGeneratorDelegation() { //Note: in practice, Generator is defined by php, but this class is not loaded in this test, so define it ourselves // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 5); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); StructureType::Ptr retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("generator")); dec = top->localDeclarations().at(2); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("bar")); functionType = dec->type(); QVERIFY(functionType); retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("generator")); } void TestDUChain::returnTypeClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 5); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); StructureType::Ptr retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("a")); dec = top->localDeclarations().at(2); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("bar")); functionType = dec->type(); QVERIFY(functionType); retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declarationReturnType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(1); FunctionType::Ptr fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); dec = top->localDeclarations().at(2); QCOMPARE(dec->identifier(), Identifier("i")); StructureType::Ptr type = dec->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declarationReturnTypeInRecursingFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->childContexts().last()->findDeclarations(Identifier(QStringLiteral("i"))); QCOMPARE(decs.size(), 1); Declaration* dec = decs.first(); StructureType::Ptr type = dec->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declarationMultipleReturnTypes() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(1)->type(); QVERIFY(fType); qDebug() << fType->toString(); TypePtr ut = UnsureType::Ptr::dynamicCast(fType->returnType()); QVERIFY(ut); QCOMPARE(2u, ut->typesSize()); ///TODO: why are the types not in the correct order, i.e. null, A QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->declaration(top)); QCOMPARE(ut->types()[0].type()->declaration(top)->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeNull); fType = top->localDeclarations().at(2)->type(); QVERIFY(fType); qDebug() << fType->toString(); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeInt); } void TestDUChain::returnTypeViaMember() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("fa($param); }\n" " function fb2($param) { $i = $this->anormal->fa($param); } }"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVector decs = top->localDeclarations(); QCOMPARE(decs.size(), 2); ClassDeclaration* aDec = dynamic_cast(decs.first()); QVERIFY(aDec); ClassDeclaration* bDec = dynamic_cast(decs.last()); QVERIFY(bDec); QCOMPARE(bDec->logicalInternalContext(top)->localDeclarations().size(), 4); typedef QPair idPair; foreach ( const idPair & pair, QList< idPair >() << qMakePair(QString("fb1"), QString("astatic")) << qMakePair(QString("fb2"), QString("anormal")) ) { qDebug() << pair.first << pair.second; ClassMethodDeclaration* fDec = dynamic_cast( bDec->logicalInternalContext(top)->findDeclarations(Identifier(pair.first)).first() ); QVERIFY(fDec); ClassMemberDeclaration* mDec = dynamic_cast( bDec->logicalInternalContext(top)->findDeclarations(Identifier(pair.second)).first() ); QVERIFY(mDec); QVERIFY(mDec->type()); QCOMPARE(mDec->type()->declaration(top), aDec); QCOMPARE(fDec->logicalInternalContext(top)->localDeclarations().size(), 1); Declaration* iDec = fDec->logicalInternalContext(top)->localDeclarations().first(); QCOMPARE(iDec->identifier().toString(), QString("i")); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->declaration(top), aDec); } } void TestDUChain::declarationReturnTypeDocBlock() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(0)->localDeclarations().at(0); FunctionType::Ptr fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); //function foo dec = top->localDeclarations().at(2); fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); //function bar dec = top->localDeclarations().at(3); fType = dec->type(); QVERIFY(fType); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralTypeExtended::TypeObject); //test hint in internal functions file of a type that is added later on // function QList decs = top->findDeclarations(Identifier(QStringLiteral("should_return_exception"))); QCOMPARE(decs.size(), 1); dec = decs.first(); fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("exception")); // method decs = top->findDeclarations(Identifier(QStringLiteral("internal_test_class"))); QCOMPARE(decs.size(), 1); ClassDeclaration* cdec = dynamic_cast(decs.first()); QVERIFY(cdec); decs = cdec->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("should_return_exception"))); QCOMPARE(decs.size(), 1); dec = decs.first(); fType = dec->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("exception")); } void TestDUChain::declarationReturnTypeDocBlockIntegral() { QByteArray method("localDeclarations().at(0)->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeString); //function bar fType = top->localDeclarations().at(1)->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeMixed); //function aaa fType = top->childContexts().at(4)->localDeclarations().first()->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())->dataType() == IntegralType::TypeInt); } void TestDUChain::declarationReturnTypeClassChain() { QByteArray method("childContexts().first(); QCOMPARE(ctx->type(), DUContext::Class); QVERIFY(ctx->owner()); QVERIFY(StructureType::Ptr::dynamicCast(ctx->owner()->abstractType())); //function a // FIXME QEXPECT_FAIL("", "This test fails after porting the plugin to KF5.", Abort); QVERIFY(/* func a (this) */ ctx->localDeclarations().at(0)->type().data() == ctx->owner()->abstractType().data()); QVERIFY(/* func b (self) */ ctx->localDeclarations().at(1)->type().data() == ctx->owner()->abstractType().data()); } void TestDUChain::declarationReturnTypeTypehint() { //Typehint preferred over phpdoc preferred over inferred type QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); FunctionType::Ptr fun = top->localDeclarations().first()->type(); QVERIFY(fun); IntegralType::Ptr returnType = IntegralType::Ptr::dynamicCast(fun->returnType()); QVERIFY(returnType); QVERIFY(returnType->dataType() == IntegralType::TypeBoolean); } void TestDUChain::declarationReturnTypeTypehintVoid() { //Typehint preferred over phpdoc preferred over inferred type QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); FunctionType::Ptr fun = top->localDeclarations().first()->type(); QVERIFY(fun); IntegralType::Ptr returnType = IntegralType::Ptr::dynamicCast(fun->returnType()); QVERIFY(returnType); QVERIFY(returnType->dataType() == IntegralType::TypeVoid); } void TestDUChain::declarationReturnTypeTypehintObject() { //Typehint preferred over phpdoc preferred over inferred type QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 1); FunctionType::Ptr fun = top->localDeclarations().first()->type(); QVERIFY(fun); IntegralTypeExtended::Ptr returnType = IntegralTypeExtended::Ptr::dynamicCast(fun->returnType()); QVERIFY(returnType); QVERIFY(returnType->dataType() == IntegralTypeExtended::TypeObject); } void TestDUChain::declareTypehintFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->internalContext(), top->childContexts().at(0)); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(top->childContexts().at(0)->childContexts().count(), 0); DUContext* contextFunctionFoo = top->childContexts().at(1); QCOMPARE(contextFunctionFoo->localScopeIdentifier(), QualifiedIdentifier("foo")); DUContext* contextFunctionBodyFoo = top->childContexts().at(2); QCOMPARE(contextFunctionBodyFoo->localScopeIdentifier(), QualifiedIdentifier("foo")); QCOMPARE(contextFunctionBodyFoo->importedParentContexts().count(), 1); QCOMPARE(contextFunctionBodyFoo->childContexts().count(), 0); QVERIFY(contextFunctionBodyFoo->importedParentContexts().first().context(top) == contextFunctionFoo); QVERIFY(top->childContexts().at(1)->localDeclarations().first()->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->type()->qualifiedIdentifier(), QualifiedIdentifier("a")); FunctionType::Ptr fType = top->localDeclarations().at(1)->type(); QVERIFY(fType); QVERIFY(StructureType::Ptr::dynamicCast(fType->returnType())); QCOMPARE(StructureType::Ptr::dynamicCast(fType->returnType())->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::declareVariadicFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); AbstractType::Ptr arg = fun->arguments().first(); QVERIFY(arg); QVERIFY(arg.cast()); QCOMPARE(arg.cast()->typesCount(), 1); QCOMPARE(arg.cast()->prettyName().str(), QStringLiteral("array")); AbstractType::Ptr typehint = arg.cast()->typeAt(0).abstractType(); QVERIFY(typehint); QVERIFY(IntegralType::Ptr::dynamicCast(typehint)); QVERIFY(IntegralType::Ptr::dynamicCast(typehint)->dataType() == IntegralType::TypeMixed); } void TestDUChain::declareTypehintVariadicFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(1)->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); AbstractType::Ptr arg = fun->arguments().first(); QVERIFY(arg); QVERIFY(arg.cast()); QCOMPARE(arg.cast()->typesCount(), 1); QCOMPARE(arg.cast()->prettyName().str(), QStringLiteral("array")); AbstractType::Ptr typehint = arg.cast()->typeAt(0).abstractType(); QVERIFY(typehint); QCOMPARE(typehint->toString(), QStringLiteral("A")); } void TestDUChain::declareTypehintObjectFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralTypeExtended::TypeObject); IntegralTypeExtended::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralTypeExtended::TypeObject); } void TestDUChain::declareTypehintArrayFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeArray); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeArray); } void TestDUChain::declareTypehintCallableFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralTypeExtended::TypeCallable); IntegralTypeExtended::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralTypeExtended::TypeCallable); } void Php::TestDUChain::functionWithCallableAndFunctionReturn() { QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralTypeExtended::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralTypeExtended::TypeCallable); IntegralTypeExtended::Ptr retType = IntegralTypeExtended::Ptr::dynamicCast(fun->returnType()); QVERIFY(retType); QVERIFY(retType->dataType() == IntegralTypeExtended::TypeCallable); } void TestDUChain::declareTypehintIterableFunction() { //Note: in practice, Traversable is defined by php, but this interface is not loaded in this test, so define it ourselves // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().count(), 2); FunctionType::Ptr fun = top->localDeclarations().at(1)->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QCOMPARE(argType->typesSize(), 2u); QVERIFY(argType->types()[0].abstractType().cast()); QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(argType->types()[1].abstractType().cast()); QCOMPARE(argType->types()[1].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("traversable")); } void TestDUChain::declareTypehintBoolFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeBoolean); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeBoolean); } void TestDUChain::declareTypehintFloatFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeFloat); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeFloat); } void TestDUChain::declareTypehintIntFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeInt); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeInt); } void TestDUChain::declareTypehintStringFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeString); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeString); } void TestDUChain::declareNullableTypehintArrayFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QCOMPARE(argType->typesSize(), 2u); QVERIFY(argType->types()[0].abstractType().cast()); QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(argType->types()[1].abstractType().cast()); QVERIFY(argType->types()[1].abstractType().cast()->dataType() == IntegralType::TypeNull); UnsureType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QCOMPARE(type->typesSize(), 2u); QVERIFY(type->types()[0].abstractType().cast()); QVERIFY(type->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(type->types()[1].abstractType().cast()); QVERIFY(type->types()[1].abstractType().cast()->dataType() == IntegralType::TypeNull); } void TestDUChain::declareTypehintWithPhpdocFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeInt); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeInt); } void TestDUChain::declareNullableTypehintMixedFunction() { // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())); QVERIFY(IntegralType::Ptr::dynamicCast(fun->arguments().first())->dataType() == IntegralType::TypeMixed); IntegralType::Ptr type = top->childContexts().first()->localDeclarations().first()->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeMixed); } void TestDUChain::declareTypehintNullableIterableFunction() { //Note: in practice, Traversable is defined by php, but this interface is not loaded in this test, so define it ourselves // 0 1 2 3 // 0123456789012345678901234567890123 QByteArray method("localDeclarations().count(), 2); FunctionType::Ptr fun = top->localDeclarations().at(1)->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QCOMPARE(argType->typesSize(), 3u); QVERIFY(argType->types()[0].abstractType().cast()); QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(argType->types()[1].abstractType().cast()); QCOMPARE(argType->types()[1].abstractType().cast()->qualifiedIdentifier(), QualifiedIdentifier("traversable")); QVERIFY(argType->types()[2].abstractType().cast()); QVERIFY(argType->types()[2].abstractType().cast()->dataType() == IntegralType::TypeNull); } void TestDUChain::classImplementsInterface() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 2); //interface I Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("i")); QCOMPARE(dec->toString(), QString("interface I")); StructureType::Ptr typeI = dec->type(); QCOMPARE(typeI->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(typeI->declaration(top) == dec); ClassDeclaration* classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Interface); QCOMPARE(dec->internalContext(), top->childContexts().at(0)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->importedParentContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("i")); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); IndexedType indexedTypeI = classDec->indexedType(); //class A dec = top->localDeclarations().at(1); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr typeA = dec->type(); QCOMPARE(typeA->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(typeA->declaration(top) == dec); classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Class); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("a")); //class A imports interface I context QCOMPARE(dec->internalContext()->importedParentContexts().count(), 1); QVERIFY(dec->internalContext()->importedParentContexts().at(0).context(top) == top->childContexts().at(0)); QCOMPARE(classDec->baseClassesSize(), 1u); QCOMPARE(classDec->baseClasses()[0].baseClass, indexedTypeI); QCOMPARE(dec->uses().count(), 0); } void TestDUChain::classExtends() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 2); //class A Declaration* dec = top->localDeclarations().at(0); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr typeA = dec->type(); QCOMPARE(typeA->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(typeA->declaration(top) == dec); ClassDeclaration* classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Class); QCOMPARE(dec->internalContext(), top->childContexts().at(0)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->importedParentContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("a")); QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), 1); IndexedType indexedTypeA = classDec->indexedType(); //class B dec = top->localDeclarations().at(1); QVERIFY(dec->isDefinition()); QCOMPARE(dec->identifier(), Identifier("b")); StructureType::Ptr typeB = dec->type(); QCOMPARE(typeB->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(typeB->declaration(top) == dec); classDec = dynamic_cast(dec); QVERIFY(classDec); QCOMPARE(classDec->classType(), ClassDeclarationData::Class); QCOMPARE(dec->internalContext(), top->childContexts().at(1)); QCOMPARE(dec->internalContext()->childContexts().count(), 0); QCOMPARE(dec->internalContext()->localScopeIdentifier(), QualifiedIdentifier("b")); //class B imports class A context QCOMPARE(dec->internalContext()->importedParentContexts().count(), 1); QVERIFY(dec->internalContext()->importedParentContexts().at(0).context(top) == top->childContexts().at(0)); QCOMPARE(classDec->baseClassesSize(), 1u); QCOMPARE(classDec->baseClasses()[0].baseClass, indexedTypeA); QCOMPARE(dec->uses().count(), 0); } void TestDUChain::staticMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(2)->type(); QVERIFY(type); QCOMPARE(type->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::ownStaticMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(1)); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)->type()); AbstractType::Ptr ret = top->childContexts().at(1)->localDeclarations().at(0) ->type()->returnType(); QVERIFY(StructureType::Ptr::dynamicCast(ret)); QCOMPARE(StructureType::Ptr::dynamicCast(ret)->declaration(top), top->localDeclarations().at(0)); QVERIFY(top->childContexts().at(1)->childContexts().at(1 + 2)); QVERIFY(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(0)); QVERIFY(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(0)->type()); QCOMPARE(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(0) ->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QCOMPARE(top->childContexts().at(1)->childContexts().at(1 + 2)->localDeclarations().at(1) ->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::thisVar() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x(); } } "); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); FunctionType::Ptr fn = top->childContexts().at(0)->localDeclarations().at(0)->type(); QVERIFY(fn); StructureType::Ptr cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("a")); fn = top->childContexts().at(0)->localDeclarations().at(1)->type(); QVERIFY(fn); cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("a")); } void TestDUChain::objectFunctionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x(); } } "); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); FunctionType::Ptr fn = top->childContexts().at(1)->localDeclarations().at(0)->type(); QVERIFY(fn); StructureType::Ptr cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("b")); fn = top->childContexts().at(1)->localDeclarations().at(1)->type(); QVERIFY(fn); cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::objectFunctionCall2() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x()->c(); } } "); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); FunctionType::Ptr fn = top->childContexts().at(2)->localDeclarations().at(1)->type(); QVERIFY(fn); StructureType::Ptr cls = StructureType::Ptr::dynamicCast(fn->returnType()); QVERIFY(cls); QCOMPARE(cls->qualifiedIdentifier(), QualifiedIdentifier("c")); } void TestDUChain::objectFunctionCall3() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("b();"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("i")); QCOMPARE(top->localDeclarations().at(2)->type()->qualifiedIdentifier(), QualifiedIdentifier("a"));; QCOMPARE(top->localDeclarations().at(3)->qualifiedIdentifier(), QualifiedIdentifier("j")); QCOMPARE(top->localDeclarations().at(3)->type()->qualifiedIdentifier(), QualifiedIdentifier("b"));; } void TestDUChain::objectVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo;"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().at(3)->qualifiedIdentifier(), QualifiedIdentifier("i")); QCOMPARE(top->localDeclarations().at(3)->type()->qualifiedIdentifier(), QualifiedIdentifier("b"));; } void TestDUChain::staticMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("i")); QCOMPARE(top->localDeclarations().at(2)->type()->qualifiedIdentifier(), QualifiedIdentifier("b"));; } void TestDUChain::ownStaticMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(1)->childContexts().at(1); QCOMPARE(barContext->localDeclarations().at(0)->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QCOMPARE(barContext->localDeclarations().at(1)->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); } void TestDUChain::classConst_data() { QTest::addColumn("classBody"); QTest::addColumn("problems"); QTest::newRow("int") << "const C = 1;" << 0; QTest::newRow("string") << "const C = 'asdf';" << 0; QTest::newRow("float") << "const C = 0.5;" << 0; QTest::newRow("bool") << "const C = true;" << 0; QTest::newRow("selfConst") << "const C2 = 1; const C = self::C2;" << 0; QTest::newRow("parentConst") << "const C = parent::P;" << 0; QTest::newRow("null") << "const C = null;" << 0; QTest::newRow("array") << "const C = array();" << 0; QTest::newRow("expression") << "const C = 'foo' . 'foo';" << 0; } void TestDUChain::classConst() { QFETCH(QString, classBody); QFETCH(int, problems); QString fullClass("childContexts().count(), 2); QCOMPARE(top->problems().count(), problems); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::C")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::C")).first()->context(), top->childContexts().last()); } void TestDUChain::classConstWithTypeHint() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 1); QCOMPARE(top->problems().count(), 0); QList< Declaration* > decs = top->findDeclarations(QualifiedIdentifier("a::C")); QCOMPARE(decs.count(), 1); QCOMPARE(decs.first()->context(), top->childContexts().last()); IntegralType::Ptr type = decs.first()->abstractType().cast(); QVERIFY(type); QCOMPARE(type->dataType(), IntegralType::TypeInt); QVERIFY(type->modifiers() & AbstractType::ConstModifier); } +void TestDUChain::classConstVisibility() +{ + // 0 1 2 3 4 5 6 7 + // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + QByteArray method("childContexts().count(), 1); + QCOMPARE(top->problems().count(), 0); + + QList< Declaration* > decs = top->findDeclarations(QualifiedIdentifier("a::B")); + QCOMPARE(decs.count(), 1); + QCOMPARE(decs.first()->context(), top->childContexts().last()); + + IntegralType::Ptr type = decs.first()->abstractType().cast(); + QVERIFY(type); + QCOMPARE(type->dataType(), IntegralType::TypeInt); + QVERIFY(type->modifiers() & AbstractType::ConstModifier); + + ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); + QVERIFY(cmdec->accessPolicy() == Declaration::Public); + + decs = top->findDeclarations(QualifiedIdentifier("a::C")); + QCOMPARE(decs.count(), 1); + QCOMPARE(decs.first()->context(), top->childContexts().last()); + + type = decs.first()->abstractType().cast(); + QVERIFY(type); + QCOMPARE(type->dataType(), IntegralType::TypeInt); + QVERIFY(type->modifiers() & AbstractType::ConstModifier); + + cmdec = dynamic_cast(decs.first()); + QVERIFY(cmdec->accessPolicy() == Declaration::Protected); + + decs = top->findDeclarations(QualifiedIdentifier("a::D")); + QCOMPARE(decs.count(), 1); + QCOMPARE(decs.first()->context(), top->childContexts().last()); + + type = decs.first()->abstractType().cast(); + QVERIFY(type); + QCOMPARE(type->dataType(), IntegralType::TypeInt); + QVERIFY(type->modifiers() & AbstractType::ConstModifier); + + cmdec = dynamic_cast(decs.first()); + QVERIFY(cmdec->accessPolicy() == Declaration::Private); +} + void TestDUChain::semiReservedClassConst() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().count(), 1); QCOMPARE(top->problems().count(), 0); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::SWITCH")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::SWITCH")).first()->context(), top->childContexts().last()); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::PUBLIC")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::PUBLIC")).first()->context(), top->childContexts().last()); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::STRING")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a::STRING")).first()->context(), top->childContexts().last()); } +void TestDUChain::illegalClassConst_data() +{ + QTest::addColumn("code"); + + QTest::newRow("final const") << QStringLiteral("problems().count(), 1); +} + void TestDUChain::fileConst_data() { QTest::addColumn("code"); QTest::addColumn("problems"); QTest::addColumn("dataType"); QTest::newRow("int") << "const C = 1;" << 0 << (uint) IntegralType::TypeInt; QTest::newRow("string") << "const C = 'asdf';" << 0 << (uint) IntegralType::TypeString; QTest::newRow("float") << "const C = 0.5;" << 0 << (uint) IntegralType::TypeFloat; QTest::newRow("bool") << "const C = true;" << 0 << (uint) IntegralType::TypeBoolean; QTest::newRow("array") << "const C = array();" << 0 << (uint) IntegralType::TypeArray; QTest::newRow("expression") << "const C = 'foo' . 'foo';" << 0 << (uint) IntegralType::TypeString; } void TestDUChain::fileConst() { QFETCH(QString, code); QFETCH(int, problems); QFETCH(uint, dataType); code.prepend("problems().count(), problems); QList< Declaration* > decs = top->findDeclarations(QualifiedIdentifier(QStringLiteral("C"))); QCOMPARE(decs.count(), 1); IntegralType::Ptr type = decs.first()->abstractType().cast(); QVERIFY(type); QCOMPARE(type->dataType(), dataType); QVERIFY(type->modifiers() & AbstractType::ConstModifier); } void TestDUChain::semiReservedFileConst() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("FOO")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("BAR")).count(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("FOO")).first()->context(), top); QCOMPARE(top->findDeclarations(QualifiedIdentifier("BAR")).first()->context(), top); QVERIFY(top->findDeclarations(QualifiedIdentifier("FOO")).first()->abstractType()->modifiers() & AbstractType::ConstModifier); QVERIFY(top->findDeclarations(QualifiedIdentifier("BAR")).first()->abstractType()->modifiers() & AbstractType::ConstModifier); } void TestDUChain::defaultFunctionParam() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("(top->localDeclarations().first()); QVERIFY(fun); QCOMPARE(fun->defaultParametersSize(), 3u); QVERIFY(fun->defaultParameters()[0].isEmpty()); QCOMPARE(fun->defaultParameters()[1].str(), QString("false")); QCOMPARE(fun->defaultParameters()[2].str(), QString("null")); } void TestDUChain::defaultFunctionParamWithTypehint() { QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); IntegralType::Ptr argType = IntegralType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QVERIFY(argType->dataType() == IntegralType::TypeArray); } void TestDUChain::nullDefaultFunctionParamWithTypehint() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first()->type(); QVERIFY(fun); QCOMPARE(fun->arguments().count(), 1); UnsureType::Ptr argType = UnsureType::Ptr::dynamicCast(fun->arguments().first()); QVERIFY(argType); QCOMPARE(argType->typesSize(), 2u); QVERIFY(argType->types()[0].abstractType().cast()); QVERIFY(argType->types()[0].abstractType().cast()->dataType() == IntegralType::TypeArray); QVERIFY(argType->types()[1].abstractType().cast()); QVERIFY(argType->types()[1].abstractType().cast()->dataType() == IntegralType::TypeNull); } void TestDUChain::globalFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("importedParentContexts().count(), 1); QVERIFY(DUChain::self()->chainForDocument(internalFunctionFile())); QCOMPARE(DUChain::self()->chainForDocument(internalFunctionFile()), top->importedParentContexts().first().context(top)); QCOMPARE(top->findDeclarations(QualifiedIdentifier("substr")).count(), 1); } void TestDUChain::globalVariableFromInternalFunctions() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("importedParentContexts().count(), 1); QVERIFY(DUChain::self()->chainForDocument(internalFunctionFile())); QCOMPARE(DUChain::self()->chainForDocument(internalFunctionFile()), top->importedParentContexts().first().context(top)); QCOMPARE(top->findDeclarations(QualifiedIdentifier("_GET")).count(), 1); } void TestDUChain::newObjectFromOtherFile() { TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo.php"), "localDeclarations().first()->type()->declaration(top), addTop->localDeclarations().first()); } void TestDUChain::unknownReturnType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); FunctionType::Ptr fType = dec->type(); QVERIFY(fType); QVERIFY(IntegralType::Ptr::dynamicCast(fType->returnType())); QVERIFY(IntegralType::Ptr::staticCast(fType->returnType())->dataType() == IntegralType::TypeVoid); } void TestDUChain::staticFunctionCallFromOtherFile() { TopDUContext* addTop = parseAdditionalFile(IndexedString("/duchaintest/foo2.php"), "childContexts().first()->localDeclarations().first()->type(); QVERIFY(fun); StructureType::Ptr ret = StructureType::Ptr::dynamicCast(fun->returnType()); qDebug() << fun->returnType()->toString(); QVERIFY(ret); QCOMPARE(ret->declaration(top), top->localDeclarations().first()); } void TestDUChain::internalFunctions() { return; //disabled because it is too slow QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevphpsupport/phpfunctions.php")); QFile file(fileName); file.open(QIODevice::ReadOnly | QIODevice::Text); TopDUContext* top = parse(file.readAll(), DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); } void TestDUChain::trueFalse() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0)->type()->dataType() == IntegralType::TypeBoolean); QVERIFY(top->localDeclarations().at(1)->type()->dataType() == IntegralType::TypeBoolean); } void TestDUChain::null() { QByteArray method("localDeclarations().at(0)->type()->dataType() == IntegralType::TypeNull); } void TestDUChain::array() { QByteArray method("localDeclarations().at(0)->type()->dataType() == IntegralType::TypeArray); QVERIFY(top->localDeclarations().at(1)->type()->dataType() == IntegralType::TypeArray); // $b[] = 'test'; is not a redeclaration of b! Esp. it's type should not change. QCOMPARE(top->findDeclarations(Identifier("b")).count(), 1); } void TestDUChain::functionDocBlock() { { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Bar")); QCOMPARE(top->childContexts().first()->localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("childContexts().first()->localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("childContexts().first()->localDeclarations().first()->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo\n Bar")); } { // same as above but with indendation TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo\n Bar")); } } void TestDUChain::variableDocBlock() { { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); QCOMPARE(top->localDeclarations().at(1)->comment(), QByteArray("Foo")); } { TopDUContext* top = parse("localDeclarations().first()->comment(), QByteArray("Foo")); QCOMPARE(top->localDeclarations().at(1)->comment(), QByteArray("Foo")); } } void TestDUChain::functionDocBlockParams() { TopDUContext* top = parse("localDeclarations().at(1)->type()->arguments().count(), 4); AbstractType::Ptr arg = top->localDeclarations().at(1)->type()->arguments().at(0); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeInt); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)->type()); QVERIFY(top->childContexts().at(1)->localDeclarations().at(0)->type()->dataType() == IntegralType::TypeInt); arg = top->localDeclarations().at(1)->type()->arguments().at(1); QVERIFY(StructureType::Ptr::dynamicCast(arg)); QCOMPARE(StructureType::Ptr::dynamicCast(arg)->declaration(top), top->localDeclarations().at(0)); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(1)->type()->declaration(top), top->localDeclarations().at(0)); arg = top->localDeclarations().at(1)->type()->arguments().at(2); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeMixed); arg = top->localDeclarations().at(1)->type()->arguments().at(3); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeMixed); } } void TestDUChain::memberFunctionDocBlockParams() { TopDUContext* top = parse("childContexts().first()->localDeclarations().first()->type()->arguments().count(), 3); AbstractType::Ptr arg = top->childContexts().first()->localDeclarations().first()->type()->arguments().at(0); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeBoolean); arg = top->childContexts().first()->localDeclarations().first()->type()->arguments().at(1); QVERIFY(StructureType::Ptr::dynamicCast(arg)); QCOMPARE(StructureType::Ptr::dynamicCast(arg)->declaration(top), top->localDeclarations().at(0)); arg = top->childContexts().first()->localDeclarations().first()->type()->arguments().at(2); QVERIFY(IntegralType::Ptr::dynamicCast(arg)); QVERIFY(IntegralType::Ptr::dynamicCast(arg)->dataType() == IntegralType::TypeArray); } } void TestDUChain::foreachLoop() { { TopDUContext* top = parse("$i) { $i; }", DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->localDeclarations().count(), 3); QCOMPARE(top->localDeclarations().at(1)->qualifiedIdentifier(), QualifiedIdentifier("k")); QVERIFY(top->localDeclarations().at(1)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(1)->abstractType().cast()->dataType(), static_cast(IntegralType::TypeMixed)); QCOMPARE(top->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(top->localDeclarations().at(2)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(2)->abstractType().cast()->dataType(), static_cast(IntegralType::TypeMixed)); } { // bug: https://bugs.kde.org/show_bug.cgi?id=237110 TopDUContext* top = parse("localDeclarations().count(), 3); QCOMPARE(top->localDeclarations().at(1)->qualifiedIdentifier(), QualifiedIdentifier("b")); qDebug() << top->localDeclarations().at(1)->toString(); QVERIFY(top->localDeclarations().at(1)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(1)->abstractType().cast()->dataType(), static_cast(IntegralType::TypeMixed)); QCOMPARE(top->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("c")); QVERIFY(top->localDeclarations().at(2)->abstractType().cast()); QCOMPARE(top->localDeclarations().at(2)->abstractType().cast()->qualifiedIdentifier().toString(), QString("stdclass")); } } void TestDUChain::php4StyleConstructor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("bb(); } } "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* dec = top->childContexts().first()->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("aa::aa")); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(classFuncDec->isConstructor()); } void TestDUChain::constructor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 { QByteArray method("childContexts().first()->localDeclarations().at(0); QVERIFY(dec); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(!classFuncDec->isDestructor()); QVERIFY(classFuncDec->isConstructor()); } { QByteArray method("childContexts().first()->localDeclarations().at(0); QVERIFY(dec); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(!classFuncDec->isDestructor()); QVERIFY(classFuncDec->isConstructor()); } } void TestDUChain::destructor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().at(0); QVERIFY(dec); ClassFunctionDeclaration* classFuncDec = dynamic_cast(dec); QVERIFY(classFuncDec); QVERIFY(classFuncDec->isDestructor()); QVERIFY(!classFuncDec->isConstructor()); } void TestDUChain::functionInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0)->qualifiedIdentifier(), QualifiedIdentifier("aaa")); } void TestDUChain::objectWithClassName() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo();"); TopDUContext* top = parse(method, DumpNone, QUrl(QStringLiteral("file:///internal/testObjectWithClassName.php"))); DUChainReleaser releaseTop(top); // update top (the pointer will be the same) QByteArray method2("foo();"); TopDUContext* top2 = parse(method2, DumpNone, QUrl(QStringLiteral("file:///internal/testObjectWithClassName.php"))); QVERIFY(top2 == top); } void TestDUChain::largeNumberOfDeclarations() { TopDUContext* top = new TopDUContext(IndexedString(QUrl(QStringLiteral("file:///internal/testurl"))), RangeInRevision(0, 0, 6000, 0), nullptr); DUChain::self()->addDocumentChain(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); for (int i = 0; i < 6000; ++i) { RangeInRevision newRange(i, 0, i, 1); auto* dec = new Declaration(newRange, top); dec->setIdentifier(Identifier(QStringLiteral("dec%0").arg(i))); dec->setAbstractType(AbstractType::Ptr(nullptr)); } } void TestDUChain::staticVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().at(1)->localDeclarations().count(), 6); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->qualifiedIdentifier(), QualifiedIdentifier("aaa::foo")); QVERIFY(top->childContexts().at(1)->localDeclarations().first()->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->type()->dataType(), (uint)IntegralType::TypeMixed); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(1)->qualifiedIdentifier(), QualifiedIdentifier("aaa::bar")); QVERIFY(top->childContexts().at(1)->localDeclarations().at(1)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(1)->type()->dataType(), (uint)IntegralType::TypeInt); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(2)->qualifiedIdentifier(), QualifiedIdentifier("aaa::baz")); QVERIFY(top->childContexts().at(1)->localDeclarations().at(2)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(2)->type()->dataType(), (uint)IntegralType::TypeString); QVERIFY(top->childContexts().at(1)->localDeclarations().at(3)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(3)->type()->dataType(), (uint)IntegralType::TypeArray); QVERIFY(top->childContexts().at(1)->localDeclarations().at(4)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(4)->type()->dataType(), (uint)IntegralType::TypeInt); QVERIFY(top->childContexts().at(1)->localDeclarations().at(5)->type()); QCOMPARE(top->childContexts().at(1)->localDeclarations().at(5)->type()->dataType(), (uint)IntegralType::TypeInt); } void TestDUChain::returnTypeTwoDeclarations() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); UnsureType::Ptr retType = UnsureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->typesSize(), 2u); QVERIFY(retType->types()[0].abstractType().cast()); QCOMPARE(retType->types()[0].abstractType().cast()->dataType(), (uint)IntegralType::TypeString); QVERIFY(retType->types()[1].abstractType().cast()); QCOMPARE(retType->types()[1].abstractType().cast()->dataType(), (uint)IntegralType::TypeInt); } void TestDUChain::globalVariableNotVisibleInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("a")).first()->uses().count(), 0); } void TestDUChain::globalVariableInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("a")).first()->uses().count(), 1); } void TestDUChain::nonGlobalVariableInFunction() { // bug: https://bugs.kde.org/show_bug.cgi?id=240920 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findLocalDeclarations(Identifier("a")).count(), 1); QCOMPARE(top->findLocalDeclarations(Identifier("a")).first()->uses().count(), 0); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->childContexts().last()->findLocalDeclarations(Identifier("a")).count(), 1); QCOMPARE(top->childContexts().last()->findLocalDeclarations(Identifier("a")).first()->uses().count(), 0); } void TestDUChain::superglobalInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier("_GET")).count(), 1); Declaration* dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("_GET"))).first(); QVERIFY(dynamic_cast(dec)); QVERIFY(static_cast(dec)->isSuperglobal()); QCOMPARE(dec->uses().keys().count(), 1); QCOMPARE(dec->uses().values().count(), 1); QCOMPARE(dec->uses().values().first().count(), 2); QCOMPARE(dec->uses().values().first().first(), RangeInRevision(0, 3, 0, 8)); QCOMPARE(dec->uses().values().first().at(1), RangeInRevision(0, 27, 0, 32)); } void TestDUChain::returnWithoutFunction() { //yes, this is possible in php, you then have $a as return value of an include call QByteArray method("localDeclarations().at(2)->internalContext()->importedParentContexts().empty()); QCOMPARE(top->localDeclarations().at(1)->internalContext()->importedParentContexts().count(), 1); QCOMPARE(top->localDeclarations().at(1)->internalContext()->importedParentContexts().first().context(top), top->localDeclarations().at(2)->internalContext()); QCOMPARE(top->localDeclarations().at(0)->internalContext()->importedParentContexts().count(), 1); QCOMPARE(top->localDeclarations().at(0)->internalContext()->importedParentContexts().first().context(top), top->localDeclarations().at(1)->internalContext()); } void TestDUChain::circularInterface() { QByteArray method("problems().count(), 0); QVERIFY(top->localDeclarations().at(0)->internalContext()->importedParentContexts().empty()); QCOMPARE(top->localDeclarations().at(1)->internalContext()->importedParentContexts().count(), 1); QCOMPARE(top->localDeclarations().at(1)->internalContext()->importedParentContexts().first().context(top), top->localDeclarations().at(0)->internalContext()); QCOMPARE(top->localDeclarations().at(2)->internalContext()->importedParentContexts().count(), 1); QCOMPARE(top->localDeclarations().at(2)->internalContext()->importedParentContexts().first().context(top), top->localDeclarations().at(1)->internalContext()); } void TestDUChain::findDeclarations() { DUChainWriteLocker lock(DUChain::lock()); TopDUContext* top1 = new TopDUContext(IndexedString(QUrl(QStringLiteral("file:///internal/testfile1"))), RangeInRevision(0, 0, 0, 10), nullptr); DUChainReleaser releaseTop1(top1); DUChain::self()->addDocumentChain(top1); TopDUContext* top2 = new TopDUContext(IndexedString(QUrl(QStringLiteral("file:///internal/testfile2"))), RangeInRevision(0, 0, 0, 10), nullptr); DUChainReleaser releaseTop2(top2); DUChain::self()->addDocumentChain(top2); Declaration* declaration = new Declaration(RangeInRevision(0, 0, 0, 3), top1); declaration->setIdentifier(Identifier(QStringLiteral("foo"))); QVERIFY(!top1->usingImportsCache()); QVERIFY(!top2->usingImportsCache()); QCOMPARE(1, top1->findDeclarations(Identifier("foo")).count()); QCOMPARE(0, top2->findDeclarations(Identifier("foo")).count()); top2->addImportedParentContext(top1); QVERIFY(!top1->usingImportsCache()); QVERIFY(!top2->usingImportsCache()); QCOMPARE(1, top2->findDeclarations(Identifier("foo")).count()); top2->clearImportedParentContexts(); QCOMPARE(top2->importedParentContexts().size(), 0); QVERIFY(!top1->usingImportsCache()); QVERIFY(!top2->usingImportsCache()); QCOMPARE(0, top2->findDeclarations(Identifier("foo")).count()); top2->addImportedParentContext(top1); QCOMPARE(1, top2->findDeclarations(Identifier("foo")).count()); } void TestDUChain::memberTypeAfterMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first(); // function foo { ClassMemberDeclaration* var = dynamic_cast(contextClassA->localDeclarations().first()); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("foo")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); IntegralType::Ptr ret = var->type()->returnType().cast(); QVERIFY(ret); QVERIFY(ret->dataType() == IntegralType::TypeVoid); } // public $bar { ClassMemberDeclaration* var = dynamic_cast(contextClassA->localDeclarations().at(1)); QVERIFY(var); QCOMPARE(var->identifier(), Identifier("bar")); QCOMPARE(var->accessPolicy(), Declaration::Public); QCOMPARE(var->isStatic(), false); QVERIFY(var->type()); QVERIFY(var->type()->dataType() == IntegralType::TypeNull); } } void TestDUChain::catchDeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("(top->localDeclarations().first()); QVERIFY(ex); QCOMPARE(ex->identifier(), Identifier("e")); QVERIFY(ex->type()); QCOMPARE(QualifiedIdentifier("exception"), ex->type()->declaration(top)->qualifiedIdentifier()); } void TestDUChain::resourceType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("(top->localDeclarations().first()); QVERIFY(fun); FunctionType::Ptr ftype = FunctionType::Ptr::dynamicCast(fun->abstractType()); QVERIFY(ftype); IntegralType::Ptr rtype = IntegralType::Ptr::dynamicCast(ftype->returnType()); QVERIFY(rtype); QCOMPARE(rtype->toString(), QString("resource")); QVERIFY(rtype->dataType() == IntegralTypeExtended::TypeResource); } void TestDUChain::foreachIterator() { QByteArray code; code.append("localDeclarations().at(3); QCOMPARE(iDec->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(top->localDeclarations().first() == iDec->type()->declaration(top)); } void TestDUChain::foreachIterator2() { QByteArray code; code.append("localDeclarations().size(), 3); Declaration* iDec = top->localDeclarations().at(2); QCOMPARE(iDec->qualifiedIdentifier(), QualifiedIdentifier("i")); qDebug() << iDec->abstractType()->toString(); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(top->localDeclarations().first() == iDec->type()->declaration(top)); } void TestDUChain::foreachIterator3() { QByteArray code; code.append("localDeclarations().at(3); QCOMPARE(iDec->qualifiedIdentifier(), QualifiedIdentifier("i")); QVERIFY(iDec->type()); QCOMPARE(iDec->type()->qualifiedIdentifier(), QualifiedIdentifier("b")); QVERIFY(top->localDeclarations().first() == iDec->type()->declaration(top)); } void TestDUChain::foreachIterator4() { // see also: https://bugs.kde.org/show_bug.cgi?id=276603 QByteArray code = "i){}\n" " foreach(array(1,2) as $this->k => $this->v){}\n" " foreach(array(1,2) as A::$s){}\n" " }\n" "}\n"; TopDUContext* top = parse(code, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); Declaration* aDec = top->localDeclarations().first(); DUContext* fooCtx = top->childContexts().first()->childContexts().last(); QVERIFY(fooCtx->owner()); QCOMPARE(aDec->uses().size(), 1); QCOMPARE(aDec->uses().begin()->size(), 4); } void TestDUChain::returnThis() { QByteArray code("childContexts().first()->localDeclarations().first(); QVERIFY(dec->type()); AbstractType::Ptr t = dec->type()->returnType(); qDebug() << t->toString(); QVERIFY(StructureType::Ptr::dynamicCast(t)); QVERIFY(StructureType::Ptr::dynamicCast(t)->declaration(top) == top->localDeclarations().first()); } void TestDUChain::unsureReturnType() { QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)2, ut->typesSize()); QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->dataType() == IntegralType::TypeBoolean); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeInt); } void TestDUChain::unsureReturnType2() { QByteArray code("localDeclarations().at(2); QVERIFY(dec->type()); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)2, ut->typesSize()); QVERIFY(ut->types()[0].type()); QCOMPARE(ut->types()[0].type()->toString(), QString("A")); QVERIFY(ut->types()[1].type()); QCOMPARE(ut->types()[1].type()->toString(), QString("B")); } void TestDUChain::unsureReturnType3() { QByteArray code("localDeclarations().at(0); QVERIFY(dec->type()); qDebug() << dec->type()->returnType()->toString(); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)3, ut->typesSize()); QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->dataType() == IntegralType::TypeInt); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeBoolean); QVERIFY(ut->types()[2].type()); QVERIFY(ut->types()[2].type()->dataType() == IntegralType::TypeString); } void TestDUChain::unsureReturnType4() { QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); TypePtr ut = dec->type()->returnType().cast(); QVERIFY(ut); QCOMPARE((uint)2, ut->typesSize()); QVERIFY(ut->types()[0].type()); QVERIFY(ut->types()[0].type()->dataType() == IntegralType::TypeBoolean); QVERIFY(ut->types()[1].type()); QVERIFY(ut->types()[1].type()->dataType() == IntegralType::TypeInt); } void TestDUChain::referencedArgument() { // php does not return references QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); qDebug() << dec->abstractType()->toString(); IntegralType::Ptr aType = dec->type()->returnType().cast(); QVERIFY(aType); QCOMPARE(aType->dataType(), (uint)IntegralType::TypeInt); QCOMPARE(top->childContexts().first()->type(), DUContext::Function); ReferenceType::Ptr rType = top->childContexts().first()->localDeclarations().first()->abstractType().cast(); QVERIFY(rType); QVERIFY(rType->baseType()->equals(aType.data())); } void TestDUChain::unsureReferencedArgument() { // php does not return references QByteArray code("localDeclarations().first(); QVERIFY(dec->type()); qDebug() << dec->abstractType()->toString(); UnsureType::Ptr aType = dec->type()->returnType().cast(); QVERIFY(aType); QCOMPARE(aType->typesSize(), 2u); QCOMPARE(aType->types()[0].abstractType().cast()->dataType(), (uint)IntegralType::TypeInt); QCOMPARE(aType->types()[1].abstractType().cast()->dataType(), (uint)IntegralType::TypeString); QCOMPARE(top->childContexts().first()->type(), DUContext::Function); ReferenceType::Ptr rType = top->childContexts().first()->localDeclarations().first()->abstractType().cast(); QVERIFY(rType); QVERIFY(rType->baseType()->equals(aType.data())); } void TestDUChain::defaultArgument() { // php does not return references QByteArray code("childContexts().first()->localDeclarations().first(); QVERIFY(dec->type()); QCOMPARE(dec->type()->dataType(), (uint)IntegralType::TypeInt); } void TestDUChain::declareMemberOutOfClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("asdf = true; $bar->asdf = false;\n" // not allowed: "$bar->prot = 1;\n" // not allowed: "$bar->priv = 1;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); { // $bar is only declared once QList decs = top->findLocalDeclarations(Identifier(QStringLiteral("bar"))); QCOMPARE(decs.size(), 1); Declaration *dec = decs.first(); QVERIFY(dec->type()); QVERIFY(dec->type()->declaration(top)->identifier().nameEquals(Identifier("foo"))); // while we are at it, compare uses QCOMPARE(dec->uses().keys().count(), 1); QCOMPARE(dec->uses().values().count(), 1); QCOMPARE(dec->uses().values().first().count(), 4); qDebug() << dec->uses().values().first().at(0).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(0), RangeInRevision(1, 16, 1, 20)); qDebug() << dec->uses().values().first().at(1).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(1), RangeInRevision(1, 35, 1, 39)); qDebug() << dec->uses().values().first().at(2).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(2), RangeInRevision(2, 0, 2, 4)); qDebug() << dec->uses().values().first().at(3).castToSimpleRange(); QCOMPARE(dec->uses().values().first().at(3), RangeInRevision(3, 0, 3, 4)); } { // check if asdf got declared QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("asdf"))); // the type of both assignments to $bar->asdf are the same, hence it should only be declared once QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->accessPolicy() == Declaration::Public); QVERIFY(!cmdec->isStatic()); QVERIFY(cmdec->type()); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeBoolean); } // check that prot and priv don't get redeclared QCOMPARE(top->problems().count(), 2); QCOMPARE(top->problems().at(0)->finalLocation().start().line(), 2); QCOMPARE(top->problems().at(1)->finalLocation().start().line(), 3); } void TestDUChain::declareMemberOutOfClass2() { // see also: https://bugs.kde.org/show_bug.cgi?id=283356 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("x = 1;\n" "class A { var $x = 1; }"); TopDUContext* top = parse(code, DumpAST); QVERIFY(top); // update top = parse(code, DumpNone, top->url().toUrl(), ReferencedTopDUContext(top)); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); QList decs = top->findLocalDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 2); { Declaration *dec = decs.first(); QVERIFY(dynamic_cast(dec)); QVERIFY(dec->type()); QVERIFY(dec->type()->declaration(top)->identifier().nameEquals(Identifier("a"))); } { Declaration *dec = decs.last(); QVERIFY(dynamic_cast(dec)); QVERIFY(dec->type()); QVERIFY(dec->type()->declaration(top)->identifier().nameEquals(Identifier("a"))); } { // check if x got declared QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("x"))); // the type of both assignments to $a->x are the same, hence it should only be declared once QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->accessPolicy() == Declaration::Public); QVERIFY(!cmdec->isStatic()); QVERIFY(cmdec->type()); QCOMPARE(cmdec->type()->dataType(), (uint) IntegralType::TypeInt); } } void TestDUChain::declareMemberInClassMethod() { QByteArray code("asdf = true; $this->asdf = false; }\n" // should only declare bar once as private " private $xyz = 0; function test2() { $this->xyz = 42; }\n" // should create a local declaration for the private attribute " function test3() { $this->prot = 42;\n$this->priv = 42; }\n" " }"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); { // asdf QList decs = top->childContexts().last()->findLocalDeclarations(Identifier(QStringLiteral("asdf"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration *dec = dynamic_cast(decs.first()); QVERIFY(dec); QVERIFY(dec->accessPolicy() == Declaration::Public); QVERIFY(!dec->isStatic()); QVERIFY(dec->type()); QVERIFY(dec->type()->dataType() == IntegralType::TypeBoolean); } { // xyz QList decs = top->childContexts().last()->findLocalDeclarations(Identifier(QStringLiteral("xyz"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration *dec = dynamic_cast(decs.first()); QVERIFY(dec); QVERIFY(dec->accessPolicy() == Declaration::Private); QVERIFY(!dec->isStatic()); QVERIFY(dec->type()); QVERIFY(dec->type()->dataType() == IntegralType::TypeInt); } { // priv QList decs = top->childContexts().last()->findLocalDeclarations(Identifier(QStringLiteral("priv"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration *dec = dynamic_cast(decs.first()); QVERIFY(dec); QVERIFY(dec->accessPolicy() == Declaration::Public); QVERIFY(!dec->isStatic()); QVERIFY(dec->type()); QVERIFY(dec->type()->dataType() == IntegralType::TypeInt); } { // prot QVERIFY(top->childContexts().last()->findLocalDeclarations(Identifier("prot")).isEmpty()); } QCOMPARE(top->problems().count(), 0); } void TestDUChain::thisRedeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("test = true; $this = false;} }"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // only $this = false is a problem, $this->test = true is perfectly valid QCOMPARE(top->problems().count(), 1); qDebug() << top->problems().first()->finalLocation(); QVERIFY(top->problems().first()->finalLocation() == KDevelop::DocumentRange(top->url(), KTextEditor::Range(0, 50, 0, 55))); } void TestDUChain::implicitArrayDeclaration() { ///TODO: adapt to unsure type once it's supported { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" decs = top->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); VariableDeclaration* vdec = dynamic_cast(decs.first()); QVERIFY(vdec); QVERIFY(vdec->type()); QVERIFY(vdec->type()->dataType() == IntegralType::TypeArray); } { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" decs = top->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); VariableDeclaration* vdec = dynamic_cast(decs.first()); QVERIFY(vdec); QVERIFY(vdec->type()); QVERIFY(vdec->type()->dataType() == IntegralType::TypeArray); } { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("a[1] = true;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->type()); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeArray); } { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("a[$b] = true;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList decs = top->childContexts().first()->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->type()); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeArray); } } void TestDUChain::implicitReferenceDeclaration() { { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" decs = top->findDeclarations(Identifier(QStringLiteral("bar"))); QCOMPARE(decs.size(), 1); QVERIFY(dynamic_cast(decs.first())); QVERIFY(decs.first()->type()); qDebug() << decs.first()->type()->dataType() << decs.first()->toString(); QVERIFY(decs.first()->type()->dataType() == IntegralType::TypeNull); } { // a user reported a crash with the code example below // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("a);} }"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVERIFY( top->childContexts().last()->localScopeIdentifier() == QualifiedIdentifier("foo")); // a is already declared QList decs = top->childContexts().last()->findDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QVERIFY(cmdec->type()); qDebug() << cmdec->type()->dataType() << cmdec->toString(); QVERIFY(cmdec->type()->dataType() == IntegralType::TypeNull); } } void TestDUChain::classContextRange() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code(" foobar = 1; $a->barFoo= 0;"); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().first()->range(), KDevelop::RangeInRevision(0, 6, 0, 17)); QCOMPARE(top->childContexts().first()->localDeclarations().count(), 2); } void TestDUChain::lateClassMembers() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray code("val = 'b'; } private $val = 'a'; } "); TopDUContext* top = parse(code, DumpAST); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); ClassDeclaration* cdec = dynamic_cast(top->localDeclarations().first()); QVERIFY(cdec); QList decs = cdec->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("val"))); QCOMPARE(decs.count(), 1); ClassMemberDeclaration* cmdec = dynamic_cast(decs.first()); QVERIFY(cmdec); QCOMPARE(cmdec->accessPolicy(), Declaration::Private); } void TestDUChain::list() { foreach ( const QString& code, QStringList() << " decs = top->findDeclarations(Identifier(identifier)); QCOMPARE(decs.size(), 1); Declaration *dec = decs.first(); QVERIFY(dec->type()); QCOMPARE(dec->type()->dataType(), (uint) IntegralType::TypeMixed); ///TODO: support arrays better and compare to actual type } } } void TestDUChain::alternateDocCommentTypeHints() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("(top->localDeclarations().first()); QVERIFY(cdec); QVERIFY(cdec->type()); QVector decs = cdec->logicalInternalContext(top)->localDeclarations(); QCOMPARE(decs.size(), 1); Declaration *dec = decs.first(); QVERIFY(dec->type()); QCOMPARE(dec->type()->declaration(top), cdec); } void TestDUChain::findFunctionArgs() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("(top->localDeclarations().first()); QVERIFY(funcDec); QVERIFY(funcDec->internalContext()); QVERIFY(funcDec->internalFunctionContext()); QVERIFY(funcDec->internalContext()->imports(funcDec->internalFunctionContext())); QList decs; foreach ( Declaration* arg, funcDec->internalFunctionContext()->localDeclarations() ) { decs = funcDec->internalContext()->findDeclarations(arg->identifier()); QCOMPARE(decs.size(), 1); decs = funcDec->internalContext()->findDeclarations(arg->qualifiedIdentifier()); qDebug() << arg->qualifiedIdentifier().toString(); QEXPECT_FAIL("", "strangely the arg dec is only found with its identifier, not by its qualifiedidentifier...", Continue); QCOMPARE(decs.size(), 1); } } void TestDUChain::undeclaredPropertyInString() { // testcase for bug 209814 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("baz\"; } }", DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->childContexts().size(), 1); DUContext* classCtx = top->childContexts().first(); QVERIFY(classCtx->type() == DUContext::Class); QCOMPARE(classCtx->localDeclarations().size(), 2); QCOMPARE(classCtx->findDeclarations(Identifier("foo")).size(), 1); QCOMPARE(classCtx->findDeclarations(Identifier("bar")).size(), 1); } void TestDUChain::undeclaredVarPropertyInString() { // testcase for bug 210043 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("baz\";", DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // just don't crash } void TestDUChain::upcommingClassInString() { // testcase for bug 232687 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("blah\";\n" " }\n" "}\n" "class B {\n" " var $blah;\n" "}\n", DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // just don't crash } void TestDUChain::namespaces() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().count(), 0); QCOMPARE(top->childContexts().size(), 4); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier().toString(), QString("asdf")); QCOMPARE(top->childContexts().at(1)->localScopeIdentifier().toString(), QString("ns1")); QCOMPARE(top->childContexts().at(2)->type(), DUContext::Function); QCOMPARE(top->childContexts().at(3)->localScopeIdentifier().toString(), QString("a")); QCOMPARE(top->localDeclarations().size(), 3); QCOMPARE(top->localDeclarations().at(0)->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations().at(1)->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations().at(2)->kind(), Declaration::Type); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf")).size(), 1); QCOMPARE(top->childContexts().at(0)->localDeclarations().size(), 3); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf::a")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf::b")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("asdf::c")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1")).size(), 1); QCOMPARE(top->childContexts().at(1)->localDeclarations().size(), 1); QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->kind(), Declaration::Namespace); ///TODO: support \ as separator QCOMPARE(top->childContexts().at(1)->localDeclarations().first()->qualifiedIdentifier().toString(), QString("ns1::ns2")); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2")).first()->logicalInternalContext(top)->localDeclarations().size(), 3); QCOMPARE(top->childContexts().at(1)->childContexts().size(), 1); QCOMPARE(top->childContexts().at(1)->childContexts().first()->localDeclarations().size(), 3); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2")).first()->logicalInternalContext(top)->localDeclarations().first()->qualifiedIdentifier().toString(), QString("ns1::ns2::a")); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2::a")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2::b")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("ns1::ns2::c")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("a")).size(), 1); QCOMPARE(top->findDeclarations(QualifiedIdentifier("b")).size(), 0); QCOMPARE(top->findDeclarations(QualifiedIdentifier("c")).size(), 0); ///TODO: prevent redeclarations of namespaces } void TestDUChain::namespacesNoCurly() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().count(), 0); foreach(ProblemPointer p, top->problems()) { qDebug() << p->description() << p->explanation() << p->finalLocation(); } QCOMPARE(top->childContexts().size(), 2); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier().toString(), QString("asdf")); QCOMPARE(top->childContexts().at(1)->localScopeIdentifier().toString(), QString("ns1")); QCOMPARE(top->localDeclarations().size(), 2); QCOMPARE(top->localDeclarations().at(0)->kind(), Declaration::Namespace); QCOMPARE(top->localDeclarations().at(1)->kind(), Declaration::Namespace); } void TestDUChain::namespacesBaseType() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().count(), 0); foreach(ProblemPointer p, top->problems()) { qDebug() << p->description() << p->explanation() << p->finalLocation(); } QCOMPARE(top->childContexts().size(), 1); QCOMPARE(top->childContexts().at(0)->localScopeIdentifier().toString(), QString("string")); QCOMPARE(top->localDeclarations().size(), 1); QCOMPARE(top->localDeclarations().at(0)->kind(), Declaration::Namespace); } void TestDUChain::useNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().count(), 5); Declaration* dec = top->localDeclarations().at(2); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("ns2")); QVERIFY(dynamic_cast(dec)); dec = top->localDeclarations().at(3); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("ns5")); QVERIFY(dynamic_cast(dec)); dec = top->localDeclarations().at(4); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("ns6")); QVERIFY(dynamic_cast(dec)); ///TODO: find out why this is explictly required QVERIFY(!dynamic_cast(dec)->importIdentifier().explicitlyGlobal()); } void TestDUChain::useBaseTypeNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().count(), 4); Declaration* dec = top->localDeclarations().at(2); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("string")); QVERIFY(dynamic_cast(dec)); dec = top->localDeclarations().at(3); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("iterable")); QVERIFY(dynamic_cast(dec)); } void TestDUChain::useNamespaceBaseTypeAlias() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().count(), 2); } void TestDUChain::namespaceStaticVar() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().isEmpty()); Declaration* fooDec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("ns::c::foo"))).first(); QVERIFY(fooDec); QVERIFY(!fooDec->uses().isEmpty()); QVERIFY(!fooDec->uses().begin()->isEmpty()); QCOMPARE(fooDec->uses().begin()->begin()->start.line, 5); } void TestDUChain::namespacedCatch() { // see also: https://bugs.kde.org/show_bug.cgi?id=281451 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().isEmpty()); Declaration* eDec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("ns::e"))).first(); QVERIFY(eDec); QVERIFY(!eDec->uses().isEmpty()); QVERIFY(!eDec->uses().begin()->isEmpty()); QCOMPARE(eDec->uses().begin()->begin()->start.line, 6); } struct TestUse { TestUse(const QString& _id, Declaration::Kind _kind, int _uses) : id(_id), kind(_kind), uses(_uses) {} TestUse() {} QualifiedIdentifier id; Declaration::Kind kind; int uses; }; Q_DECLARE_METATYPE ( TestUse ) Q_DECLARE_METATYPE ( QList ) void TestDUChain::errorRecovery_data() { QTest::addColumn("code"); QTest::addColumn< QList >("usesMap"); QTest::newRow("conditional") << QStringLiteral("() << TestUse(QStringLiteral("a"), Declaration::Instance, 1)); QTest::newRow("namespace") << QStringLiteral("() << TestUse(QStringLiteral("foo"), Declaration::Namespace, 1) << TestUse(QStringLiteral("y"), Declaration::Namespace, 0) << TestUse(QStringLiteral("foo::a"), Declaration::Instance, 1)); QTest::newRow("class") << QStringLiteral("() << TestUse(QStringLiteral("foo"), Declaration::Type, 0) << TestUse(QStringLiteral("foo::bar"), Declaration::Instance, 1) << TestUse(QStringLiteral("foo::func"), Declaration::Type, 1) ); } void TestDUChain::errorRecovery() { QFETCH(QString, code); QFETCH(QList, usesMap); TopDUContext* top = parse(code.toLocal8Bit(), DumpAll); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; foreach ( const TestUse& use, usesMap ) { QList< Declaration* > decs = top->findDeclarations(use.id); QCOMPARE(decs.count(), 1); Declaration* dec = decs.first(); QCOMPARE(dec->kind(), use.kind); if (use.uses) { QCOMPARE(dec->uses().count(), 1); QCOMPARE(dec->uses().begin()->count(), use.uses); } } } void TestDUChain::varStatic() { //bug: https://bugs.kde.org/244076 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().empty()); // we cannot support anything though :( } void TestDUChain::staticNowdoc() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().empty()); QCOMPARE(top->childContexts().first()->localDeclarations().count(), 2); QCOMPARE(top->childContexts().first()->localDeclarations().first()->type()->dataType(), static_cast(IntegralType::TypeString)); QCOMPARE(top->childContexts().first()->localDeclarations().last()->type()->dataType(), static_cast(IntegralType::TypeString)); } void TestDUChain::curlyVarAfterObj() { // bug: https://bugs.kde.org/show_bug.cgi?id=241645 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("{$a->bar}();\n" "$a->{$a->asdf};\n" , DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().empty()); } void TestDUChain::embeddedHTML_data() { QTest::addColumn("code"); QTest::newRow("if") << QStringLiteral("\n"); QTest::newRow("elseif") << QStringLiteral("\n\n"); QTest::newRow("foreach") << QStringLiteral("\n\n"); QTest::newRow("switch") << QStringLiteral("\n\n"); QTest::newRow("for") << QStringLiteral("\n\n"); QTest::newRow("while") << QStringLiteral("\n\n"); QTest::newRow("else") << QStringLiteral(""); } void TestDUChain::embeddedHTML() { QFETCH(QString, code); TopDUContext* top = parse(code.toLocal8Bit(), DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().empty()); } void TestDUChain::cases() { // testcase for bug https://bugs.kde.org/show_bug.cgi?id=245832 TopDUContext* top = parse("problems().empty()); } void TestDUChain::closureParser() { // testcase for the parser after closures where introduced, // to make sure nothing brakes and all parser conflicts are resolved TopDUContext* top = parse("problems().empty()); } void TestDUChain::closures() { TopDUContext* top = parse("problems().isEmpty()); QCOMPARE(top->localDeclarations().count(), 2); Declaration* l = top->localDeclarations().first(); QCOMPARE(l->identifier().toString(), QString("l")); Declaration* closure = top->localDeclarations().last(); QVERIFY(closure->identifier().isEmpty()); FunctionType::Ptr funcType = closure->type(); QVERIFY(funcType); QCOMPARE(funcType->arguments().count(), 2); QVERIFY(funcType->arguments().at(0).cast()); QCOMPARE(funcType->arguments().at(0).cast()->dataType(), static_cast(IntegralType::TypeMixed)); QVERIFY(funcType->arguments().at(1).cast()); QCOMPARE(funcType->arguments().at(1).cast()->qualifiedIdentifier().toString(), QString("stdclass")); QVERIFY(funcType->returnType().cast()); QCOMPARE(funcType->returnType().cast()->dataType(), static_cast(IntegralType::TypeInt)); QVERIFY(l->abstractType()->equals(closure->abstractType().constData())); } void TestDUChain::closureEmptyUse() { // test case for: https://bugs.kde.org/show_bug.cgi?id=267105 // don't crash but report parse error TopDUContext* top = parse(" 2; };\n", DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QCOMPARE(top->problems().size(), 1); } void TestDUChain::iifeParser() { // testcase for bug https://bugs.kde.org/show_bug.cgi?id=370515 TopDUContext* top = parse("problems().empty()); } void TestDUChain::iife() { TopDUContext* top = parse("problems().isEmpty()); QCOMPARE(top->localDeclarations().count(), 2); Declaration* l = top->localDeclarations().first(); QCOMPARE(l->identifier().toString(), QString("l")); Declaration* iife = top->localDeclarations().last(); QVERIFY(iife->identifier().isEmpty()); } void TestDUChain::gotoTest() { TopDUContext* top = parse("problems().isEmpty()); ///TODO: create declaration for destination label ///TODO: create use for goto label ///TODO: report error when trying to jump into loop or switch statement } void TestDUChain::ternary() { TopDUContext* top = parse("problems().isEmpty()); } void TestDUChain::bug296709() { // see also: https://bugs.kde.org/show_bug.cgi?id=296709 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse( "problems().isEmpty()); QList< Declaration* > decs = top->findLocalDeclarations(Identifier(QStringLiteral("a"))); QCOMPARE(decs.size(), 1); QCOMPARE(decs.at(0)->range(), RangeInRevision(1, 19, 1, 21)); QCOMPARE(decs.at(0)->uses().count(), 1); QCOMPARE(decs.at(0)->uses().begin()->count(), 1); QCOMPARE(decs.at(0)->uses().begin()->first(), RangeInRevision(2, 2, 2, 4)); } void TestDUChain::declareFinalMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 1); DUContext* contextClassA = top->childContexts().first(); Declaration* dec = contextClassA->localDeclarations().at(0); ClassFunctionDeclaration* funDec = dynamic_cast(dec); QVERIFY(funDec); QCOMPARE(funDec->qualifiedIdentifier(), QualifiedIdentifier("a::foo")); QVERIFY(funDec->isFinal()); } void Php::TestDUChain::testTodoExtractor() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("languageController()->completionSettings()->todoMarkerWords().contains("TODO")); QVERIFY(KDevelop::ICore::self()->languageController()->completionSettings()->todoMarkerWords().contains("FIXME")); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVERIFY(top); QCOMPARE(top->problems().size(), 2); QCOMPARE(top->problems().at(0)->description(), QString("TODO: bla")); QCOMPARE(top->problems().at(0)->range(), RangeInRevision(1, 3, 1, 12)); QCOMPARE(top->problems().at(1)->description(), QString("FIXME blub")); QCOMPARE(top->problems().at(1)->range(), RangeInRevision(2, 4, 2, 14)); } void TestDUChain::useThisAsArray() { QByteArray method("values[$offset]; }\n" " function offsetSet($offset, $value) { $this->values[$offset] = $value; }\n" " function offsetExists($offset) { return array_key_exists($offset, $this->values); }\n" " function offsetUnset($offset) { unset($this->values[$offset]); }\n" " function setTest() { $this['test'] = 'test'; } \n" " }\n"); TopDUContext* top = parse(method); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(top->importedParentContexts().count(), 1); QVERIFY(DUChain::self()->chainForDocument(internalFunctionFile())); QCOMPARE(DUChain::self()->chainForDocument(internalFunctionFile()), top->importedParentContexts().first().context(top)); QVERIFY(top->problems().isEmpty()); } void TestDUChain::wrongUseOfThisAsArray() { // missing functions from \ArrayAccess and not declared abstract QByteArray method("problems().size(),1); } void TestDUChain::staticFunctionClassPhp54() { QByteArray method("problems().isEmpty()); QCOMPARE(top->localDeclarations().count(),1); Declaration* dec = top->localDeclarations().at(0); ClassDeclaration* classDec = dynamic_cast(dec); QCOMPARE(classDec->uses().count(),1); } void TestDUChain::functionArgumentUnpacking_data() { QTest::addColumn("code"); QTest::newRow("unpackVariable") << QStringLiteral("problems().isEmpty()); } void TestDUChain::illegalExpression_data() { QTest::addColumn("code"); QTest::newRow("equality expression") << QStringLiteral(" 2 > 1;\n"); QTest::newRow("double print expression") << QStringLiteral("foo;\n"); QTest::newRow("instanceof with class constant") << QStringLiteral("foo();\n"); QTest::newRow("instanceof with static method") << QStringLiteral("("code"); QTest::newRow("simple print expression") << QStringLiteral("problems().isEmpty()); } void TestDUChain::simpleExpression_data() { QTest::addColumn("code"); QTest::newRow("string concat") << QStringLiteral("problems().isEmpty()); } void TestDUChain::generatorAssignment() { //Note: in practice, Generator is defined by php, but this class is not loaded in this test, so define it ourselves // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); StructureType::Ptr retType = StructureType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QCOMPARE(retType->qualifiedIdentifier(), QualifiedIdentifier("generator")); dec = top->childContexts().at(2)->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); IntegralType::Ptr type = dec->type(); QVERIFY(type); QVERIFY(type->dataType() == IntegralType::TypeMixed); } void TestDUChain::generatorClosure() { //Note: in practice, Generator is defined by php, but this class is not loaded in this test, so define it ourselves // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("parentContext()); QCOMPARE(top->childContexts().count(), 3); QCOMPARE(top->localDeclarations().count(), 2); Declaration* dec = top->localDeclarations().at(1); QCOMPARE(dec->qualifiedIdentifier(), QualifiedIdentifier("foo")); FunctionType::Ptr functionType = dec->type(); QVERIFY(functionType); IntegralType::Ptr retType = IntegralType::Ptr::dynamicCast(functionType->returnType()); QVERIFY(retType); QVERIFY(retType->dataType() == IntegralType::TypeVoid); dec = top->childContexts().at(2)->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); Declaration* closure = top->childContexts().at(2)->localDeclarations().at(1); QVERIFY(closure->identifier().isEmpty()); FunctionType::Ptr funcType = closure->type(); QVERIFY(funcType); QVERIFY(funcType->returnType().cast()); QCOMPARE(funcType->returnType().cast()->dataType(), static_cast(IntegralType::TypeVoid)); QVERIFY(dec->abstractType()->equals(closure->abstractType().constData())); dec = top->childContexts().at(2)->childContexts().at(1)->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("b")); closure = top->childContexts().at(2)->childContexts().at(1)->localDeclarations().at(1); QVERIFY(closure->identifier().isEmpty()); funcType = closure->type(); QVERIFY(funcType); StructureType::Ptr generatorType = StructureType::Ptr::dynamicCast(funcType->returnType()); QVERIFY(generatorType); QCOMPARE(generatorType->qualifiedIdentifier(), QualifiedIdentifier("generator")); QVERIFY(dec->abstractType()->equals(closure->abstractType().constData())); } diff --git a/duchain/tests/duchain.h b/duchain/tests/duchain.h index 5ed9d12..e40c76b 100644 --- a/duchain/tests/duchain.h +++ b/duchain/tests/duchain.h @@ -1,204 +1,207 @@ /* This file is part of KDevelop Copyright 2008 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 TESTDUCHAIN_H #define TESTDUCHAIN_H #include "tests/duchaintestbase.h" namespace Php { class TestDUChain : public DUChainTestBase { Q_OBJECT public: TestDUChain(); private slots: void declareFunction(); void declareBaseTypeFunction(); void declareSemiReservedFunction(); void declareVar(); void varTypehint(); void declareClass(); void declareBaseTypeClass(); void declareClassWithSemiReservedMethod(); void declareClassWithBaseTypeMethod(); void classMemberVar(); void classMemberVarAfterUse(); void classMemberVarDocBlockType(); void declareTypehintFunction(); void declareVariadicFunction(); void declareTypehintVariadicFunction(); void declareTypehintObjectFunction(); void declareTypehintArrayFunction(); void declareTypehintCallableFunction(); void functionWithCallableAndFunctionReturn(); void declareTypehintIterableFunction(); void declareTypehintBoolFunction(); void declareTypehintFloatFunction(); void declareTypehintIntFunction(); void declareTypehintStringFunction(); void declareNullableTypehintArrayFunction(); void declareNullableTypehintMixedFunction(); void declareTypehintNullableIterableFunction(); void declareTypehintWithPhpdocFunction(); void returnTypeGenerator_data(); void returnTypeGenerator(); void returnTypeGeneratorDelegation(); void returnTypeClass(); void declarationReturnType(); void declarationReturnTypeInRecursingFunction(); void returnTypeViaMember(); void declarationMultipleReturnTypes(); void declarationReturnTypeDocBlock(); void declarationReturnTypeDocBlockIntegral(); void declarationReturnTypeClassChain(); void declarationReturnTypeTypehint(); void declarationReturnTypeTypehintVoid(); void declarationReturnTypeTypehintObject(); void classImplementsInterface(); void classExtends(); void staticMethod(); void ownStaticMethod(); void thisVar(); void objectFunctionCall(); void objectFunctionCall2(); void objectFunctionCall3(); void objectVariable(); void staticMemberVariable(); void ownStaticMemberVariable(); void classConst(); void classConst_data(); void classConstWithTypeHint(); + void classConstVisibility(); void semiReservedClassConst(); + void illegalClassConst(); + void illegalClassConst_data(); void fileConst(); void fileConst_data(); void semiReservedFileConst(); void define(); void defaultFunctionParam(); void defaultFunctionParamWithTypehint(); void nullDefaultFunctionParamWithTypehint(); void globalFunction(); void globalVariableFromInternalFunctions(); void newObjectFromOtherFile(); void unknownReturnType(); void staticFunctionCallFromOtherFile(); void classConstantFromOtherFile(); void globalFunctionCallFromOtherFile(); void constantFromOtherFile(); void singleton(); void internalFunctions(); void trueFalse(); void null(); void array(); void functionDocBlock(); void variableDocBlock(); void functionDocBlockParams(); void memberFunctionDocBlockParams(); void foreachLoop(); void php4StyleConstructor(); void constructor(); void destructor(); void functionInFunction(); void objectWithClassName(); void largeNumberOfDeclarations(); void staticVariable(); void returnTypeTwoDeclarations(); void globalVariableNotVisibleInFunction(); void globalVariableInFunction(); void nonGlobalVariableInFunction(); void superglobalInFunction(); void returnWithoutFunction(); void circularInheritance(); void circularInterface(); void findDeclarations(); void memberTypeAfterMethod(); void catchDeclaration(); void resourceType(); void foreachIterator(); void foreachIterator2(); void foreachIterator3(); void foreachIterator4(); void returnThis(); void unsureReturnType(); void unsureReturnType2(); void unsureReturnType3(); void unsureReturnType4(); void referencedArgument(); void unsureReferencedArgument(); void defaultArgument(); void declareMemberOutOfClass(); void declareMemberOutOfClass2(); void declareMemberInClassMethod(); void thisRedeclaration(); void implicitArrayDeclaration(); void implicitReferenceDeclaration(); void classContextRange(); void lateClassMembers(); void list(); void alternateDocCommentTypeHints(); void findFunctionArgs(); void undeclaredPropertyInString(); void undeclaredVarPropertyInString(); void upcommingClassInString(); void namespaces(); void namespacesNoCurly(); void namespacesBaseType(); void useNamespace(); void useBaseTypeNamespace(); void useNamespaceBaseTypeAlias(); void namespaceStaticVar(); void namespacedCatch(); void errorRecovery_data(); void errorRecovery(); void varStatic(); void staticNowdoc(); void curlyVarAfterObj(); void embeddedHTML_data(); void embeddedHTML(); void cases(); void closureParser(); void closures(); void closureEmptyUse(); void iifeParser(); void iife(); void gotoTest(); void ternary(); void bug296709(); void declareFinalMethod(); void testTodoExtractor(); void useThisAsArray(); void wrongUseOfThisAsArray(); void staticFunctionClassPhp54(); void functionArgumentUnpacking_data(); void functionArgumentUnpacking(); void illegalExpression_data(); void illegalExpression(); void printExpression_data(); void printExpression(); void simpleExpression_data(); void simpleExpression(); void generatorAssignment(); void generatorClosure(); }; } #endif diff --git a/parser/php.g b/parser/php.g index e3e30ba..afef331 100644 --- a/parser/php.g +++ b/parser/php.g @@ -1,1297 +1,1297 @@ ------------------------------------------------------------------------------- -- Copyright (c) 2008 Niko Sams -- -- This grammar 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 grammar 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 -- Lesser 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. ----------------------------------------------------------- ----------------------------------------------------------- -- Grammar for PHP 5.2 -- Modelled after the Zend Grammar shipped with PHP5.2 -- source, the PHP Language Reference documentation, -- and parts taken from KDevelop Java Grammar ----------------------------------------------------------- -- 4 first/first conflicts: -- - var_expression: variable vs. varExpressionNormal -- no problem because of ifs that allow always just one rule -- - classNameReference: STRING vs. staticMember (foo vs. foo::$bar) -- resolved by LA() -- - encapsVar: STRING_VARNAME LBRACKET vs. expr (expr allows STRING_VARNAME too - but not LBRACKET) -- resolved by LA() -- - constantOrClassConst: constant vs. class constant (FOO v.s Cls::FOO) -- resolved by LA() (could be avoided, but the Ast is much cleaner that way) -- 1 first/follow conflicts: -- - elseifList: dangling-else conflict - should be ok -- TODO: (post 1.0.0 release) -- 1) decrease memory consumption -- 1.1) use quint32 instead of qint64 for end/start tokens -- 1.2) investigate whether using a map/hash for the ducontext member of all -- ast nodes gives a significant memory decrease while not hampering performance -- 1.3) investigate how unions could be used for exclusive AST node members -- 1.4) see whether we can always use the expression lists instead of both -- single member pointer and list of members, esp. in expressions -- 2) better cope with invalid code, have at least a partial AST -- 3) investigate whether expanding the visitor lookup to a -- (albeit huge) switch() in KDev-PG-Qt gives a significant performance gain -- I have the gut feeling that the current lookup takes unnecessary much time -- ------------------------------------------------------------ -- Forward declaration in phpast.h ------------------------------------------------------------ [: #include namespace KDevelop { class DUContext; } :] ------------------------------------------------------------ -- Additional includes for the parser ------------------------------------------------------------ %parser_declaration_header "parser/tokenstream.h" %parser_declaration_header "QtCore/QString" %parser_declaration_header "language/duchain/problem.h" %parser_declaration_header "parser/phplexer.h" %parser_bits_header "parserdebug.h" ------------------------------------------------------------ -- Export macro to use the parser in a shared lib ------------------------------------------------------------ %export_macro "KDEVPHPPARSER_EXPORT" %export_macro_header "parserexport.h" ------------------------------------------------------------ -- Enumeration types for additional AST members, -- in the global "Php" namespace ------------------------------------------------------------ %namespace [: class Lexer; enum ModifierFlags { ModifierPrivate = 1, ModifierPublic = 1 << 1, ModifierProtected = 1 << 2, ModifierStatic = 1 << 3, ModifierFinal = 1 << 4, ModifierAbstract = 1 << 5 }; enum ClassModifier { NormalClass, AbstractClass, FinalClass }; enum ScalarTypes { ScalarTypeInt, ScalarTypeFloat, ScalarTypeString }; enum CastType { CastInt, CastDouble, CastString, CastArray, CastObject, CastBool, CastUnset }; enum OperationType { OperationPlus = 1, OperationMinus, OperationConcat, OperationMul, OperationDiv, OperationExp, OperationMod, OperationAnd, OperationOr, OperationXor, OperationSl, OperationSr, OperationSpaceship, }; :] ------------------------------------------------------------ -- Ast Node class members ------------------------------------------------------------ %ast_extra_members [: KDevelop::DUContext* ducontext; :] ------------------------------------------------------------ -- Parser class members ------------------------------------------------------------ %parserclass (public declaration) [: /** * Transform the raw input into tokens. * When this method returns, the parser's token stream has been filled * and any parse*() method can be called. */ void tokenize(const QString& contents, int initialState = Lexer::HtmlState); enum ProblemType { Error, Warning, Info, Todo }; KDevelop::ProblemPointer reportProblem( Parser::ProblemType type, const QString& message, int tokenOffset = -1 ); QList problems() { return m_problems; } QString tokenText(qint64 begin, qint64 end); void setDebug(bool debug); void setCurrentDocument(KDevelop::IndexedString url); void setTodoMarkers(const QStringList& markers); void extractTodosFromComment(const QString& comment, qint64 offset); enum InitialLexerState { HtmlState = 0, DefaultState = 1 }; :] %parserclass (private declaration) [: enum VarExpressionState { Normal, OnlyVariable, OnlyNewObject }; QString m_contents; bool m_debug; KDevelop::IndexedString m_currentDocument; QList m_problems; struct ParserState { VarExpressionState varExpressionState; bool varExpressionIsVariable; }; ParserState m_state; QRegularExpression m_todoMarkers; :] %parserclass (constructor) [: m_state.varExpressionState = Normal; m_state.varExpressionIsVariable = false; :] %token_stream TokenStream ;; ----------------------------------------------------------- -- List of defined tokens ----------------------------------------------------------- -- keywords: %token ABSTRACT ("abstract"), BREAK ("break"), CASE ("case"), CATCH ("catch"), CLASS ("class"), CONST ("const"), CONTINUE ("continue"), DEFAULT ("default"), DO ("do"), ELSE ("else"), EXTENDS ("extends"), FINAL ("final"), FOR ("for"), IF ("if"), IMPLEMENTS ("implements"), INSTANCEOF ("instanceof"), INTERFACE ("interface"), NEW ("new"), PRIVATE ("private"), PROTECTED ("protected"), PUBLIC ("public"), RETURN ("return"), STATIC ("static"), SWITCH ("switch"), THROW ("throw"), TRY ("try"), WHILE ("while"), ECHO ("echo"), PRINT ("print"), FINALLY ("finally"), CLONE ("clone"), EXIT ("exit"), ELSEIF ("elseif"), ENDIF ("endif"), ENDWHILE ("endwhile"), ENDFOR ("endfor"), FOREACH ("foreach"), ENDFOREACH ("endforeach"), DECLARE ("declare"), ENDDECLARE ("enddeclare"), AS ("as"), ENDSWITCH ("endswitch"), FUNCTION ("function"), USE ("use"), GLOBAL ("global"), VAR ("var "), UNSET ("unset"), ISSET ("isset"), EMPTY ("empty"), HALT_COMPILER ("halt compiler"), DOUBLE_ARROW ("=>"), LIST ("list"), ARRAY ("array"), CLASS_C ("__CLASS__"), METHOD_C ("__METHOD__"), FUNC_C ("__FUNCTION__"), LINE ("__LINE__"), FILE ("__FILE__"), COMMENT ("comment"), DOC_COMMENT ("doc comment"), PAAMAYIM_NEKUDOTAYIM ("::"), INCLUDE ("include"), INCLUDE_ONCE ("include_once"), EVAL ("eval"), REQUIRE ("require"), REQUIRE_ONCE ("require_once"), NAMESPACE ("namespace"), NAMESPACE_C("__NAMESPACE__"), USE("use"), GOTO ("goto"), TRAIT ("trait"), INSTEADOF ("insteadof"), CALLABLE ("callable"), VOID ("void"), DIR ("__DIR__"), TRAIT_C ("__TRAIT__"), YIELD ("yield"), YIELD_FROM("yield from") ;; -- casts: %token INT_CAST ("int cast"), DOUBLE_CAST ("double cast"), STRING_CAST ("string cast"), ARRAY_CAST ("array cast"), OBJECT_CAST ("object cast"), BOOL_CAST ("bool cast"), UNSET_CAST ("unset cast") ;; -- seperators: %token SEMICOLON (";"), DOUBLE_QUOTE ("\""), LBRACKET ("["), RBRACKET ("]"), LPAREN ("("), RPAREN (")"), LBRACE ("{"), RBRACE ("}"), COMMA (","), AT ("@"), CURLY_OPEN ("curly open"), -- { in "{$foo}"; not the same as LBRACE DOLLAR_OPEN_CURLY_BRACES ("${"), START_HEREDOC ("start heredoc"), END_HEREDOC ("end heredoc"), BACKTICK ("`"), BACKSLASH ("\\"), START_NOWDOC("start nowdoc"), END_NOWDOC("end nowdoc") ;; -- operators: %token IS_EQUAL ("=="), IS_NOT_EQUAL ("!="), IS_IDENTICAL ("==="), IS_NOT_IDENTICAL ("!=="), IS_SMALLER ("<"), IS_GREATER (">"), IS_SMALLER_OR_EQUAL ("<="), IS_GREATER_OR_EQUAL (">="), BOOLEAN_OR ("||"), BOOLEAN_AND ("&&"), ASSIGN ("="), EXP_ASSIGN("**="), PLUS_ASSIGN ("+="), MINUS_ASSIGN ("-="), MUL_ASSIGN ("*="), DIV_ASSIGN ("/="), CONCAT_ASSIGN (".="), MOD_ASSIGN ("%="), AND_ASSIGN ("&="), OR_ASSIGN ("|="), XOR_ASSIGN ("^="), SL_ASSIGN ("<<="), SR_ASSIGN (">>="), OBJECT_OPERATOR ("->"), PLUS ("+"), MINUS("-"), CONCAT("."), INC ("++"), DEC ("--"), BANG ("!"), QUESTION ("?"), COLON (":"), BIT_AND ("&"), BIT_OR("|"), BIT_XOR ("^"), SL ("<<"), SR (">>"), MUL("*"), DIV("/"), MOD ("%"), TILDE ("~"), DOLLAR ("$"), EXP ("**"), ELLIPSIS ("..."), NULL_COALESCE ("??"), SPACESHIP ("<=>"), LOGICAL_OR ("logical or"), LOGICAL_AND ("logical and"), LOGICAL_XOR ("logical xor") ;; -- literals and identifiers: %token INLINE_HTML ("inline html"), WHITESPACE ("whitespace"), CONSTANT_ENCAPSED_STRING ("constant encapsed string"), VARIABLE ("variable"), ENCAPSED_AND_WHITESPACE ("encapsed and whitespace"), DNUMBER ("double number"), LNUMBER ("long number"), NUM_STRING ("num string"), STRING ("string"), STRING_VARNAME ("string varname") ;; -- when in "${varname}" -- open/close tags %token OPEN_TAG (""), OPEN_TAG_WITH_ECHO (" start ;; namespaceDeclaration=namespaceDeclarationStatement | statement=topStatement -> outerTopStatement ;; -- first/first conflict for FUNCTION (?[: (LA(1).kind == Token_FUNCTION && ((LA(2).kind == Token_BIT_AND && LA(3).kind == Token_LPAREN) || LA(2).kind == Token_LPAREN)) || LA(1).kind != Token_FUNCTION :] statement=statement ) | functionDeclaration=functionDeclarationStatement | classDeclaration=classDeclarationStatement | traitDeclaration=traitDeclarationStatement | interfaceDeclaration=interfaceDeclarationStatement | HALT_COMPILER LPAREN RPAREN SEMICOLON -- Lexer stops allready -> topStatement ;; [: bool reported = false; while ( true ) { :] try/recover(#statements=topStatement)* [: if (yytoken != Token_RBRACE && yytoken != Token_EOF && yytoken != Token_CLOSE_TAG && yytoken != Token_ELSEIF && yytoken != Token_ELSE && yytoken != Token_ENDIF && yytoken != Token_ENDFOREACH && yytoken != Token_ENDFOR && yytoken != Token_ENDWHILE && yytoken != Token_ENDSWITCH && yytoken != Token_ENDDECLARE && yytoken != Token_CASE && yytoken != Token_DEFAULT) { if (!reported) { qint64 index = tokenStream->index() - 1; Token &token = tokenStream->at(index); QString tokenValue = token.kind != 0 ? tokenText(token.begin, token.end) : QStringLiteral("EOF"); reportProblem(Error, QStringLiteral("Unexpected token \"%1\".").arg(tokenValue)); reported = true; } yylex(); } else { break; } } :] -> innerStatementList ;; --Operator Precedence, from PHP Manual --left or --left xor --left and --right print --right = += -= *= /= .= %= &= |= ^= <<= >>= assignment --left ? : ternary --right ?? comparison --left || logical --left && logical --left | bitwise --left ^ bitwise --left & bitwise and references --non-associative == != === !== <=> comparison --non-associative < <= > >= comparison --left << >> bitwise --left + - . arithmetic and string --left * / % arithmetic --non-associative ! ~ - (int) (float) (string) (array) (object) @ types --non-associative ++ -- increment/decrement --left [ array() --non-associative new new expression=logicalOrExpression -> expr ;; #expression=logicalXorExpression @ LOGICAL_OR -> logicalOrExpression ;; #expression=logicalAndExpression @ LOGICAL_XOR -> logicalXorExpression ;; #expression=printExpression @ LOGICAL_AND -> logicalAndExpression ;; (print=PRINT | 0) expression=assignmentExpression -> printExpression ;; -- leftside must me a variable, we check afterwards if it was a variable and -- if not we report an error 0 --needed for line below [: m_state.varExpressionIsVariable = false; :] --reset flag expression=conditionalExpression ( assignmentExpressionEqual=assignmentExpressionEqual | ( ( PLUS_ASSIGN [: (*yynode)->operation = OperationPlus; :] | MINUS_ASSIGN [: (*yynode)->operation = OperationMinus; :] | MUL_ASSIGN [: (*yynode)->operation = OperationMul; :] | EXP_ASSIGN [: (*yynode)->operation = OperationExp; :] | DIV_ASSIGN [: (*yynode)->operation = OperationDiv; :] | CONCAT_ASSIGN [: (*yynode)->operation = OperationConcat; :] | MOD_ASSIGN [: (*yynode)->operation = OperationMod; :] | AND_ASSIGN [: (*yynode)->operation = OperationAnd; :] | OR_ASSIGN [: (*yynode)->operation = OperationOr; :] | XOR_ASSIGN [: (*yynode)->operation = OperationXor; :] | SL_ASSIGN [: (*yynode)->operation = OperationSl; :] | SR_ASSIGN [: (*yynode)->operation = OperationSr; :] ) assignmentExpressionCheckIfVariable assignmentExpression=assignmentExpression) | 0) -> assignmentExpression [ member variable operation: OperationType; ];; --=& is special: -- $foo =& $var; is allowed but not $foo =& 'static'; -- $foo =& new bar(); is allowed too but deprecated and reports a warning --we set a flag (varExpressionState) with that var_expression accepts only valid parts --this is done in such a strage way because we need the full expression to allow --things like $foo =& $bar || e(); ASSIGN assignmentExpressionCheckIfVariable --as in assignmentExpression (BIT_AND [: if (yytoken == Token_NEW) { reportProblem(Warning, QStringLiteral("=& new foo() is deprecated"), -2); m_state.varExpressionState = OnlyNewObject; } else { m_state.varExpressionState = OnlyVariable; }:] | 0) assignmentExpression=assignmentExpression [: m_state.varExpressionState = Normal; :] -> assignmentExpressionEqual ;; -- check if var_expression was a variable, if not report an error -- varExpressionIsVariable is set in var_expression 0 --to allow cpp-code [: if (!m_state.varExpressionIsVariable) { reportProblem(Error, QStringLiteral("Left side is not a variable")); return false; } :] -> assignmentExpressionCheckIfVariable ;; expression=nullCoalesceExpression ( QUESTION (ifExpression=expr|0) COLON elseExpression=conditionalExpression | 0 ) -> conditionalExpression ;; #expression=booleanOrExpression @ NULL_COALESCE -> nullCoalesceExpression ;; #expression=booleanAndExpression @ BOOLEAN_OR -> booleanOrExpression ;; #expression=bitOrExpression @ BOOLEAN_AND -> booleanAndExpression ;; #expression=bitXorExpression @ BIT_OR -> bitOrExpression ;; #expression=bitAndExpression @ BIT_XOR -> bitXorExpression ;; #expression=equalityExpression @ BIT_AND -> bitAndExpression ;; expression=relationalExpression (additionalExpression=equalityExpressionRest | 0) -> equalityExpression ;; ( IS_EQUAL | IS_NOT_EQUAL | IS_IDENTICAL | IS_NOT_IDENTICAL | SPACESHIP [: (*yynode)->operation = OperationSpaceship; :] ) expression=relationalExpression -> equalityExpressionRest [ member variable operation: OperationType; ];; expression=shiftExpression ( additionalExpression=relationalExpressionRest --instanceof as in java.g (correct??) | INSTANCEOF instanceofType=classNameReference | 0 ) -> relationalExpression ;; ( IS_SMALLER | IS_GREATER | IS_SMALLER_OR_EQUAL | IS_GREATER_OR_EQUAL ) expression=shiftExpression -> relationalExpressionRest ;; expression=additiveExpression (#additionalExpression=shiftExpressionRest)* -> shiftExpression ;; ( SL | SR ) expression=additiveExpression -> shiftExpressionRest ;; expression=multiplicativeExpression (#additionalExpression=additiveExpressionRest)* -> additiveExpression ;; ( PLUS [: (*yynode)->operation = OperationPlus; :] | MINUS [: (*yynode)->operation = OperationMinus; :] | CONCAT [: (*yynode)->operation = OperationConcat; :] ) expression=multiplicativeExpression -> additiveExpressionRest [ member variable operation: OperationType; ];; expression=unaryExpression (#additionalExpression=multiplicativeExpressionRest)* -> multiplicativeExpression ;; ( MUL [: (*yynode)->operation = OperationMul; :] | DIV [: (*yynode)->operation = OperationDiv; :] | EXP [: (*yynode)->operation = OperationExp; :] | MOD [: (*yynode)->operation = OperationMod; :] ) expression=unaryExpression -> multiplicativeExpressionRest [ member variable operation: OperationType; ];; ( MINUS unaryExpression=unaryExpression | PLUS unaryExpression=unaryExpression | BANG unaryExpression=unaryExpression | TILDE unaryExpression=unaryExpression | INT_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastInt; :] | DOUBLE_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastDouble; :] | STRING_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastString; :] | ARRAY_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastArray; :] | OBJECT_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastObject; :] | BOOL_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastBool; :] | UNSET_CAST unaryExpression=unaryExpression [: (*yynode)->castType = CastUnset; :] | AT unaryExpression=unaryExpression | LIST LPAREN assignmentList=assignmentList RPAREN ASSIGN unaryExpression=unaryExpression | EXIT (LPAREN (expression=expr | 0) RPAREN | 0) | EVAL LPAREN expression=expr RPAREN | INCLUDE includeExpression=unaryExpression | INCLUDE_ONCE includeExpression=unaryExpression | REQUIRE includeExpression=unaryExpression | REQUIRE_ONCE includeExpression=unaryExpression | unaryExpressionNotPlusminus=unaryExpressionNotPlusminus ) -> unaryExpression [ member variable castType: CastType; ];; (#prefixOperator=postprefixOperator)* varExpression=varExpression (#postfixOperator=postprefixOperator)* -> unaryExpressionNotPlusminus ;; op=INC | op=DEC -> postprefixOperator ;; -- 10 first follow conflicts because we go back up the chain (affects both print and yield) (print=PRINT+) printExpression=assignmentExpression | isGenerator=YIELD (generatorExpression=printExpression ( DOUBLE_ARROW generatorValueExpr=printExpression | 0 ) | 0) | isGenerator=YIELD_FROM generatorExpression=printExpression --first/first conflict - no problem because of ifs | ?[: m_state.varExpressionState == OnlyVariable :] 0 [: m_state.varExpressionState = Normal; :] variable=variable | ?[: m_state.varExpressionState == OnlyNewObject :] 0 [: m_state.varExpressionState = Normal; :] newObject=varExpressionNewObject | varExpressionNormal=varExpressionNormal | varExpressionArray=varExpressionArray arrayIndex=arrayIndexSpecifier* -> varExpression ;; (?[: LA(1).kind == Token_LPAREN && LA(2).kind == Token_FUNCTION && LA(3).kind == Token_LPAREN :] iife=iifeSyntax ) | LPAREN try/rollback (newObject=varExpressionNewObject RPAREN (#variableProperties=instantiationAccess*)) catch (expression=expr RPAREN) | BACKTICK encapsList=encapsList BACKTICK --try/rollback resolves conflict scalar vs. staticMember (foo::bar vs. foo::$bar) --varExpressionIsVariable flag is needed for assignmentExpression | try/rollback (variable=variable [: m_state.varExpressionIsVariable = true; :]) catch (scalar=scalar) | ISSET LPAREN (#issetVariable=variable @ COMMA) RPAREN | EMPTY LPAREN emptyExpression=expr RPAREN | newObject=varExpressionNewObject | CLONE cloneCar=varExpressionNormal | closure=closure -> varExpressionNormal ;; ARRAY LPAREN (#arrayValues=arrayPairValue -- break because array(1,) is allowed (solves FIRST/FOLLOW conflict) @ (COMMA [: if (yytoken == Token_RPAREN) { break; } :] ) | 0) RPAREN | LBRACKET (#arrayValues=arrayPairValue -- break because [1,] is allowed (solves FIRST/FOLLOW conflict) @ (COMMA [: if (yytoken == Token_RBRACKET) { break; } :] ) | 0) RBRACKET -> varExpressionArray ;; -- https://wiki.php.net/rfc/closures FUNCTION (isRef=BIT_AND|0) LPAREN parameters=parameterList RPAREN ( USE LPAREN lexicalVars=lexicalVarList RPAREN | 0) ( COLON returnType=returnType | 0) LBRACE try/recover(functionBody=innerStatementList) RBRACE -> closure ;; LPAREN try/rollback (closure=closure RPAREN LPAREN parameterList=functionCallParameterList RPAREN) catch (expression=expr RPAREN) -> iifeSyntax ;; (#lexicalVars=lexicalVar @ COMMA) | 0 [: reportProblem(Error, QStringLiteral("Use list of closure must not be empty.")); :] -> lexicalVarList ;; (isRef=BIT_AND | 0) variable=variableIdentifier -> lexicalVar ;; NEW classNameReference=classNameReference ctor=ctorArguments -> varExpressionNewObject ;; LPAREN parameterList=functionCallParameterList RPAREN | 0 -> ctorArguments ;; #parameters=functionCallParameterListElement @ COMMA | 0 -> functionCallParameterList ;; (BIT_AND variable=variable) | (isVariadic=ELLIPSIS | 0) expr=expr -> functionCallParameterListElement ;; #element=assignmentListElement @COMMA -> assignmentList ;; variable=variable | LIST LPAREN assignmentList=assignmentList RPAREN | 0 -> assignmentListElement ;; expr=expr (DOUBLE_ARROW (exprValue=expr | BIT_AND varValue=variable) | 0) | BIT_AND variable=variable -> arrayPairValue ;; var=baseVariableWithFunctionCalls (#variableProperties=variableObjectProperty*) -> variable ;; OBJECT_OPERATOR | PAAMAYIM_NEKUDOTAYIM -> objectOperator ;; ( ?[: LA(1).kind == Token_DOLLAR:] LBRACE variable=variable RBRACE | objectProperty=objectProperty ) (isFunctionCall=LPAREN parameterList=functionCallParameterList RPAREN arrayIndex=arrayIndexSpecifier* | 0) -> variableProperty ;; objectOperator variableProperty=variableProperty -> variableObjectProperty ;; OBJECT_OPERATOR variableProperty=variableProperty -> instantiationAccess ;; --Conflict -- foo::$bar[0] (=baseVariable-staticMember) --vs.foo::$bar[0](); (=static function call) try/rollback (functionCall=functionCall arrayIndex=arrayIndexSpecifier*) catch (baseVariable=baseVariable) -> baseVariableWithFunctionCalls ;; LBRACKET (expr=expr | 0) RBRACKET -> arrayIndexSpecifier ;; LBRACKET (expr=expr) RBRACKET -> stringIndexSpecifier ;; stringFunctionNameOrClass=namespacedIdentifier ( LPAREN stringParameterList=functionCallParameterList RPAREN | PAAMAYIM_NEKUDOTAYIM ( stringFunctionName=semiReservedIdentifier LPAREN stringParameterList=functionCallParameterList RPAREN | varFunctionName=variableWithoutObjects LPAREN stringParameterList=functionCallParameterList RPAREN | LBRACE (expr=expr) RBRACE LPAREN stringParameterList=functionCallParameterList RPAREN ) ) | varFunctionName=variableWithoutObjects LPAREN varParameterList=functionCallParameterList RPAREN -> functionCall ;; var=compoundVariableWithSimpleIndirectReference #offsetItems=dimListItem* | staticMember=staticMember -> baseVariable ;; variable=variableIdentifier | DOLLAR LBRACE expr=expr RBRACE -> compoundVariable ;; ( DOLLAR ( DOLLAR+ | 0 ) ( indirectVariable=variableIdentifier | LBRACE expr=expr RBRACE ) | variable=variableIdentifier ) -> compoundVariableWithSimpleIndirectReference ;; className=namespacedIdentifier staticProperty=staticProperty -> staticMember ;; LBRACE try/recover(statements=innerStatementList) RBRACE | IF LPAREN ifExpr=expr RPAREN ( COLON statements=innerStatementList newElseifList newElseSingle ENDIF semicolonOrCloseTag | ifStatement=statement elseifList=elseifList elseSingle=elseSingle ) | WHILE LPAREN whileExpr=expr RPAREN whileStatement=whileStatement | FOR LPAREN forExpr1=forExpr SEMICOLON forExpr2=forExpr SEMICOLON forExpr3=forExpr RPAREN forStatement=forStatement | SWITCH LPAREN swtichExpr=expr RPAREN switchCaseList=switchCaseList | FOREACH LPAREN ( -- allow $var as &$i and not expr() as &$i try/rollback(foreachVar=variable AS foreachVarAsVar=foreachVariable) catch(foreachExpr=expr AS foreachExprAsVar=variable)) (DOUBLE_ARROW foreachVariable=foreachVariable | 0) RPAREN foreachStatement=foreachStatement | DECLARE LPAREN declareItem=declareItem @ COMMA RPAREN declareStatement | SEMICOLON -- empty statement | TRY LBRACE try/recover(statements=innerStatementList) RBRACE #catches=catchItem* (FINALLY LBRACE finallyBody=innerStatementList RBRACE | 0) | UNSET LPAREN #unsetVariables=variable @ COMMA RPAREN semicolonOrCloseTag -- fix first/follow with goto target | ( ?[: LA(1).kind != Token_STRING || LA(2).kind != Token_COLON :] expr=expr semicolonOrCloseTag ) | DO doStatement=statement WHILE LPAREN whileExpr=expr RPAREN semicolonOrCloseTag | BREAK (breakExpr=expr | 0) semicolonOrCloseTag | CONTINUE (continueExpr=expr | 0) semicolonOrCloseTag | RETURN (returnExpr=expr | 0) semicolonOrCloseTag | GLOBAL #globalVars=globalVar @ COMMA semicolonOrCloseTag | STATIC #staticVars=staticVar @ COMMA semicolonOrCloseTag | ECHO #echoExprs=expr @ COMMA semicolonOrCloseTag | THROW throwExpr=expr semicolonOrCloseTag -- throws error in zend parser, so ignored | USE use_filename semicolonOrCloseTag | CLOSE_TAG | OPEN_TAG | OPEN_TAG_WITH_ECHO expr=expr semicolonOrCloseTag | INLINE_HTML | CONST #consts=constantDeclaration @ COMMA SEMICOLON | USE #useNamespace=useNamespace @ COMMA SEMICOLON | GOTO gotoLabel=STRING SEMICOLON | gotoTarget=STRING COLON -> statement ;; identifier=namespacedIdentifier (AS aliasIdentifier=identifier | 0) -> useNamespace ;; identifier=identifier ASSIGN scalar=expr -> constantDeclaration ;; identifier=semiReservedIdentifier ASSIGN scalar=expr -> classConstantDeclaration ;; SEMICOLON | CLOSE_TAG -> semicolonOrCloseTag ;; LBRACE (SEMICOLON | 0) try/recover(caseList=caseList) RBRACE | COLON (SEMICOLON | 0) caseList=caseList ENDSWITCH semicolonOrCloseTag -> switchCaseList ;; #caseItems=case_item* -> caseList ;; CASE expr=expr (COLON | SEMICOLON) statements=innerStatementList | def=DEFAULT (COLON | SEMICOLON) statements=innerStatementList -> case_item ;; CATCH LPAREN catchClass=namespacedIdentifier var=variableIdentifier RPAREN LBRACE try/recover(statements=innerStatementList) RBRACE -> catchItem ;; statement=statement | COLON statements=innerStatementList ENDDECLARE semicolonOrCloseTag -> declareStatement ;; STRING ASSIGN scalar=staticScalar -> declareItem ;; (BIT_AND | 0) variable=variable -> foreachVariable ;; statement=statement | COLON statements=innerStatementList ENDFOREACH semicolonOrCloseTag -> foreachStatement ;; var=variableIdentifier (ASSIGN value=staticScalar | 0) -> staticVar ;; var=variableIdentifier | DOLLAR (dollarVar=variable | LBRACE expr=expr RBRACE) -> globalVar ;; #exprs=expr @ COMMA | 0 -> forExpr ;; statement=statement | COLON statements=innerStatementList ENDFOR semicolonOrCloseTag -> forStatement ;; statement=statement | COLON statements=innerStatementList ENDWHILE semicolonOrCloseTag -> whileStatement ;; --first/follow conflict; todo check if this is a problem #elseifListItem=elseifListItem* -> elseifList ;; ELSEIF LPAREN expr=expr RPAREN statement=statement -> elseifListItem ;; ELSE statement=statement | 0 -> elseSingle ;; #newElseifListItem=newelseifListItem* -> newElseifList ;; ELSEIF LPAREN expr=expr RPAREN COLON statements=innerStatementList -> newelseifListItem ;; ELSE COLON statements=innerStatementList | 0 -> newElseSingle ;; className=className (staticProperty=staticProperty #properties=classProperty* | 0) | baseVariable=variableWithoutObjects #properties=classProperty* -> classNameReference ;; identifier=namespacedIdentifier | staticIdentifier = STATIC -> className ;; PAAMAYIM_NEKUDOTAYIM staticProperty=compoundVariableWithSimpleIndirectReference #offsetItems=dimListItem* -> staticProperty ;; (staticProperty=staticProperty | OBJECT_OPERATOR property=objectProperty) -> classProperty ;; objectDimList=objectDimList | variableWithoutObjects=variableWithoutObjects -> objectProperty ;; variableName=variableName #offsetItems=dimListItem* -> objectDimList ;; variable=compoundVariableWithSimpleIndirectReference #offsetItems=dimListItem* -> variableWithoutObjects ;; arrayIndex=arrayIndexSpecifier | LBRACE expr=expr RBRACE -> dimListItem ;; name=identifier | LBRACE expr=expr RBRACE -> variableName ;; commonScalar=commonScalar | constantOrClassConst=constantOrClassConst #offsetItem=dimListItem* | varname=STRING_VARNAME | DOUBLE_QUOTE encapsList=encapsList DOUBLE_QUOTE stringIndex=stringIndexSpecifier* | START_HEREDOC encapsList=encapsList END_HEREDOC -> scalar ;; constant=namespacedIdentifier ( PAAMAYIM_NEKUDOTAYIM classConstant=classConstant | 0 ) -> constantOrClassConst ;; semiReservedIdentifier -> classConstant ;; #encaps=encaps* -> encapsList ;; var=encapsVar | value=ENCAPSED_AND_WHITESPACE -> encaps ;; -- first/first conflict resolved by LA(2) --(expr allows STRING_VARNAME too - but without [expr]) DOLLAR_OPEN_CURLY_BRACES ( ?[: LA(2).kind == Token_LBRACKET:] STRING_VARNAME arrayIndex=arrayIndexSpecifier RBRACE | expr=expr RBRACE ) | variable=variableIdentifier (OBJECT_OPERATOR propertyIdentifier=identifier | LBRACKET offset=encapsVarOffset RBRACKET | 0) | CURLY_OPEN expr=expr RBRACE -> encapsVar ;; STRING | NUM_STRING | variableIdentifier -> encapsVarOffset ;; LNUMBER [: (*yynode)->scalarType = ScalarTypeInt; :] | DNUMBER [: (*yynode)->scalarType = ScalarTypeFloat; :] | string=CONSTANT_ENCAPSED_STRING [: (*yynode)->scalarType = ScalarTypeString; :] stringIndex=stringIndexSpecifier* | LINE [: (*yynode)->scalarType = ScalarTypeInt; :] | DIR [: (*yynode)->scalarType = ScalarTypeString; :] | FILE [: (*yynode)->scalarType = ScalarTypeString; :] | CLASS_C [: (*yynode)->scalarType = ScalarTypeString; :] | TRAIT_C [: (*yynode)->scalarType = ScalarTypeString; :] | METHOD_C [: (*yynode)->scalarType = ScalarTypeString; :] | FUNC_C [: (*yynode)->scalarType = ScalarTypeString; :] | NAMESPACE_C [: (*yynode)->scalarType = ScalarTypeString; :] | START_NOWDOC STRING END_NOWDOC [: (*yynode)->scalarType = ScalarTypeString; :] -> commonScalar [ member variable scalarType: ScalarTypes; ] ;; FUNCTION (BIT_AND | 0) functionName=identifier LPAREN parameters=parameterList RPAREN (COLON returnType=returnType | 0) LBRACE try/recover(functionBody=innerStatementList) RBRACE -> functionDeclarationStatement ;; (#parameters=parameter @ COMMA) | 0 -> parameterList ;; (parameterType=parameterType | 0) (isRef=BIT_AND | 0) (isVariadic=ELLIPSIS | 0) variable=variableIdentifier (ASSIGN defaultValue=expr | 0) -> parameter ;; genericType=namespacedIdentifier | arrayType=ARRAY | callableType=CALLABLE -> genericTypeHint ;; (isNullable=QUESTION | 0) typehint=genericTypeHint -> parameterType ;; (isNullable=QUESTION | 0) ( typehint=genericTypeHint | voidType=VOID ) -> returnType ;; value=commonScalar | constantOrClassConst=constantOrClassConst | PLUS plusValue=staticScalar | MINUS minusValue=staticScalar | array=ARRAY LPAREN (#arrayValues=staticArrayPairValue -- break because array(1,) is allowed @ (COMMA [: if (yytoken == Token_RPAREN) { break; } :] ) | 0) RPAREN | array=LBRACKET (#arrayValues=staticArrayPairValue -- break because [1,] is allowed @ (COMMA [: if (yytoken == Token_RBRACKET) { break; } :] ) | 0) RBRACKET -> staticScalar ;; #val1=staticScalar (DOUBLE_ARROW #val2=staticScalar | 0) -> staticArrayPairValue ;; (isGlobal=BACKSLASH | 0) #namespaceName=identifier+ @ BACKSLASH -> namespacedIdentifier ;; string=STRING -> identifier ;; INCLUDE | INCLUDE_ONCE | EVAL | REQUIRE | REQUIRE_ONCE | LOGICAL_OR | LOGICAL_XOR | LOGICAL_AND | INSTANCEOF | NEW | CLONE | EXIT | IF | ELSEIF | ELSE | ENDIF | ECHO | DO | WHILE | ENDWHILE | FOR | ENDFOR | FOREACH | ENDFOREACH | DECLARE | ENDDECLARE | AS | TRY | CATCH | FINALLY | THROW | USE | INSTEADOF | GLOBAL | VAR | UNSET | ISSET | EMPTY | CONTINUE | GOTO | FUNCTION | CONST | RETURN | PRINT | YIELD | LIST | SWITCH | ENDSWITCH | CASE | DEFAULT | BREAK | ARRAY | CALLABLE | EXTENDS | IMPLEMENTS | NAMESPACE | TRAIT | INTERFACE | CLASS | CLASS_C | TRAIT_C | FUNC_C | METHOD_C | LINE | FILE | DIR | NAMESPACE_C -> reservedNonModifiers ;; reservedNonModifiers | STATIC | ABSTRACT | FINAL | PRIVATE | PROTECTED | PUBLIC -> semiReserved ;; identifier [: qint64 index = tokenStream->index() - 2; (*yynode)->string = index; :] | semiReserved [: qint64 index = tokenStream->index() - 2; (*yynode)->string = index; :] -> semiReservedIdentifier [ member variable string: qint64; ] ;; identifier [: qint64 index = tokenStream->index() - 2; (*yynode)->string = index; :] | reservedNonModifiers [: qint64 index = tokenStream->index() - 2; (*yynode)->string = index; :] -> reservedNonModifierIdentifier [ member variable string: qint64; ] ;; variable=VARIABLE -> variableIdentifier ;; NAMESPACE #namespaceName=identifier* @ BACKSLASH ( -- the semicolon case needs at least one namespace identifier, the {...} case not... SEMICOLON [: if (!(*yynode)->namespaceNameSequence) { reportProblem(Error, QStringLiteral("Missing namespace identifier."), -2); } :] | LBRACE try/recover(body=innerStatementList) RBRACE ) -> namespaceDeclarationStatement ;; INTERFACE interfaceName=identifier (EXTENDS extends=classImplements | 0) LBRACE try/recover(body=classBody) RBRACE -> interfaceDeclarationStatement ;; TRAIT traitName=identifier LBRACE body=classBody RBRACE -> traitDeclarationStatement ;; modifier=optionalClassModifier CLASS className=identifier (EXTENDS extends=classExtends | 0) (IMPLEMENTS implements=classImplements | 0) LBRACE body=classBody RBRACE -> classDeclarationStatement ;; identifier=namespacedIdentifier -> classExtends ;; #implements=namespacedIdentifier @ COMMA -> classImplements ;; -- error recovery, to understand it you probably have to look at the generated code ;-) [: bool reported = false; while ( true ) { :] try/recover(#classStatements=classStatement)* [: if (yytoken != Token_RBRACE && yytoken != Token_EOF && yytoken != Token_CLOSE_TAG) { if (!reported) { reportProblem(Error, QStringLiteral("Unexpected token in class context.")); reported = true; } yylex(); } else { break; } } :] RBRACE [: rewind(tokenStream->index() - 2); :] -> classBody ;; - CONST #consts=classConstantDeclaration @ COMMA SEMICOLON - | VAR variable=classVariableDeclaration SEMICOLON + VAR variable=classVariableDeclaration SEMICOLON | modifiers=optionalModifiers ( variable=classVariableDeclaration SEMICOLON | FUNCTION (BIT_AND | 0) methodName=semiReservedIdentifier LPAREN parameters=parameterList RPAREN ( COLON returnType=returnType | 0) methodBody=methodBody + | CONST #consts=classConstantDeclaration @ COMMA SEMICOLON ) | USE #traits=namespacedIdentifier @ COMMA (imports=traitAliasDeclaration|SEMICOLON) -> classStatement ;; LBRACE #statements=traitAliasStatement @ (SEMICOLON [: if (yytoken == Token_RBRACE) { break; } :]) RBRACE -> traitAliasDeclaration ;; importIdentifier=traitAliasIdentifier -- first/first conflict resolved by LA(2) -- We can either have a single token (modifier or identifier), or a combination ( AS (?[: LA(2).kind == Token_SEMICOLON :] (modifiers=traitVisibilityModifiers | aliasNonModifierIdentifier=reservedNonModifierIdentifier) | modifiers=traitVisibilityModifiers aliasIdentifier=semiReservedIdentifier ) | INSTEADOF #conflictIdentifier=namespacedIdentifier @ COMMA ) -> traitAliasStatement ;; identifier=namespacedIdentifier PAAMAYIM_NEKUDOTAYIM methodIdentifier=semiReservedIdentifier -> traitAliasIdentifier ;; SEMICOLON -- abstract method | LBRACE try/recover(statements=innerStatementList) RBRACE -> methodBody ;; #vars=classVariable @ COMMA -> classVariableDeclaration ;; variable=variableIdentifier (ASSIGN value=staticScalar | 0) -> classVariable ;; PUBLIC [: (*yynode)->modifiers |= ModifierPublic; :] | PROTECTED [: (*yynode)->modifiers |= ModifierProtected; :] | PRIVATE [: (*yynode)->modifiers |= ModifierPrivate; :] | STATIC [: (*yynode)->modifiers |= ModifierStatic; :] | ABSTRACT [: (*yynode)->modifiers |= ModifierAbstract; :] | FINAL [: (*yynode)->modifiers |= ModifierFinal; :] -> traitVisibilityModifiers[ member variable modifiers: unsigned int; ] ;; ( PUBLIC [: (*yynode)->modifiers |= ModifierPublic; :] | PROTECTED [: (*yynode)->modifiers |= ModifierProtected; :] | PRIVATE [: (*yynode)->modifiers |= ModifierPrivate; :] | STATIC [: (*yynode)->modifiers |= ModifierStatic; :] | ABSTRACT [: (*yynode)->modifiers |= ModifierAbstract; :] | FINAL [: (*yynode)->modifiers |= ModifierFinal; :] | 0 )* -> optionalModifiers[ member variable modifiers: unsigned int; ] ;; ( ABSTRACT [: (*yynode)->modifier = AbstractClass; :] | FINAL [: (*yynode)->modifier = FinalClass; :] | 0 ) -> optionalClassModifier[ member variable modifier: ClassModifier; ] ;; ----------------------------------------------------------------- -- Code segments copied to the implementation (.cpp) file. -- If existent, kdevelop-pg's current syntax requires this block -- to occur at the end of the file. ----------------------------------------------------------------- [: #include #include namespace Php { void Parser::tokenize(const QString& contents, int initialState) { m_contents = contents; Lexer lexer(tokenStream, contents, initialState); int kind = Parser::Token_EOF; int lastDocCommentBegin; int lastDocCommentEnd; do { lastDocCommentBegin = 0; lastDocCommentEnd = 0; kind = lexer.nextTokenKind(); while (kind == Parser::Token_WHITESPACE || kind == Parser::Token_COMMENT || kind == Parser::Token_DOC_COMMENT) { if (kind == Parser::Token_COMMENT || kind == Parser::Token_DOC_COMMENT) { extractTodosFromComment(tokenText(lexer.tokenBegin(), lexer.tokenEnd()), lexer.tokenBegin()); } if (kind == Parser::Token_DOC_COMMENT) { lastDocCommentBegin = lexer.tokenBegin(); lastDocCommentEnd = lexer.tokenEnd(); } kind = lexer.nextTokenKind(); } if ( !kind ) // when the lexer returns 0, the end of file is reached { kind = Parser::Token_EOF; } Parser::Token &t = tokenStream->push(); t.begin = lexer.tokenBegin(); t.end = lexer.tokenEnd(); t.kind = kind; t.docCommentBegin = lastDocCommentBegin; t.docCommentEnd = lastDocCommentEnd; //if ( m_debug ) qDebug() << kind << tokenText(t.begin,t.end) << t.begin << t.end; } while ( kind != Parser::Token_EOF ); yylex(); // produce the look ahead token } void Parser::extractTodosFromComment(const QString& comment, qint64 startPosition) { auto i = m_todoMarkers.globalMatch(comment); while (i.hasNext()) { auto match = i.next(); auto p = reportProblem(Todo, match.captured(1), 0); if (!p) { continue; } qint64 line = 0; qint64 column = 0; tokenStream->locationTable()->positionAt(startPosition, &line, &column); auto location = p->finalLocation(); location.setStart(KTextEditor::Cursor(line, column + match.capturedStart(1))); location.setEnd(KTextEditor::Cursor(line, column + match.capturedEnd(1))); p->setFinalLocation(location); }; } void Parser::setTodoMarkers(const QStringList& markers) { QString pattern = QStringLiteral("^(?:[/\\*\\s]*)(.*(?:"); bool first = true; foreach(const QString& marker, markers) { if (!first) { pattern += '|'; } pattern += QRegularExpression::escape(marker); first = false; } pattern += QStringLiteral(").*?)(?:[/\\*\\s]*)$"); m_todoMarkers.setPatternOptions(QRegularExpression::MultilineOption); m_todoMarkers.setPattern(pattern); } QString Parser::tokenText(qint64 begin, qint64 end) { return m_contents.mid(begin,end-begin+1); } KDevelop::ProblemPointer Parser::reportProblem( Parser::ProblemType type, const QString& message, int offset ) { qint64 sLine; qint64 sCol; qint64 index = tokenStream->index() + offset; if (index >= tokenStream->size()) { return {}; } tokenStream->startPosition(index, &sLine, &sCol); qint64 eLine; qint64 eCol; tokenStream->endPosition(index, &eLine, &eCol); auto p = KDevelop::ProblemPointer(new KDevelop::Problem()); p->setSource(KDevelop::IProblem::Parser); switch ( type ) { case Error: p->setSeverity(KDevelop::IProblem::Error); break; case Warning: p->setSeverity(KDevelop::IProblem::Warning); break; case Info: p->setSeverity(KDevelop::IProblem::Hint); break; case Todo: p->setSeverity(KDevelop::IProblem::Hint); p->setSource(KDevelop::IProblem::ToDo); break; } p->setDescription(message); KTextEditor::Range range(sLine, sCol, eLine, eCol + 1); p->setFinalLocation(KDevelop::DocumentRange(m_currentDocument, range)); m_problems << p; return p; } // custom error recovery void Parser::expectedToken(int /*expected*/, qint64 /*where*/, const QString& name) { reportProblem( Parser::Error, QStringLiteral("Expected token \"%1\"").arg(name)); } void Parser::expectedSymbol(int /*expectedSymbol*/, const QString& name) { qint64 line; qint64 col; qint64 index = tokenStream->index()-1; Token &token = tokenStream->at(index); qCDebug(PARSER) << "token starts at:" << token.begin; qCDebug(PARSER) << "index is:" << index; tokenStream->startPosition(index, &line, &col); QString tokenValue = tokenText(token.begin, token.end); qint64 eLine; qint64 eCol; tokenStream->endPosition(index, &eLine, &eCol); reportProblem( Parser::Error, QStringLiteral("Expected symbol \"%1\" (current token: \"%2\" [%3] at %4:%5 - %6:%7)") .arg(name, token.kind != 0 ? tokenValue : QStringLiteral("EOF")) .arg(token.kind) .arg(line) .arg(col) .arg(eLine) .arg(eCol)); } void Parser::setDebug( bool debug ) { m_debug = debug; } void Parser::setCurrentDocument(KDevelop::IndexedString url) { m_currentDocument = url; } Parser::ParserState *Parser::copyCurrentState() { ParserState *state = new ParserState(); state->varExpressionState = m_state.varExpressionState; state->varExpressionIsVariable = m_state.varExpressionIsVariable; return state; } void Parser::restoreState( Parser::ParserState* state) { m_state.varExpressionState = state->varExpressionState; m_state.varExpressionIsVariable = state->varExpressionIsVariable; } } // end of namespace Php :] -- kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on; auto-insert-doxygen on; mode KDevelop-PG[-Qt]