diff --git a/duchain/expressionvisitor.cpp b/duchain/expressionvisitor.cpp index 4f48000..c3a3557 100644 --- a/duchain/expressionvisitor.cpp +++ b/duchain/expressionvisitor.cpp @@ -1,1018 +1,1050 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "expressionvisitor.h" #include "parsesession.h" #include "editorintegrator.h" #include "helper.h" #include "declarations/variabledeclaration.h" #include "declarations/classdeclaration.h" #include #include #include #include #include #include #include #include #include "duchaindebug.h" #define ifDebug(x) using namespace KDevelop; namespace Php { ExpressionVisitor::ExpressionVisitor(EditorIntegrator* editor) : m_editor(editor), m_createProblems(false), m_offset(CursorInRevision::invalid()), m_currentContext(nullptr), m_isAssignmentExpressionEqual(false), m_inDefine(false) { } DeclarationPointer ExpressionVisitor::processVariable(VariableIdentifierAst* variable) { Q_ASSERT(m_currentContext); CursorInRevision position = m_editor->findPosition(variable->variable, EditorIntegrator::BackEdge); if ( m_offset.isValid() ) { position.line += m_offset.line; position.column += m_offset.column; } DeclarationPointer ret; Identifier identifier = identifierForNode(variable).last(); ifDebug(qCDebug(DUCHAIN) << "processing variable" << identifier.toString() << position.castToSimpleCursor();) DUChainReadLocker lock; if (identifier.nameEquals(Identifier(QStringLiteral("this")))) { if (m_currentContext->parentContext() && m_currentContext->parentContext()->type() == DUContext::Class && m_currentContext->parentContext()->owner()) { ret = m_currentContext->parentContext()->owner(); } } else { //DontSearchInParent-flag because (1) in Php global variables aren't available in function //context and (2) a function body consists of a single context (so this is no problem) ret = findVariableDeclaration(m_currentContext, identifier, position, DUContext::DontSearchInParent); } if (!ret && m_currentContext->type() == DUContext::Namespace) { ret = findVariableDeclaration(m_currentContext, identifier, position, DUContext::NoSearchFlags); } if (!ret) { //look for a function argument ///TODO: why doesn't m_currentContext->findDeclarations() work? /// evaluate if the stuff below is fast enough (faster?) than findDeclarations() ///see r1028306 foreach(const DUContext::Import &import, m_currentContext->importedParentContexts() ) { if ( !import.isDirect() || import.position > position ) { continue; } DUContext* ctx = import.context(m_currentContext->topContext()); if ( ctx->type() == DUContext::Function ) { QList args = ctx->findLocalDeclarations(identifier); if ( !args.isEmpty() ) { ret = args.first(); break; } } } } if (!ret) { //look for a superglobal variable foreach(Declaration* dec, m_currentContext->topContext()->findDeclarations(identifier, position)) { VariableDeclaration* varDec = dynamic_cast(dec); if (varDec && varDec->isSuperglobal()) { ret = dec; break; } } } lock.unlock(); if ( !m_isAssignmentExpressionEqual || identifier.nameEquals( Identifier(QStringLiteral("this")) ) // might be something like $s = $s . $s; in which case we have to add a use for the first $s || (ret && ret->range().end < position) ) { // also don't report uses for the place of declaration if (!ret || ret->range().end != position) { usingDeclaration(variable, ret); } } ifDebug(qCDebug(DUCHAIN) << "found declaration:" << (ret ? ret->toString() : QString("no declaration found"));) return ret; } void ExpressionVisitor::visitNode(AstNode *node) { if (node && node->ducontext) { m_currentContext = node->ducontext; } Q_ASSERT(m_currentContext); DefaultVisitor::visitNode(node); } void ExpressionVisitor::visitAssignmentExpression(AssignmentExpressionAst *node) { if (node->assignmentExpressionEqual) { m_isAssignmentExpressionEqual = true; } visitNode(node->expression); m_isAssignmentExpressionEqual = false; visitNode(node->assignmentExpressionEqual); visitNode(node->assignmentExpression); if (node->operation == OperationPlus || node->operation == OperationMinus || node->operation == OperationMul || node->operation == OperationDiv || node->operation == OperationExp) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else if (node->operation == OperationConcat) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } void ExpressionVisitor::visitArrayIndexSpecifier(ArrayIndexSpecifierAst* node) { DefaultVisitor::visitArrayIndexSpecifier(node); // it's an array item but we don't support it really, so just assign type mixed and be done m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); } void ExpressionVisitor::visitCompoundVariableWithSimpleIndirectReference(CompoundVariableWithSimpleIndirectReferenceAst *node) { if (node->variable) { m_result.setDeclaration(processVariable(node->variable)); } DefaultVisitor::visitCompoundVariableWithSimpleIndirectReference(node); } void ExpressionVisitor::visitVariable(VariableAst* node) { if ( node->variablePropertiesSequence && node->variablePropertiesSequence->front() && node->variablePropertiesSequence->front()->element && node->variablePropertiesSequence->front()->element->variableProperty && node->variablePropertiesSequence->front()->element->variableProperty->objectProperty ) { // make sure we mark $foo as a use in $foo->... bool isAssignmentExpressionEqual = m_isAssignmentExpressionEqual; m_isAssignmentExpressionEqual = false; DefaultVisitor::visitVariable(node); m_isAssignmentExpressionEqual = isAssignmentExpressionEqual; } else { DefaultVisitor::visitVariable(node); } } void ExpressionVisitor::visitVarExpression(VarExpressionAst *node) { DefaultVisitor::visitVarExpression(node); if (node->isGenerator != -1) { DeclarationPointer generatorDecl = findDeclarationImport(ClassDeclarationType, QualifiedIdentifier("generator")); if (generatorDecl) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); if (hasCurrentClosureReturnType()) { FunctionType::Ptr closureType = currentClosureReturnType().cast(); closureType->setReturnType(generatorDecl->abstractType()); } } } } void ExpressionVisitor::visitVarExpressionNewObject(VarExpressionNewObjectAst *node) { DefaultVisitor::visitVarExpressionNewObject(node); if (node->classNameReference->className && node->classNameReference->className->staticIdentifier != -1) { static const QualifiedIdentifier id(QStringLiteral("static")); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->classNameReference->className, dec); m_result.setDeclaration(dec); } else if (node->classNameReference->className && node->classNameReference->className->identifier) { const QualifiedIdentifier id = identifierForNamespace(node->classNameReference->className->identifier, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->classNameReference->className->identifier->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->classNameReference->className->identifier, id); m_result.setDeclaration(dec); } } void ExpressionVisitor::visitVarExpressionArray(VarExpressionArrayAst *node) { DefaultVisitor::visitVarExpressionArray(node); m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeArray))); } void ExpressionVisitor::visitClosure(ClosureAst* node) { auto* closureType = new FunctionType; closureType->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); openClosureReturnType(AbstractType::Ptr(closureType)); if (node->functionBody) { visitInnerStatementList(node->functionBody); } if (node->returnType && node->returnType->typehint && isClassTypehint(node->returnType->typehint, m_editor)) { NamespacedIdentifierAst* objectType = node->returnType->typehint->genericType; QualifiedIdentifier id = identifierForNamespace(objectType, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(objectType->namespaceNameSequence->back()->element, dec); buildNamespaceUses(objectType, id); } //Override found type with return typehint or phpdoc return typehint AbstractType::Ptr type = returnType(node->returnType, {}, m_editor, m_currentContext); if (type) { closureType->setReturnType(type); } if (node->parameters->parametersSequence) { const KDevPG::ListNode< ParameterAst* >* it = node->parameters->parametersSequence->front(); forever { AbstractType::Ptr type = parameterType(it->element, {}, m_editor, m_currentContext); closureType->addArgument(type); if (it->element->parameterType && it->element->parameterType->typehint && isClassTypehint(it->element->parameterType->typehint, m_editor)) { NamespacedIdentifierAst* objectType = it->element->parameterType->typehint->genericType; QualifiedIdentifier id = identifierForNamespace(objectType, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(objectType->namespaceNameSequence->back()->element, dec); buildNamespaceUses(objectType, id); } if (it->element->defaultValue) { visitExpr(it->element->defaultValue); } if ( it->hasNext() ) { it = it->next; } else { break; } } } if (node->lexicalVars && node->lexicalVars->lexicalVarsSequence) { const KDevPG::ListNode< LexicalVarAst* >* it = node->lexicalVars->lexicalVarsSequence->front(); DUChainWriteLocker lock; forever { DeclarationPointer found; foreach(Declaration* dec, m_currentContext->findDeclarations(identifierForNode(it->element->variable))) { if (dec->kind() == Declaration::Instance) { found = dec; break; } } usingDeclaration(it->element->variable, found); if ( it->hasNext() ) { it = it->next; } else { break; } } } m_result.setType(AbstractType::Ptr(closureType)); closeClosureReturnType(); } void ExpressionVisitor::visitFunctionCallParameterList( FunctionCallParameterListAst* node ) { QList decs = m_result.allDeclarations(); AbstractType::Ptr type = m_result.type(); DefaultVisitor::visitFunctionCallParameterList( node ); m_result.setDeclarations(decs); m_result.setType(type); } void ExpressionVisitor::visitFunctionCallParameterListElement(FunctionCallParameterListElementAst* node) { DefaultVisitor::visitFunctionCallParameterListElement(node); if (m_inDefine) m_inDefine = false; //reset after first parameter passed, the second argument can be a class name } void ExpressionVisitor::visitFunctionCall(FunctionCallAst* node) { if (node->stringFunctionNameOrClass && !node->stringFunctionName && !node->varFunctionName) { QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); if (id.toString(RemoveExplicitlyGlobalPrefix) == QLatin1String("define") && node->stringParameterList && node->stringParameterList->parametersSequence && node->stringParameterList->parametersSequence->count() > 0) { //in a define() call the first argument is the constant name. we don't want to look for a class name to build uses m_inDefine = true; } } DefaultVisitor::visitFunctionCall(node); m_inDefine = false; if (node->stringFunctionNameOrClass) { if (node->stringFunctionName) { //static function call foo::bar() DUContext* context = findClassContext(node->stringFunctionNameOrClass); if (context) { DUChainReadLocker lock(DUChain::lock()); QualifiedIdentifier methodName(stringForNode(node->stringFunctionName).toLower()); m_result.setDeclarations(context->findDeclarations(methodName)); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node->stringFunctionName, m_result.allDeclarations().last()); FunctionType::Ptr function = m_result.allDeclarations().last()->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } } else { m_result.setHadUnresolvedIdentifiers(true); usingDeclaration(node->stringFunctionName, DeclarationPointer()); m_result.setType(AbstractType::Ptr()); } } else if (node->varFunctionName) { //static function call foo::$bar() } else if (node->expr) { //static function call foo::{expr}() const QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->stringFunctionNameOrClass->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->stringFunctionNameOrClass, id); m_result.setDeclaration(dec); } else { //global function call foo(); QualifiedIdentifier id = identifierForNamespace(node->stringFunctionNameOrClass, m_editor); DeclarationPointer dec = findDeclarationImport(FunctionDeclarationType, id); if (!dec) { id.setExplicitlyGlobal(true); dec = findDeclarationImport(FunctionDeclarationType, id); } ifDebug(qCDebug(DUCHAIN) << "function call of" << (dec ? dec->toString() : QString("function not found"));) m_result.setDeclaration(dec); usingDeclaration(node->stringFunctionNameOrClass->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->stringFunctionNameOrClass, id); if (dec) { FunctionType::Ptr function = dec->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } else { m_result.setHadUnresolvedIdentifiers(true); } } } } ///TODO: DUContext pointer? DUContext* ExpressionVisitor::findClassContext(IdentifierAst* className) { DUContext* context = nullptr; DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, className); usingDeclaration(className, declaration); if (declaration) { DUChainReadLocker lock(DUChain::lock()); context = declaration->internalContext(); if (!context && m_currentContext->parentContext() && m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //className is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } return context; } ///TODO: DUContext pointer? DUContext* ExpressionVisitor::findClassContext(NamespacedIdentifierAst* className) { DUContext* context = nullptr; const QualifiedIdentifier id = identifierForNamespace(className, m_editor); DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(className->namespaceNameSequence->back()->element, declaration); buildNamespaceUses(className, id); if (declaration) { DUChainReadLocker lock(DUChain::lock()); context = declaration->internalContext(); if (!context && m_currentContext->parentContext() && m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //className is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } return context; } void ExpressionVisitor::visitConstantOrClassConst(ConstantOrClassConstAst *node) { DefaultVisitor::visitConstantOrClassConst(node); if (node->classConstant) { //class constant Foo::BAR DUContext* context = findClassContext(node->constant); if (context) { DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(context->findDeclarations(Identifier(m_editor->parseSession()->symbol(node->classConstant)))); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node->classConstant, m_result.allDeclarations().last()); } else { usingDeclaration(node->classConstant, DeclarationPointer()); } if (!stringForNode(node->classConstant).compare(QLatin1String("class"), Qt::CaseInsensitive)) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } else { m_result.setType(AbstractType::Ptr()); } } else { QString str(stringForNode(node->constant).toLower()); if (str == QLatin1String("true") || str == QLatin1String("false")) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } else if (str == QLatin1String("null")) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeNull))); } else { //constant (created with declare('foo', 'bar')) or const Foo = 1; QualifiedIdentifier id = identifierForNamespace(node->constant, m_editor, true); DeclarationPointer declaration = findDeclarationImport(ConstantDeclarationType, id); if (!declaration) { id.setExplicitlyGlobal(true); declaration = findDeclarationImport(ConstantDeclarationType, id); } if (!declaration) { ///TODO: is this really wanted? //it could also be a global function call, without () declaration = findDeclarationImport(FunctionDeclarationType, id); } m_result.setDeclaration(declaration); usingDeclaration(node->constant->namespaceNameSequence->back()->element, declaration); buildNamespaceUses(node->constant, id); } } } void ExpressionVisitor::visitScalar(ScalarAst *node) { DefaultVisitor::visitScalar(node); if (node->commonScalar) { uint type = IntegralType::TypeVoid; switch (node->commonScalar->scalarType) { case ScalarTypeInt: type = IntegralType::TypeInt; break; case ScalarTypeFloat: type = IntegralType::TypeFloat; break; case ScalarTypeString: type = IntegralType::TypeString; break; } m_result.setType(AbstractType::Ptr(new IntegralType(type))); } else if (node->varname != -1) { //STRING_VARNAME-Token, probably the type of varname should be used m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } else if (node->encapsList) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } if (!m_inDefine && node->commonScalar && node->commonScalar->scalarType == ScalarTypeString) { QString str = m_editor->parseSession()->symbol(node->commonScalar); QRegExp exp("^['\"]([A-Za-z0-9_]+)['\"]$"); if (exp.exactMatch(str)) { //that *could* be a class name QualifiedIdentifier id(exp.cap(1).toLower()); DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, id); if (declaration) { usingDeclaration(node->commonScalar, declaration); } else { m_result.setHadUnresolvedIdentifiers(true); } } } } void ExpressionVisitor::visitStaticScalar(StaticScalarAst *node) { if (node->ducontext) { m_currentContext = node->ducontext; } Q_ASSERT(m_currentContext); DefaultVisitor::visitStaticScalar(node); uint type = IntegralType::TypeVoid; if (node->value) { switch (node->value->scalarType) { case ScalarTypeInt: type = IntegralType::TypeInt; break; case ScalarTypeFloat: type = IntegralType::TypeFloat; break; case ScalarTypeString: type = IntegralType::TypeString; break; } } else if (node->plusValue || node->minusValue) { type = IntegralType::TypeInt; } else if (node->array != -1) { type = IntegralType::TypeArray; } if (type != IntegralType::TypeVoid) { m_result.setType(AbstractType::Ptr(new IntegralType(type))); } } void ExpressionVisitor::visitEncapsVar(EncapsVarAst *node) { DefaultVisitor::visitEncapsVar(node); if (node->variable) { // handle $foo DeclarationPointer dec = processVariable(node->variable); if (dec && node->propertyIdentifier) { // handle property in $foo->bar DeclarationPointer foundDec; DUChainReadLocker lock(DUChain::lock()); if ( StructureType::Ptr structType = dec->type() ) { if ( ClassDeclaration* cdec = dynamic_cast(structType->declaration(m_currentContext->topContext())) ) { ///TODO: share code with visitVariableProperty DUContext* ctx = cdec->internalContext(); if (!ctx && m_currentContext->parentContext()) { if (m_currentContext->parentContext()->localScopeIdentifier() == cdec->qualifiedIdentifier()) { //class is currentClass (internalContext is not yet set) ctx = m_currentContext->parentContext(); } } if (ctx) { foreach( Declaration* pdec, ctx->findDeclarations(identifierForNode(node->propertyIdentifier)) ) { if ( !pdec->isFunctionDeclaration() ) { foundDec = pdec; break; } } } } } lock.unlock(); usingDeclaration(node->propertyIdentifier, foundDec); } } } void ExpressionVisitor::visitVariableProperty(VariablePropertyAst *node) { ifDebug(qCDebug(DUCHAIN) << "node:" << m_editor->parseSession()->symbol(node) << (node->isFunctionCall != -1 ? QString("is function call") : QString("is no function call"));) if (node->objectProperty && node->objectProperty->objectDimList) { //handle $foo->bar() and $foo->baz, $foo is m_result.type() AbstractType::Ptr type = m_result.type(); //If the variable type is unsure, try to see if it contains a StructureType. If so, use that // (since the other types do not allow accessing properties) if (type && type.cast()) { UnsureType::Ptr unsureType = type.cast(); int numStructureType = 0; StructureType::Ptr structureType; for (unsigned int i = 0; itypesSize(); ++i) { StructureType::Ptr subType = unsureType->types()[i].type(); if (subType) { structureType = subType; ++numStructureType; } } //Only use the found structureType if there's exactly *one* such type if (numStructureType == 1) { Q_ASSERT(structureType); type = AbstractType::Ptr(structureType); } } if (type && StructureType::Ptr::dynamicCast(type)) { DUChainReadLocker lock(DUChain::lock()); Declaration* declaration = StructureType::Ptr::staticCast(type)->declaration(m_currentContext->topContext()); if (declaration) { ifDebug(qCDebug(DUCHAIN) << "parent:" << declaration->toString();) DUContext* context = declaration->internalContext(); if (!context && m_currentContext->parentContext()) { if (m_currentContext->parentContext()->localScopeIdentifier() == declaration->qualifiedIdentifier()) { //class is currentClass (internalContext is not yet set) context = m_currentContext->parentContext(); } } if (context) { QualifiedIdentifier propertyId; if ( node->isFunctionCall != -1 ) { propertyId = QualifiedIdentifier(stringForNode(node->objectProperty->objectDimList->variableName->name).toLower()); } else { propertyId = identifierForNode(node->objectProperty->objectDimList->variableName->name); } ifDebug(qCDebug(DUCHAIN) << "property id:" << propertyId.toString();) QList decs; foreach ( Declaration* dec, context->findDeclarations(propertyId) ) { if ( node->isFunctionCall != -1 ) { if ( dec->isFunctionDeclaration() ) { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found:" << dec->toString();) } } else { if ( !dec->isFunctionDeclaration() ) { ClassMemberDeclaration *classDec = dynamic_cast(dec); if (classDec && classDec->accessPolicy() == Declaration::Private) { if (declaration == dec->context()->owner()) { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found private:" << dec->toString();) } } else { decs << dec; ifDebug(qCDebug(DUCHAIN) << "found:" << dec->toString();) } } } } m_result.setDeclarations(decs); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { if ( !m_isAssignmentExpressionEqual ) { usingDeclaration(node->objectProperty->objectDimList->variableName, m_result.allDeclarations().last()); } if (node->isFunctionCall != -1) { FunctionType::Ptr function = m_result.allDeclarations().last()->type(); if (function) { m_result.setType(function->returnType()); } else { m_result.setType(AbstractType::Ptr()); } } } else { if ( !m_isAssignmentExpressionEqual ) { usingDeclaration(node->objectProperty->objectDimList->variableName, DeclarationPointer()); } m_result.setType(AbstractType::Ptr()); } } else { m_result.setType(AbstractType::Ptr()); } } else { m_result.setType(AbstractType::Ptr()); } } } DefaultVisitor::visitVariableProperty(node); } void ExpressionVisitor::visitStaticMember(StaticMemberAst* node) { //don't call DefaultVisitor::visitStaticMember(node); //because we would end up in visitCompoundVariableWithSimpleIndirectReference - if (node->staticProperty->staticProperty->variable->variable) { - DUContext* context = findClassContext(node->className); - if (context) { - useDeclaration(node->staticProperty->staticProperty->variable, context); - } else { - usingDeclaration(node->className, DeclarationPointer()); + if (node->staticProperty && node->staticProperty->staticProperty) { + if (node->staticProperty->staticProperty->variable) { + DUContext* context = findClassContext(node->className); + if (context) { + useDeclaration(node->staticProperty->staticProperty->variable, context); + } else { + usingDeclaration(node->className, DeclarationPointer()); + m_result.setType(AbstractType::Ptr()); + } + } else if (node->staticProperty->staticProperty->expr) { + const QualifiedIdentifier id = identifierForNamespace(node->className, m_editor); + DeclarationPointer declaration = findDeclarationImport(ClassDeclarationType, id); + usingDeclaration(node->className->namespaceNameSequence->back()->element, declaration); + buildNamespaceUses(node->className, id); + + visitExpr(node->staticProperty->staticProperty->expr); + m_result.setType(AbstractType::Ptr()); } - if (node->staticProperty->offsetItemsSequence) { - const KDevPG::ListNode< DimListItemAst* >* it = node->staticProperty->offsetItemsSequence->front(); - do { - visitDimListItem(it->element); - } while(it->hasNext() && (it = it->next)); - } + } + + if (node->staticProperty && node->staticProperty->offsetItemsSequence) { + const KDevPG::ListNode< DimListItemAst* >* it = node->staticProperty->offsetItemsSequence->front(); + do { + visitDimListItem(it->element); + } while(it->hasNext() && (it = it->next)); } } void ExpressionVisitor::visitClassNameReference(ClassNameReferenceAst* node) { if (node->staticProperty) { DUContext* context = findClassContext(node->className->identifier); - if (context) { - useDeclaration(node->staticProperty->staticProperty->variable, context); + if (context && node->staticProperty && node->staticProperty->staticProperty) { + if (node->staticProperty->staticProperty->variable) { + // static properties (object::$property) + useDeclaration(node->staticProperty->staticProperty->variable, context); + } else if (node->staticProperty->staticProperty->expr) { + // variable static properties (object::${$property}) + visitExpr(node->staticProperty->staticProperty->expr); + usingDeclaration(node->className, DeclarationPointer()); + } } - if (node->staticProperty->offsetItemsSequence) { + if (node->staticProperty && node->staticProperty->offsetItemsSequence) { const KDevPG::ListNode< DimListItemAst* >* dim_it = node->staticProperty->offsetItemsSequence->front(); do { visitDimListItem(dim_it->element); } while(dim_it->hasNext() && (dim_it = dim_it->next)); } } if (node->baseVariable) { DefaultVisitor::visitVariableWithoutObjects(node->baseVariable); } if (node->propertiesSequence) { if (!m_result.allDeclarations().isEmpty()) { DUContext* context = nullptr; StructureType::Ptr type; Declaration *declaration = nullptr; const KDevPG::ListNode< ClassPropertyAst* >* it = node->propertiesSequence->front(); do { // first check for property names held in variables ($object->$property) if (it->element->property && it->element->property->variableWithoutObjects && it->element->property->variableWithoutObjects->variable->variable) { VariableIdentifierAst *varnode = it->element->property->variableWithoutObjects->variable->variable; useDeclaration(varnode, m_currentContext); + } else if (it->element->property && it->element->property->variableWithoutObjects + && it->element->property->variableWithoutObjects->variable->expr) { + // variable dynamic properties ($object->${$property}) + visitExpr(it->element->property->variableWithoutObjects->variable->expr); } else if (!m_result.allDeclarations().isEmpty()) { // handle array indices after normal/static properties ($object->property[$index] // $object::$property[$index]) if (it->element->property && it->element->property->objectDimList && it->element->property->objectDimList->offsetItemsSequence) { const KDevPG::ListNode< DimListItemAst* >* dim_it = it->element->property->objectDimList->offsetItemsSequence->front(); do { visitDimListItem(dim_it->element); } while(dim_it->hasNext() && (dim_it = dim_it->next)); } else if (it->element->staticProperty && it->element->staticProperty->offsetItemsSequence) { const KDevPG::ListNode< DimListItemAst* >* dim_it = it->element->staticProperty->offsetItemsSequence->front(); do { visitDimListItem(dim_it->element); } while(dim_it->hasNext() && (dim_it = dim_it->next)); } + // Handle dynamic static properties first, as they don't need a class context + if (it->element->staticProperty && it->element->staticProperty->staticProperty + && it->element->staticProperty->staticProperty->expr) { + // variable static properties ($object::${$property}) + visitExpr(it->element->staticProperty->staticProperty->expr); + usingDeclaration(it->element->staticProperty, DeclarationPointer()); + } + type = m_result.allDeclarations().last()->type(); if (!type) { context = nullptr; continue; } DUChainReadLocker lock(DUChain::lock()); declaration = type->declaration(m_currentContext->topContext()); lock.unlock(); if (!declaration) { context = nullptr; continue; } context = declaration->internalContext(); if (!context || context->type() != DUContext::Class) { context = nullptr; continue; } - if (it->element->staticProperty) { + if (it->element->staticProperty && it->element->staticProperty->staticProperty + && it->element->staticProperty->staticProperty->variable) { // static properties ($object::$property) VariableIdentifierAst *varnode = it->element->staticProperty->staticProperty->variable; useDeclaration(varnode, context); } else if (it->element->property && it->element->property->objectDimList && it->element->property->objectDimList->variableName->name) { // normal properties ($object->property) IdentifierAst *varidnode = it->element->property->objectDimList->variableName->name; useDeclaration(varidnode, context); } else { context = nullptr; } } } while(it->hasNext() && (it = it->next)); } } } void ExpressionVisitor::visitUnaryExpression(UnaryExpressionAst* node) { DefaultVisitor::visitUnaryExpression(node); if (node->castType) { uint type = 0; switch (node->castType) { case CastInt: type = IntegralType::TypeInt; break; case CastDouble: type = IntegralType::TypeFloat; break; case CastString: type = IntegralType::TypeString; break; case CastArray: type = IntegralType::TypeArray; break; case CastObject: { /// Qualified identifier for 'stdclass' static const QualifiedIdentifier stdclassQId(QStringLiteral("stdclass")); DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(m_currentContext->findDeclarations(stdclassQId)); break; } case CastBool: type = IntegralType::TypeBoolean; break; case CastUnset: //TODO break; } if (type) { m_result.setType(AbstractType::Ptr(new IntegralType(type))); } } } void ExpressionVisitor::visitAdditiveExpressionRest(AdditiveExpressionRestAst* node) { DefaultVisitor::visitAdditiveExpressionRest(node); if (node->operation == OperationPlus || node->operation == OperationMinus) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else if (node->operation == OperationConcat) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeString))); } } void ExpressionVisitor::visitRelationalExpression(RelationalExpressionAst *node) { DefaultVisitor::visitRelationalExpression(node); if (node->instanceofType && node->instanceofType->className && node->instanceofType->className->identifier) { const QualifiedIdentifier id = identifierForNamespace(node->instanceofType->className->identifier, m_editor); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, id); usingDeclaration(node->instanceofType->className->identifier->namespaceNameSequence->back()->element, dec); buildNamespaceUses(node->instanceofType->className->identifier, id); m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } } void ExpressionVisitor::visitRelationalExpressionRest(RelationalExpressionRestAst *node) { DefaultVisitor::visitRelationalExpressionRest(node); m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } void ExpressionVisitor::visitEqualityExpressionRest(EqualityExpressionRestAst *node) { DefaultVisitor::visitEqualityExpressionRest(node); if (node->operation && node->operation == OperationSpaceship) { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeInt))); } else { m_result.setType(AbstractType::Ptr(new IntegralType(IntegralType::TypeBoolean))); } } void ExpressionVisitor::visitStatement(StatementAst *node) { DefaultVisitor::visitStatement(node); if (node->returnExpr) { FunctionType::Ptr closureType = currentClosureReturnType().cast(); if (closureType) { closureType->setReturnType(m_result.type()); } } } QString ExpressionVisitor::stringForNode(AstNode* id) { if (!id) return QString(); return m_editor->parseSession()->symbol(id); } QualifiedIdentifier ExpressionVisitor::identifierForNode(IdentifierAst* id) { if (!id) return QualifiedIdentifier(); return QualifiedIdentifier(stringForNode(id)); } QString ExpressionVisitor::stringForNode(VariableIdentifierAst* id) { if (!id) return QString(); QString ret(m_editor->parseSession()->symbol(id->variable)); ret = ret.mid(1); //cut off $ return ret; } QualifiedIdentifier ExpressionVisitor::identifierForNode(VariableIdentifierAst* id) { if (!id) return QualifiedIdentifier(); return QualifiedIdentifier(stringForNode(id)); } void ExpressionVisitor::setCreateProblems(bool v) { m_createProblems = v; } void ExpressionVisitor::setOffset(const CursorInRevision& offset) { m_offset = offset; } void ExpressionVisitor::buildNamespaceUses(NamespacedIdentifierAst* namespaces, const QualifiedIdentifier& identifier) { QualifiedIdentifier curId; curId.setExplicitlyGlobal(identifier.explicitlyGlobal()); Q_ASSERT(identifier.count() == namespaces->namespaceNameSequence->count()); for ( int i = 0; i < identifier.count() - 1; ++i ) { curId.push(identifier.at(i)); AstNode* node = namespaces->namespaceNameSequence->at(i)->element; DeclarationPointer dec = findDeclarationImport(NamespaceDeclarationType, curId); usingDeclaration(node, dec); } } void ExpressionVisitor::useDeclaration(VariableIdentifierAst* node, DUContext* context) { DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(context->findDeclarations(identifierForNode(node))); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node, m_result.allDeclarations().last()); } else { usingDeclaration(node, DeclarationPointer()); } } void ExpressionVisitor::useDeclaration(IdentifierAst* node, DUContext* context) { DUChainReadLocker lock(DUChain::lock()); m_result.setDeclarations(context->findDeclarations(identifierForNode(node))); lock.unlock(); if (!m_result.allDeclarations().isEmpty()) { usingDeclaration(node, m_result.allDeclarations().last()); } else { usingDeclaration(node, DeclarationPointer()); } } DeclarationPointer ExpressionVisitor::findDeclarationImport(DeclarationType declarationType, IdentifierAst* node) { // methods and class names are case insensitive QualifiedIdentifier id; if ( declarationType == ClassDeclarationType || declarationType == FunctionDeclarationType ) { id = QualifiedIdentifier(stringForNode(node).toLower()); } else { id = identifierForNode(node); } return findDeclarationImport(declarationType, id); } DeclarationPointer ExpressionVisitor::findDeclarationImport(DeclarationType declarationType, VariableIdentifierAst* node) { return findDeclarationImport(declarationType, identifierForNode(node)); } DeclarationPointer ExpressionVisitor::findDeclarationImport( DeclarationType declarationType, const QualifiedIdentifier& identifier) { return findDeclarationImportHelper(m_currentContext, identifier, declarationType); } Declaration* ExpressionVisitor::findVariableDeclaration(DUContext* context, Identifier identifier, CursorInRevision position, DUContext::SearchFlag flag) { QList decls = context->findDeclarations(identifier, position, nullptr, flag); for (int i = decls.count() - 1; i >= 0; i--) { Declaration *dec = decls.at(i); if (dec->kind() == Declaration::Instance && dynamic_cast(dec)) { return dec; } } return nullptr; } } diff --git a/duchain/tests/uses.cpp b/duchain/tests/uses.cpp index d9e58a4..c176cf7 100644 --- a/duchain/tests/uses.cpp +++ b/duchain/tests/uses.cpp @@ -1,1589 +1,1701 @@ /* 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 "uses.h" #include #include #include #include #include "../declarations/classdeclaration.h" #include "../declarations/variabledeclaration.h" #include "../declarations/traitmethodaliasdeclaration.h" #include "../declarations/traitmemberaliasdeclaration.h" #include "../types/structuretype.h" using namespace KDevelop; QTEST_MAIN(Php::TestUses) namespace Php { void compareUses(Declaration* dec, QList ranges) { qDebug() << "comparing uses for" << dec->toString(); QCOMPARE(dec->uses().keys().count(), 1); QCOMPARE(dec->uses().values().count(), 1); QCOMPARE(dec->uses().values().first().count(), ranges.count()); for (int i = 0; i < ranges.count(); ++i) { qDebug() << dec->uses().values().first().at(i) << ranges.at(i); QCOMPARE(dec->uses().values().first().at(i), ranges.at(i)); } } void compareUses(Declaration* dec, RangeInRevision range) { QList r; r << range; compareUses(dec, r); } TestUses::TestUses() { } void TestUses::newObject() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 25, 0, 28)); QCOMPARE(top->localDeclarations().first()->uses().keys().first(), IndexedString(QUrl("file:///internal/usestest/newObject.php"))); } void TestUses::functionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(); compareUses(fun, RangeInRevision(0, 21, 0, 24)); QCOMPARE(fun->uses().keys().first(), IndexedString(QUrl("file:///internal/usestest/functionCall.php"))); } void TestUses::memberFunctionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo(); "); TopDUContext* top = parse(method, DumpNone, QUrl(QStringLiteral("file:///internal/usestest/memberFunctionCall.php"))); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* fun = top->childContexts().first()->localDeclarations().first(); compareUses(fun, RangeInRevision(0, 51, 0, 54)); QCOMPARE(fun->uses().keys().first(), IndexedString(QUrl("file:///internal/usestest/memberFunctionCall.php"))); } void TestUses::unsureMemberFunctionCall() { //First try with a single unsure structure type { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo();"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* fun = top->childContexts().first()->localDeclarations().first(); compareUses(fun, RangeInRevision(0, 85, 0, 88)); } //Now try with two unsure structure types { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo();"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* fun = top->childContexts().first()->localDeclarations().first(); QCOMPARE(fun->uses().keys().count(), 0); } } void TestUses::memberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; "); TopDUContext* top = parse(method, DumpNone, QUrl(QStringLiteral("file:///internal/usestest/memberVariable.php"))); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* var = top->childContexts().first()->localDeclarations().first(); compareUses(var, RangeInRevision(0, 46, 0, 49)); QCOMPARE(var->uses().keys().first(), IndexedString(QUrl("file:///internal/usestest/memberVariable.php"))); } void TestUses::implicitMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("y = 1; $x->y = 2; class A {}"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); Declaration* var = top->childContexts().first()->localDeclarations().first(); QList ranges; ranges << RangeInRevision(0, 21, 0, 22) << RangeInRevision(0, 32, 0, 33); compareUses(var, ranges); QVERIFY(var->range() == RangeInRevision(0, 21, 0, 22)); } void TestUses::variable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 42 - 3, 1, 44 - 3) << RangeInRevision(1, 46 - 3, 1, 48 - 3) << RangeInRevision(1, 59 - 3, 1, 61 - 3); compareUses(top->localDeclarations().at(1), ranges); } void TestUses::varInString() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" ranges; ranges << RangeInRevision(0, 13, 0, 15) << RangeInRevision(0, 17, 0, 19); compareUses(top->localDeclarations().at(0), ranges); } void TestUses::variableInNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); };"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 55, 1, 57) << RangeInRevision(1, 59, 1, 61) << RangeInRevision(1, 72, 1, 74); compareUses(top->localDeclarations().at(2), ranges); } void TestUses::globalVariableInNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); };"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 55, 1, 57) << RangeInRevision(1, 59, 1, 61) << RangeInRevision(1, 72, 1, 74); compareUses(top->localDeclarations().at(1), ranges); } void TestUses::variableInOtherNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("foo; foo($a); };"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(1, 73, 1, 75) << RangeInRevision(1, 77, 1, 79) << RangeInRevision(1, 90, 1, 92); compareUses(top->localDeclarations().at(2), ranges); } void TestUses::memberVarInString() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789 01234567890123 4567890123456789 QByteArray method("v; \"$a->v {$a->v}\"; "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(0, 43, 0, 45) << RangeInRevision(0, 51, 0, 53) << RangeInRevision(0, 58, 0, 60); compareUses(top->localDeclarations().at(1), ranges); ranges.clear(); ranges << RangeInRevision(0, 47, 0, 48) << RangeInRevision(0, 55, 0, 56) << RangeInRevision(0, 62, 0, 63); compareUses(top->childContexts().first()->localDeclarations().first(), ranges); } void TestUses::memberFunctionInString() { // 0 1 2 3 4 5 6 7 // 012345678901234567890123456789012345678901234567 890123456789 01234567890123456789 QByteArray method("foo()}\"; "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); //$a compareUses(top->localDeclarations().at(1), RangeInRevision(0, 50, 0, 52)); //foo compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 54, 0, 57)); } void TestUses::variableTypeChange() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" ranges; ranges << RangeInRevision(0, 25, 0, 27); ranges << RangeInRevision(0, 29, 0, 31); ranges << RangeInRevision(0, 37, 0, 39); ranges << RangeInRevision(0, 41, 0, 43); ranges << RangeInRevision(0, 51, 0, 53); compareUses(top->localDeclarations().at(1), ranges); } void TestUses::variableTypeChangeInFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" ranges; ranges << RangeInRevision(0, 28, 0, 30); ranges << RangeInRevision(0, 32, 0, 34); ranges << RangeInRevision(0, 38, 0, 40); ranges << RangeInRevision(0, 42, 0, 44); compareUses(top->childContexts().at(1)->localDeclarations().at(0), ranges); } void TestUses::classExtends() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 31, 0, 32)); } void TestUses::classImplements() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 38, 0, 39)); } void TestUses::classImplementsMultiple() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 54, 0, 55)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 57, 0, 58)); } void TestUses::interfaceExtends() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 39, 0, 40)); } void TestUses::interfaceExtendsMultiple() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0), RangeInRevision(0, 55, 0, 56)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 58, 0, 59)); } void TestUses::staticMemberFunctionCall() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 47, 0, 48)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 50, 0, 53)); } void TestUses::staticMemberVariable() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 43, 0, 44)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 46, 0, 50)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 52, 0, 56)); } +void TestUses::dynamicStaticMemberVariable() +{ + // 0 1 2 3 4 5 6 7 + // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 + QByteArray method("localDeclarations().at(0); + QCOMPARE(dec->identifier(), Identifier("a")); + compareUses(dec, QList() + << RangeInRevision(0, 47, 0, 48)); + + dec = top->localDeclarations().at(1); + QCOMPARE(dec->identifier(), Identifier("var")); + compareUses(dec, QList() + << RangeInRevision(0, 52, 0, 56)); +} + void TestUses::constant() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 28, 0, 29)); } void TestUses::classConstant() { { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 39, 0, 40)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 42, 0, 45)); } { // bug: https://bugs.kde.org/show_bug.cgi?id=241597 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); QVERIFY(dec->abstractType()->modifiers() & AbstractType::ConstModifier); QCOMPARE(dec->qualifiedIdentifier().toString(), QString("a::FOO")); compareUses(dec, QList() << RangeInRevision(1, 43, 1, 46) << RangeInRevision(2, 3, 2, 6) << RangeInRevision(3, 3, 3, 6)); } } void TestUses::classParent() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" range; range << RangeInRevision(0, 47, 0, 48); range << RangeInRevision(0, 66, 0, 72); compareUses(top->localDeclarations().first(), range); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 74, 0, 75)); } void TestUses::classSelf() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 28, 0, 32)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 34, 0, 35)); } void TestUses::classThis() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("x(); } } "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); compareUses(top->localDeclarations().first(), RangeInRevision(0, 28, 0, 33)); compareUses(top->childContexts().first()->localDeclarations().first(), RangeInRevision(0, 35, 0, 36)); } void TestUses::objectWithClassName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("k; Aa::j; Aa::$i;"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QList ranges; ranges << RangeInRevision(0, 66, 0, 66 + 2); ranges << RangeInRevision(0, 78, 0, 78 + 2); ranges << RangeInRevision(0, 85, 0, 85 + 2); compareUses(top->localDeclarations().first(), ranges); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 70, 0, 70 + 3)); } void TestUses::classAndConstWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 38, 0, 39)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 31, 0, 32)); compareUses(top->localDeclarations().at(2), RangeInRevision(0, 76, 0, 77)); compareUses(top->localDeclarations().at(3), RangeInRevision(0, 73, 0, 74)); } void TestUses::classAndFunctionWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 35, 0, 36)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 38, 0, 39)); } void TestUses::constAndVariableWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(), RangeInRevision(0, 30, 0, 32)); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 27, 0, 28)); } void TestUses::functionAndClassWithSameName() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); QCOMPARE(fnAsdf->uses().keys().count(), 0); compareUses(top->localDeclarations().at(1), RangeInRevision(0, 70, 0, 74)); } void TestUses::constantInClassMember() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(Identifier(QStringLiteral("TEST"))).first(); QList uses; uses << RangeInRevision(0, 41, 0, 45); uses << RangeInRevision(0, 63, 0, 67); uses << RangeInRevision(0, 73, 0, 77); compareUses(constant, uses); } void TestUses::useInAsignment() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().first(); compareUses(d, RangeInRevision(0, 16, 0, 18)); } void TestUses::foreachArray() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("$i) { var_dump($k, $i); } "); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // $a, $k, $i QCOMPARE(top->localDeclarations().size(), 3); // $a Declaration *d = top->localDeclarations().at(0); compareUses(d, RangeInRevision(0, 26, 0, 28)); // $k d = top->localDeclarations().at(1); compareUses(d, RangeInRevision(0, 51, 0, 53)); // $i d = top->localDeclarations().at(2); compareUses(d, RangeInRevision(0, 55, 0, 57)); } void TestUses::assignmentToMemberArray() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("y[$a] = true; } }"); TopDUContext* top = parse(method, DumpAll); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); // class x Declaration *x = top->localDeclarations().first(); QVERIFY(x); // $this compareUses(x, RangeInRevision(0, 50, 0, 55)); // var $y Declaration *y = x->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("y"))).first(); QVERIFY(y); // $this->y compareUses(y, RangeInRevision(0, 57, 0, 58)); // function z Declaration *z = x->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("z"))).first(); QVERIFY(z); // $a Declaration *a = z->logicalInternalContext(top)->findDeclarations(Identifier(QStringLiteral("a"))).first(); QVERIFY(a); compareUses(a, QList() // $b = $a << RangeInRevision(0, 46, 0, 48) // $this->y[$a] << RangeInRevision(0, 59, 0, 61) ); } void TestUses::staticArrayIndex() { // bug: https://bugs.kde.org/show_bug.cgi?id=241160 // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, RangeInRevision(0, 68, 0, 70)); Declaration* i = top->childContexts().first()->childContexts().first()->localDeclarations().first(); QCOMPARE(i->identifier().toString(), QString("i")); compareUses(i, RangeInRevision(0, 71, 0, 73)); } void TestUses::functionParamNewDeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().first()->localDeclarations().first(); QList ranges; ranges << RangeInRevision(0, 22, 0, 24); ranges << RangeInRevision(0, 26, 0, 28); compareUses(d, ranges); } void TestUses::catchClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("findDeclarations(QualifiedIdentifier(QStringLiteral("exception"))).first(); compareUses(d, RangeInRevision(0, 18, 0, 27)); } void TestUses::variableRedeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->findDeclarations(QualifiedIdentifier(QStringLiteral("s"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(0, 13, 0, 15) << RangeInRevision(0, 18, 0, 20) << RangeInRevision(0, 23, 0, 25) ); } void TestUses::caseInsensitiveFunction() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->findLocalDeclarations(Identifier(QStringLiteral("foobar"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(1, 0, 1, 6) << RangeInRevision(2, 0, 2, 6) << RangeInRevision(3, 0, 3, 6) ); } void TestUses::caseInsensitiveMethod() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("fOoBar();\n$a->FOOBAR();\n$a->foobar();\n" "asdf::barfoo();\nasdf::bArFoo();\nasdf::BARFOO();\n"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); { QList decs = top->childContexts().first()->findDeclarations(QualifiedIdentifier(QStringLiteral("foobar"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(1, 4, 1, 10) << RangeInRevision(2, 4, 2, 10) << RangeInRevision(3, 4, 3, 10) ); } { QList decs = top->childContexts().first()->findDeclarations(QualifiedIdentifier(QStringLiteral("barfoo"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(4, 6, 4, 12) << RangeInRevision(5, 6, 5, 12) << RangeInRevision(6, 6, 6, 12) ); } } void TestUses::caseInsensitiveClass() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->findLocalDeclarations(Identifier(QStringLiteral("asdf"))); QCOMPARE(decs.size(), 1); Declaration *d = decs.first(); compareUses(d, QList() << RangeInRevision(1, 4, 1, 8) << RangeInRevision(2, 4, 2, 8) << RangeInRevision(3, 4, 3, 8) ); } void TestUses::functionUseBeforeDeclaration() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->localDeclarations(); QCOMPARE(decs.size(), 1); QCOMPARE(decs.first()->range(), RangeInRevision(0, 20, 0, 24)); compareUses(decs.first(), RangeInRevision(0, 3, 0, 7)); } void TestUses::propertyAndMethodWithSameName() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("name1(); $a->name1;\n" "$a->name2; $a->name2();"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVector decs = top->childContexts().first()->localDeclarations(); QCOMPARE(decs.size(), 4); // method name1 QVERIFY(decs[0]->identifier().nameEquals(Identifier("name1"))); QVERIFY(decs[0]->isFunctionDeclaration()); compareUses(decs[0], RangeInRevision(2, 4, 2, 9)); // property name1 QVERIFY(decs[1]->identifier().nameEquals(Identifier("name1"))); QVERIFY(!decs[1]->isFunctionDeclaration()); compareUses(decs[1], RangeInRevision(2, 17, 2, 22)); // property name2 QVERIFY(decs[2]->identifier().nameEquals(Identifier("name2"))); QVERIFY(!decs[2]->isFunctionDeclaration()); compareUses(decs[2], RangeInRevision(3, 4, 3, 9)); // method name2 QVERIFY(decs[3]->identifier().nameEquals(Identifier("name2"))); QVERIFY(decs[3]->isFunctionDeclaration()); compareUses(decs[3], RangeInRevision(3, 15, 3, 20)); } void TestUses::nestedMethodCalls() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("a($b->b());"); TopDUContext* top = parse(method, DumpNone); DUChainReleaser releaseTop(top); DUChainWriteLocker lock(DUChain::lock()); QVector topDecs = top->localDeclarations(); QCOMPARE(topDecs.size(), 4); // class a QVERIFY(topDecs[0]->identifier().nameEquals(Identifier("a"))); QVERIFY(dynamic_cast(topDecs[0])); compareUses(topDecs[0], RangeInRevision(3, 9, 3, 10)); // class b QVERIFY(topDecs[1]->identifier().nameEquals(Identifier("b"))); QVERIFY(dynamic_cast(topDecs[1])); compareUses(topDecs[1], RangeInRevision(4, 9, 4, 10)); // $a QVERIFY(topDecs[2]->identifier().nameEquals(Identifier("a"))); QVERIFY(dynamic_cast(topDecs[2])); compareUses(topDecs[2], RangeInRevision(5, 0, 5, 2)); // $b QVERIFY(topDecs[3]->identifier().nameEquals(Identifier("b"))); QVERIFY(dynamic_cast(topDecs[3])); compareUses(topDecs[3], RangeInRevision(5, 6, 5, 8)); // function a Declaration* methodADec = topDecs[0]->internalContext()->localDeclarations().first(); QVERIFY(methodADec->isFunctionDeclaration()); compareUses(methodADec, RangeInRevision(5, 4, 5, 5)); // function b Declaration* methodBDec = topDecs[1]->internalContext()->localDeclarations().first(); QVERIFY(methodBDec->isFunctionDeclaration()); compareUses(methodBDec, RangeInRevision(5, 10, 5, 11)); } void TestUses::unset() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method(" decs = top->localDeclarations(); QCOMPARE(decs.size(), 1); QCOMPARE(decs.first()->range(), RangeInRevision(0, 3, 0, 5)); compareUses(decs.first(), RangeInRevision(0, 17, 0, 19)); } void TestUses::functionArguments() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("childContexts().size(), 2); QCOMPARE(top->childContexts().first()->type(), DUContext::Function); // $a Declaration *d = top->childContexts().at(0)->localDeclarations().at(0); compareUses(d, RangeInRevision(0, 27, 0, 29)); // $b d = top->childContexts().at(0)->localDeclarations().at(1); compareUses(d, RangeInRevision(0, 35, 0, 37)); } void TestUses::namespaces() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("findDeclarations(QualifiedIdentifier(QStringLiteral("foo"))).last(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, QList() << RangeInRevision(9, 1, 9, 4) << RangeInRevision(10, 1, 10, 4) << RangeInRevision(11, 1, 11, 4) << RangeInRevision(12, 5, 12, 8) << RangeInRevision(13, 15, 13, 18) << RangeInRevision(14, 17, 14, 20) << RangeInRevision(14, 45, 14, 48)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); QVERIFY(dec->internalContext()); compareUses(dec, QList() << RangeInRevision(9, 5, 9, 8) << RangeInRevision(10, 5, 10, 8) << RangeInRevision(11, 5, 11, 8) << RangeInRevision(12, 9, 12, 12) << RangeInRevision(13, 19, 13, 22) << RangeInRevision(14, 21, 14, 24) << RangeInRevision(14, 49, 14, 52)); QCOMPARE(dec->internalContext()->localDeclarations().size(), 4); foreach(Declaration* d, dec->internalContext()->localDeclarations()) { qDebug() << d->toString() << d->qualifiedIdentifier(); } dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::MyConst"))).first(); compareUses(dec, QList() << RangeInRevision(3, 5, 3, 12) << RangeInRevision(9, 9, 9, 16)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::myclass"))).first(); QVERIFY(dynamic_cast(dec)); compareUses(dec, QList() << RangeInRevision(10, 9, 10, 16) << RangeInRevision(12, 13, 12, 20) << RangeInRevision(13, 23, 13, 30) << RangeInRevision(14, 25, 14, 32) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::myinterface"))).first(); QVERIFY(dynamic_cast(dec)); compareUses(dec, RangeInRevision(14, 53, 14, 64) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::myclass::ClassConst"))).first(); compareUses(dec, RangeInRevision(10, 18, 10, 28)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::myfunc"))).first(); compareUses(dec, RangeInRevision(11, 9, 11, 15)); } void TestUses::useNamespace() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("findDeclarations(QualifiedIdentifier(QStringLiteral("foo"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, RangeInRevision(5, 4, 5, 7)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, RangeInRevision(5, 8, 5, 11)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong"))).first(); QCOMPARE(dec->kind(), Declaration::Namespace); compareUses(dec, RangeInRevision(5, 13, 5, 21)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("bar"))).first(); QCOMPARE(dec->kind(), Declaration::NamespaceAlias); compareUses(dec, QList() << RangeInRevision(7, 4, 7, 7) << RangeInRevision(7, 11, 7, 14) << RangeInRevision(7, 20, 7, 23) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("short"))).first(); QCOMPARE(dec->kind(), Declaration::NamespaceAlias); compareUses(dec, QList() << RangeInRevision(8, 4, 8, 9) << RangeInRevision(8, 13, 8, 18) << RangeInRevision(8, 24, 8, 29) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("baz::a"))).first(); compareUses(dec, QList() << RangeInRevision(6, 8, 6, 9) << RangeInRevision(9, 4, 9, 10)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::a"))).first(); compareUses(dec, RangeInRevision(7, 8, 7, 9)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::b"))).first(); compareUses(dec, RangeInRevision(7, 15, 7, 16)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("foo::bar::C"))).first(); compareUses(dec, RangeInRevision(7, 24, 7, 25)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong::a"))).first(); compareUses(dec, RangeInRevision(8, 10, 8, 11)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong::b"))).first(); compareUses(dec, RangeInRevision(8, 19, 8, 20)); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("verylong::C"))).first(); compareUses(dec, RangeInRevision(8, 30, 8, 31)); } void TestUses::lateStatic() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("childContexts().first()->localDeclarations().first(), RangeInRevision(0, 39, 0, 40)); } void TestUses::closures() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().count(), 4); Declaration* a = top->localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(1, 23, 1, 25) << RangeInRevision(1, 36, 1, 38)); Declaration* b = top->localDeclarations().at(1); QCOMPARE(b->identifier().toString(), QString("b")); QVERIFY(b->uses().isEmpty()); } void TestUses::closureTypehints() { TopDUContext* top = parse("localDeclarations().count(), 3); Declaration* a = top->localDeclarations().at(0); QCOMPARE(a->qualifiedIdentifier(), QualifiedIdentifier("a")); compareUses(a, QList() << RangeInRevision(0, 32, 0, 33) << RangeInRevision(0, 39, 0, 40)); } void TestUses::instanceof() { // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("localDeclarations().count(), 3); Declaration* a = top->localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(1, 9, 1, 10) << RangeInRevision(2, 19, 2, 20)); } void TestUses::instanceofClassProperty() { // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("foo->bar->foo;\n", DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(2, 9, 2, 10)); dec = top->localDeclarations().at(2); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr classType = dec->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(classType->equals(dec->abstractType().data())); compareUses(dec, QList() << RangeInRevision(2, 14, 2, 16) << RangeInRevision(2, 28, 2, 30)); dec = top->childContexts().at(0)->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->identifier(), Identifier("foo")); compareUses(dec, QList() << RangeInRevision(2, 32, 2, 35) << RangeInRevision(2, 42, 2, 45)); dec = top->childContexts().at(1)->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->identifier(), Identifier("bar")); compareUses(dec, QList() << RangeInRevision(2, 37, 2, 40)); } void TestUses::instanceofStaticProperty() { // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("problems().isEmpty()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(2, 9, 2, 10) << RangeInRevision(2, 28, 2, 29)); dec = top->localDeclarations().at(2); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr classType = dec->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(classType->equals(dec->abstractType().data())); compareUses(dec, QList() << RangeInRevision(2, 14, 2, 16)); dec = top->childContexts().at(0)->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->identifier(), Identifier("foo")); compareUses(dec, QList() << RangeInRevision(2, 31, 2, 35) << RangeInRevision(2, 43, 2, 47)); dec = top->childContexts().at(1)->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->identifier(), Identifier("bar")); compareUses(dec, QList() << RangeInRevision(2, 37, 2, 41)); } void TestUses::instanceofMixedProperty() { // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("bar::$foo;\n", DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(2, 9, 2, 10) << RangeInRevision(2, 28, 2, 29)); dec = top->localDeclarations().at(2); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr classType = dec->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(classType->equals(dec->abstractType().data())); compareUses(dec, QList() << RangeInRevision(2, 14, 2, 16)); dec = top->childContexts().at(0)->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->identifier(), Identifier("foo")); compareUses(dec, QList() << RangeInRevision(2, 31, 2, 35) << RangeInRevision(2, 42, 2, 46)); dec = top->childContexts().at(1)->localDeclarations().at(0); QVERIFY(dec); QCOMPARE(dec->identifier(), Identifier("bar")); compareUses(dec, QList() << RangeInRevision(2, 37, 2, 40)); } void TestUses::instanceofVariableProperty() { // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("$foo->$bar->$foo;\n", DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 5); Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(3, 9, 3, 10)); dec = top->localDeclarations().at(4); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr classType = dec->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(classType->equals(dec->abstractType().data())); compareUses(dec, QList() << RangeInRevision(3, 14, 3, 16) << RangeInRevision(3, 28, 3, 30)); dec = top->localDeclarations().at(2); QCOMPARE(dec->identifier(), Identifier("foo")); compareUses(dec, QList() << RangeInRevision(3, 32, 3, 36) << RangeInRevision(3, 44, 3, 48)); dec = top->localDeclarations().at(3); QCOMPARE(dec->identifier(), Identifier("bar")); compareUses(dec, QList() << RangeInRevision(3, 38, 3, 42)); } +void TestUses::instanceofDynamicStaticProperty() +{ + // 0 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + TopDUContext* top = parse("problems().isEmpty()); + + QVERIFY(!top->parentContext()); + QCOMPARE(top->childContexts().count(), 2); + QCOMPARE(top->localDeclarations().count(), 5); + + Declaration* dec = top->localDeclarations().at(0); + QCOMPARE(dec->identifier(), Identifier("a")); + compareUses(dec, QList() + << RangeInRevision(3, 9, 3, 10)); + + dec = top->localDeclarations().at(4); + QCOMPARE(dec->identifier(), Identifier("a")); + StructureType::Ptr classType = dec->type(); + QVERIFY(classType); + QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); + QVERIFY(classType->equals(dec->abstractType().data())); + compareUses(dec, QList() + << RangeInRevision(3, 14, 3, 16) + << RangeInRevision(3, 28, 3, 30)); + + dec = top->localDeclarations().at(2); + QCOMPARE(dec->identifier(), Identifier("foo")); + compareUses(dec, QList() + << RangeInRevision(3, 34, 3, 38) + << RangeInRevision(3, 52, 3, 56)); + + dec = top->localDeclarations().at(3); + QCOMPARE(dec->identifier(), Identifier("bar")); + compareUses(dec, QList() + << RangeInRevision(3, 43, 3, 47)); +} + +void TestUses::instanceofDynamicVariableProperty() +{ + // 0 1 2 3 4 5 + // 012345678901234567890123456789012345678901234567890123456789 + TopDUContext* top = parse("${$foo}->${$bar}->${$foo};\n", DumpNone); + + QVERIFY(top); + DUChainReleaser releaseTop(top); + DUChainWriteLocker lock; + + QVERIFY(top->problems().isEmpty()); + + QVERIFY(!top->parentContext()); + QCOMPARE(top->childContexts().count(), 2); + QCOMPARE(top->localDeclarations().count(), 5); + + Declaration* dec = top->localDeclarations().at(0); + QCOMPARE(dec->identifier(), Identifier("a")); + compareUses(dec, QList() + << RangeInRevision(3, 9, 3, 10)); + + dec = top->localDeclarations().at(4); + QCOMPARE(dec->identifier(), Identifier("a")); + StructureType::Ptr classType = dec->type(); + QVERIFY(classType); + QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); + QVERIFY(classType->equals(dec->abstractType().data())); + compareUses(dec, QList() + << RangeInRevision(3, 14, 3, 16) + << RangeInRevision(3, 28, 3, 30)); + + dec = top->localDeclarations().at(2); + QCOMPARE(dec->identifier(), Identifier("foo")); + compareUses(dec, QList() + << RangeInRevision(3, 34, 3, 38) + << RangeInRevision(3, 52, 3, 56)); + + dec = top->localDeclarations().at(3); + QCOMPARE(dec->identifier(), Identifier("bar")); + compareUses(dec, QList() + << RangeInRevision(3, 43, 3, 47)); +} + void TestUses::instanceofPropertyArrayAccess() { // 0 1 2 3 4 5 // 012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("foo[0]::$bar[0]->foo;\n", DumpNone); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; QVERIFY(top->problems().isEmpty()); QVERIFY(!top->parentContext()); QCOMPARE(top->childContexts().count(), 2); QCOMPARE(top->localDeclarations().count(), 3); Declaration* dec = top->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(2, 9, 2, 10)); dec = top->localDeclarations().at(2); QCOMPARE(dec->identifier(), Identifier("a")); StructureType::Ptr classType = dec->type(); QVERIFY(classType); QCOMPARE(classType->qualifiedIdentifier(), QualifiedIdentifier("a")); QVERIFY(classType->equals(dec->abstractType().data())); compareUses(dec, QList() << RangeInRevision(2, 14, 2, 16) << RangeInRevision(2, 28, 2, 30)); dec = top->childContexts().at(0)->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("foo")); compareUses(dec, QList() << RangeInRevision(2, 32, 2, 35)); dec = top->childContexts().at(1)->localDeclarations().at(0); QCOMPARE(dec->identifier(), Identifier("bar")); QVERIFY(dec->uses().isEmpty()); } void TestUses::classNameString() { // 0 1 2 3 4 5 6 7 // 01234567890123456789012345678901234567890123456789012345678901234567890123456789 QByteArray method("localDeclarations().at(0); QCOMPARE(foo->identifier().toString(), QString("foo")); compareUses(foo, RangeInRevision(0, 22, 0, 27)); } void TestUses::useTrait() { // 0 1 2 3 4 5 6 7 8 // 012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 TopDUContext* top = parse("one(); $a->two();\n" "$b = new Bar(); $b->one(); $b->two(); $b->four();\n" "$c = new Baz(); $c->one(); $c->two(); $c->baz; $c::six();\n" "$d = new Boo(); $d->one(); $d->switch(); $d->three(); $d->static();\n", DumpAll); QVERIFY(top); DUChainReleaser releaseTop(top); DUChainWriteLocker lock; Declaration* dec; QVector topDecs = top->localDeclarations(); TraitMethodAliasDeclaration* method; TraitMemberAliasDeclaration* member; QCOMPARE(topDecs.size(), 11); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("a"))).first(); compareUses(dec, QList() << RangeInRevision(4, 16, 4, 17) << RangeInRevision(5, 16, 5, 17) << RangeInRevision(5, 22, 5, 23) << RangeInRevision(6, 16, 6, 17) << RangeInRevision(6, 41, 6, 42) << RangeInRevision(7, 16, 7, 17) << RangeInRevision(7, 24, 7, 25) << RangeInRevision(7, 46, 7, 47) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("b"))).first(); compareUses(dec, QList() << RangeInRevision(5, 18, 5, 19) << RangeInRevision(5, 39, 5, 40) << RangeInRevision(5, 42, 5, 43) << RangeInRevision(6, 18, 6, 19) << RangeInRevision(6, 43, 6, 44) << RangeInRevision(7, 18, 7, 19) << RangeInRevision(7, 41, 7, 42) << RangeInRevision(7, 64, 7, 65) ); dec = top->findDeclarations(QualifiedIdentifier(QStringLiteral("c"))).first(); compareUses(dec, QList() << RangeInRevision(6, 20, 6, 21) << RangeInRevision(6, 24, 6, 25) << RangeInRevision(6, 46, 6, 47) << RangeInRevision(7, 20, 7, 21) << RangeInRevision(7, 43, 7, 44) << RangeInRevision(7, 84, 7, 85) ); dec = topDecs[3]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("one"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(8, 20, 8, 23) ); dec = topDecs[3]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("two"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(8, 31, 8, 34) ); dec = topDecs[4]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("one"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(9, 20, 9, 23) ); dec = topDecs[4]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("two"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(9, 31, 9, 34) ); dec = topDecs[4]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("four"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("b")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("three")); QCOMPARE(method->accessPolicy(), Declaration::AccessPolicy::Public); compareUses(dec, QList() << RangeInRevision(9, 42, 9, 46) ); dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("one"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("c")); compareUses(dec, QList() << RangeInRevision(10, 20, 10, 23) ); dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("two"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(10, 31, 10, 34) ); dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("baz"))).first(); member = dynamic_cast(dec); QVERIFY(member); QCOMPARE(member->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("c")); compareUses(dec, QList() << RangeInRevision(10, 42, 10, 45) ); dec = topDecs[5]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("six"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("c")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("five")); QVERIFY(method->isStatic()); compareUses(dec, QList() << RangeInRevision(10, 51, 10, 54) ); dec = topDecs[6]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("one"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); compareUses(dec, QList() << RangeInRevision(11, 20, 11, 23) ); dec = topDecs[6]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("switch"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("a")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("two")); compareUses(dec, QList() << RangeInRevision(11, 31, 11, 37) ); dec = topDecs[6]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("three"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("b")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("three")); QCOMPARE(method->accessPolicy(), Declaration::AccessPolicy::Public); compareUses(dec, QList() << RangeInRevision(11, 45, 11, 50) ); dec = topDecs[6]->internalContext()->findLocalDeclarations(Identifier(QStringLiteral("static"))).first(); method = dynamic_cast(dec); QVERIFY(method); QCOMPARE(method->aliasedDeclaration().data()->context()->owner()->identifier(), Identifier("c")); QCOMPARE(method->aliasedDeclaration().data()->identifier(), Identifier("five")); compareUses(dec, QList() << RangeInRevision(11, 58, 11, 64) ); } void TestUses::exceptionFinally() { // 0 1 2 3 4 // 01234567890123456789012345678901234567890123456 QByteArray method("localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(0, 17, 0, 19) << RangeInRevision(0, 37, 0, 39)); } void TestUses::returnTypeClassFunction() { QByteArray method("localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(0, 29, 0, 30) << RangeInRevision(0, 50, 0, 54)); } void TestUses::returnTypeFunction() { QByteArray method("localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(0, 30, 0, 31)); } void TestUses::defaultValue() { QByteArray method("localDeclarations().at(0); QCOMPARE(a->identifier().toString(), QString("a")); compareUses(a, QList() << RangeInRevision(0, 44, 0, 45)); Declaration *c = top->childContexts().at(0)->localDeclarations().at(0); QCOMPARE(c->identifier().toString(), QString("C")); compareUses(c, QList() << RangeInRevision(0, 47, 0, 48)); } } diff --git a/duchain/tests/uses.h b/duchain/tests/uses.h index f7d2718..4ae13b6 100644 --- a/duchain/tests/uses.h +++ b/duchain/tests/uses.h @@ -1,103 +1,106 @@ /* 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 TESTUSES_H #define TESTUSES_H #include "../../duchain/tests/duchaintestbase.h" namespace Php { class TestUses : public DUChainTestBase { Q_OBJECT public: TestUses(); private slots: void newObject(); void functionCall(); void memberFunctionCall(); void unsureMemberFunctionCall(); void memberVariable(); void implicitMemberVariable(); void variable(); void varInString(); void variableInNamespace(); void globalVariableInNamespace(); void variableInOtherNamespace(); void memberVarInString(); void memberFunctionInString(); void variableTypeChange(); void variableTypeChangeInFunction(); void classExtends(); void classImplements(); void classImplementsMultiple(); void interfaceExtends(); void interfaceExtendsMultiple(); void staticMemberFunctionCall(); void staticMemberVariable(); + void dynamicStaticMemberVariable(); void constant(); void classConstant(); void classParent(); void classSelf(); void classThis(); void objectWithClassName(); void classAndConstWithSameName(); void classAndFunctionWithSameName(); void constAndVariableWithSameName(); void functionAndClassWithSameName(); void constantInClassMember(); void useInAsignment(); void foreachArray(); void assignmentToMemberArray(); void staticArrayIndex(); void functionParamNewDeclaration(); void catchClass(); void variableRedeclaration(); void caseInsensitiveFunction(); void caseInsensitiveMethod(); void caseInsensitiveClass(); void functionUseBeforeDeclaration(); void propertyAndMethodWithSameName(); void nestedMethodCalls(); void unset(); void functionArguments(); void namespaces(); void useNamespace(); void lateStatic(); void closures(); void closureTypehints(); void instanceof(); void instanceofClassProperty(); void instanceofStaticProperty(); void instanceofMixedProperty(); void instanceofVariableProperty(); + void instanceofDynamicStaticProperty(); + void instanceofDynamicVariableProperty(); void instanceofPropertyArrayAccess(); void classNameString(); void useTrait(); void exceptionFinally(); void returnTypeClassFunction(); void returnTypeFunction(); void defaultValue(); }; } #endif