diff --git a/duchain/builders/typebuilder.cpp b/duchain/builders/typebuilder.cpp index 0c9dbf7..f2ffe9c 100644 --- a/duchain/builders/typebuilder.cpp +++ b/duchain/builders/typebuilder.cpp @@ -1,551 +1,554 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2008 Niko Sams * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU Library General Public License as * * published by the Free Software Foundation; either version 2 of the * * License, or (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include "typebuilder.h" #include #include #include #include #include #include #include "../declarations/classdeclaration.h" #include "../types/indexedcontainer.h" #include "../types/integraltypeextended.h" #include "../types/structuretype.h" #include #include "editorintegrator.h" #include "parsesession.h" #include "phpdebugvisitor.h" #include "expressionparser.h" #include "expressionvisitor.h" #include "../declarations/classmethoddeclaration.h" #include using namespace KDevelop; namespace Php { TypeBuilder::TypeBuilder() : TypeBuilderBase() , m_gotTypeFromDocComment(false) , m_gotReturnTypeFromDocComment(false) { } TypeBuilder::~TypeBuilder() { } AbstractType::Ptr TypeBuilder::parseType(QString type, AstNode* node) { uint iType = 0; type = type.trimmed(); if (!type.compare(QLatin1String("int"), Qt::CaseInsensitive) || !type.compare(QLatin1String("integer"), Qt::CaseInsensitive)) { iType = IntegralType::TypeInt; } else if (!type.compare(QLatin1String("float"), Qt::CaseInsensitive) || !type.compare(QLatin1String("double"), Qt::CaseInsensitive)) { iType = IntegralType::TypeFloat; } else if (!type.compare(QLatin1String("bool"), Qt::CaseInsensitive) || !type.compare(QLatin1String("boolean"), Qt::CaseInsensitive) || !type.compare(QLatin1String("false"), Qt::CaseInsensitive) || !type.compare(QLatin1String("true"), Qt::CaseInsensitive)) { iType = IntegralType::TypeBoolean; } else if (!type.compare(QLatin1String("string"), Qt::CaseInsensitive)) { iType = IntegralType::TypeString; } else if (!type.compare(QLatin1String("mixed"), Qt::CaseInsensitive)) { iType = IntegralType::TypeMixed; } else if (!type.compare(QLatin1String("array"), Qt::CaseInsensitive)) { iType = IntegralType::TypeArray; } else if (!type.compare(QLatin1String("resource"), Qt::CaseInsensitive)) { return AbstractType::Ptr(new IntegralTypeExtended(IntegralTypeExtended::TypeResource)); } else if (!type.compare(QLatin1String("null"), Qt::CaseInsensitive)) { iType = IntegralType::TypeNull; } else if (!type.compare(QLatin1String("void"), Qt::CaseInsensitive)) { iType = IntegralType::TypeVoid; } else if (!type.compare(QLatin1String("self"), Qt::CaseInsensitive) || !type.compare(QLatin1String("this"), Qt::CaseInsensitive) || !type.compare(QLatin1String("static"), Qt::CaseInsensitive)) { DUChainReadLocker lock(DUChain::lock()); if ( currentContext()->type() == DUContext::Class && currentContext()->owner() ) { return currentContext()->owner()->abstractType(); } } else { if (!type.compare(QLatin1String("object"), Qt::CaseInsensitive)) { type = QStringLiteral("stdclass"); } //don't use openTypeFromName as it uses cursor for findDeclarations DeclarationPointer decl = findDeclarationImport(ClassDeclarationType, QualifiedIdentifier(type.toLower())); if (decl && decl->abstractType()) { return decl->abstractType(); } if (type.contains('|')) { QList types; foreach (const QString& t, type.split('|')) { AbstractType::Ptr subType = parseType(t, node); if (!(IntegralType::Ptr::dynamicCast(subType) && IntegralType::Ptr::staticCast(subType)->dataType() == IntegralType::TypeMixed)) { types << parseType(t, node); } } if (!type.isEmpty()) { UnsureType::Ptr ret(new UnsureType()); foreach (const AbstractType::Ptr& t, types) { ret->addType(t->indexed()); } //qCDebug(DUCHAIN) << type << ret->toString(); return AbstractType::Ptr::staticCast(ret); } } iType = IntegralType::TypeMixed; } AbstractType::Ptr ret(new IntegralType(iType)); //qCDebug(DUCHAIN) << type << ret->toString(); return ret; } AbstractType::Ptr TypeBuilder::injectParseType(QString type, AstNode* node) { AbstractType::Ptr ret = parseType(type, node); injectType(ret); //qCDebug(DUCHAIN) << type << ret->toString(); return ret; } /** * Find all (or only one - see @p docCommentName) values for a given needle * in a doc-comment. Needle has to start a line in the doccomment, * i.e.: * * * @docCommentName value * * or * * /// @docCommentName value */ QStringList findInDocComment(const QString &docComment, const QString &docCommentName, const bool onlyOne) { QStringList matches; // optimization that does not require potentially slow regexps // old code was something like this: /* if (!docComment.isEmpty()) { QRegExp rx("\\*\\s+@param\\s([^\\s]*)"); int pos = 0; while ((pos = rx.indexIn(docComment, pos)) != -1) { ret << parseType(rx.cap(1), node); pos += rx.matchedLength(); } } */ for ( int i = 0, size = docComment.size(); i < size; ++i ) { if ( docComment[i].isSpace() || docComment[i] == '*' || docComment[i] == '/' ) { // skip whitespace and comment-marker at beginning of line continue; } else if ( docComment[i] == '@' && docComment.midRef(i + 1, docCommentName.size()) == docCommentName ) { // find @return or similar i += docCommentName.size() + 1; // skip whitespace (at least one is required) if ( i >= size || !docComment[i].isSpace() ) { // skip to next line i = docComment.indexOf('\n', i); if ( i == -1 ) { break; } continue; } else if ( docComment[i] == '\n' ) { continue; } ++i; // at least one whitespace while ( i < size && docComment[i].isSpace() ) { ++i; } // finally get the typename int pos = i; while ( pos < size && !docComment[pos].isSpace() ) { ++pos; } if ( pos > i ) { matches << docComment.mid(i, pos - i); if ( onlyOne ) { break; } else { i = pos; } } } // skip to next line i = docComment.indexOf('\n', i); if ( i == -1 ) { break; } } return matches; } AbstractType::Ptr TypeBuilder::parseDocComment(AstNode* node, const QString& docCommentName) { m_gotTypeFromDocComment = false; const QString& docComment = editor()->parseSession()->docComment(node->startToken); if ( !docComment.isEmpty() ) { const QStringList& matches = findInDocComment(docComment, docCommentName, true); if ( !matches.isEmpty() ) { AbstractType::Ptr type; if (matches.first() == QLatin1String("$this")) { DUChainReadLocker lock(DUChain::lock()); if (currentContext()->owner()) { type = currentContext()->owner()->abstractType(); } } else { type = injectParseType(matches.first(), node); } if (type) { m_gotTypeFromDocComment = true; } return type; } } return AbstractType::Ptr(); } QList TypeBuilder::parseDocCommentParams(AstNode* node) { QList ret; QString docComment = editor()->parseSession()->docComment(node->startToken); if ( !docComment.isEmpty() ) { const QStringList& matches = findInDocComment(docComment, QStringLiteral("param"), false); if ( !matches.isEmpty() ) { ret.reserve(matches.size()); foreach ( const QString& type, matches ) { ret << parseType(type, node); } } } return ret; } AbstractType::Ptr TypeBuilder::getTypeForNode(AstNode* node) { AbstractType::Ptr type; if (node) { type = parseDocComment(node, QStringLiteral("var")); //we fully trust in @var typehint and don't try to evaluate ourself if (!type) { node->ducontext = currentContext(); ExpressionParser ep; ep.setCreateProblems(true); ExpressionEvaluationResult res = ep.evaluateType(node, editor()); if (res.hadUnresolvedIdentifiers()) { m_hadUnresolvedIdentifiers = true; } type = res.type(); } } if (!type) { type = AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed)); } return type; } void TypeBuilder::visitClassDeclarationStatement(ClassDeclarationStatementAst* node) { // the predeclaration builder should have set up a type already // and the declarationbuilder should have set that as current type Q_ASSERT(hasCurrentType() && currentType()); TypeBuilderBase::visitClassDeclarationStatement(node); } void TypeBuilder::visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst* node) { // the predeclaration builder should have set up a type already // and the declarationbuilder should have set that as current type Q_ASSERT(hasCurrentType() && currentType()); TypeBuilderBase::visitInterfaceDeclarationStatement(node); } void TypeBuilder::visitTraitDeclarationStatement(TraitDeclarationStatementAst* node) { // the predeclaration builder should have set up a type already // and the declarationbuilder should have set that as current type Q_ASSERT(hasCurrentType() && currentType()); TypeBuilderBase::visitTraitDeclarationStatement(node); } void TypeBuilder::visitClassStatement(ClassStatementAst *node) { if (node->methodName) { //method declaration m_currentFunctionParams = parseDocCommentParams(node); FunctionType::Ptr functionType = FunctionType::Ptr(new FunctionType()); openType(functionType); AbstractType::Ptr phpdocReturnType = parseDocComment(node, QStringLiteral("return")); functionType->setReturnType(returnType(node->returnType, phpdocReturnType, editor(), currentContext())); m_gotReturnTypeFromDocComment = functionType->returnType(); updateCurrentType(); TypeBuilderBase::visitClassStatement(node); if (currentType() && !currentType()->returnType()) { currentType()->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); } closeType(); } else { //member-variable parseDocComment(node, QStringLiteral("var")); TypeBuilderBase::visitClassStatement(node); if (m_gotTypeFromDocComment) { clearLastType(); m_gotTypeFromDocComment = false; } } } void TypeBuilder::visitClassVariable(ClassVariableAst *node) { if (!m_gotTypeFromDocComment) { openAbstractType(getTypeForNode(node->value)); TypeBuilderBase::visitClassVariable(node); closeType(); } else { TypeBuilderBase::visitClassVariable(node); } } void TypeBuilder::visitConstantDeclaration(ConstantDeclarationAst* node) { if (!m_gotTypeFromDocComment || !currentAbstractType()) { AbstractType::Ptr type = getTypeForNode(node->scalar); type->setModifiers(type->modifiers() | AbstractType::ConstModifier); openAbstractType(type); TypeBuilderBase::visitConstantDeclaration(node); closeType(); } else { currentAbstractType()->setModifiers(currentAbstractType()->modifiers() & AbstractType::ConstModifier); TypeBuilderBase::visitConstantDeclaration(node); } } void TypeBuilder::visitParameter(ParameterAst *node) { AbstractType::Ptr phpDocTypehint; if (m_currentFunctionParams.count() > currentType()->arguments().count()) { phpDocTypehint = m_currentFunctionParams.at(currentType()->arguments().count()); } AbstractType::Ptr type = parameterType(node, phpDocTypehint, editor(), currentContext()); openAbstractType(type); TypeBuilderBase::visitParameter(node); closeType(); DUChainWriteLocker lock(DUChain::lock()); currentType()->addArgument(type); } void TypeBuilder::visitFunctionDeclarationStatement(FunctionDeclarationStatementAst* node) { m_currentFunctionParams = parseDocCommentParams(node); // the predeclarationbuilder should have already built the type // and the declarationbuilder should have set it to open Q_ASSERT(hasCurrentType()); FunctionType::Ptr type = currentType(); Q_ASSERT(type); AbstractType::Ptr phpdocReturnType = parseDocComment(node, QStringLiteral("return")); type->setReturnType(returnType(node->returnType, phpdocReturnType, editor(), currentContext())); m_gotReturnTypeFromDocComment = type->returnType(); updateCurrentType(); TypeBuilderBase::visitFunctionDeclarationStatement(node); if (!type->returnType()) { type->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); } } void TypeBuilder::visitClosure(ClosureAst* node) { m_currentFunctionParams = parseDocCommentParams(node); FunctionType::Ptr type = FunctionType::Ptr(new FunctionType()); openType(type); AbstractType::Ptr phpdocReturnType = parseDocComment(node, QStringLiteral("return")); type->setReturnType(returnType(node->returnType, phpdocReturnType, editor(), currentContext())); m_gotReturnTypeFromDocComment = type->returnType(); updateCurrentType(); TypeBuilderBase::visitClosure(node); if (!type->returnType()) { type->setReturnType(AbstractType::Ptr(new IntegralType(IntegralType::TypeVoid))); } closeType(); } void TypeBuilder::visitAssignmentExpression(AssignmentExpressionAst* node) { // performance: only try to find type when we are actually in an assignment expr if (node->assignmentExpression || node->assignmentExpressionEqual) { openAbstractType(getTypeForNode(node)); } TypeBuilderBase::visitAssignmentExpression(node); if (node->assignmentExpression || node->assignmentExpressionEqual) { closeType(); } } void TypeBuilder::visitStaticVar(StaticVarAst *node) { openAbstractType(getTypeForNode(node->value)); TypeBuilderBase::visitStaticVar(node); closeType(); } void TypeBuilder::visitStatement(StatementAst* node) { TypeBuilderBase::visitStatement(node); if ( !m_gotReturnTypeFromDocComment && node->returnExpr && hasCurrentType() && currentType()) { FunctionType::Ptr ft = currentType(); // qCDebug(DUCHAIN) << "return" << (ft->returnType() ? ft->returnType()->toString() : "none") << lastType()->toString(); AbstractType::Ptr type = getTypeForNode(node->returnExpr); if (type) { // ignore references for return values, PHP does so as well if ( ReferenceType::Ptr rType = ReferenceType::Ptr::dynamicCast(type) ) { type = rType->baseType(); } if (ft->returnType() && !ft->returnType()->equals(type.data())) { if (ft->returnType().cast() && ft->returnType().cast()->dataType() == IntegralType::TypeMixed) { //don't add TypeMixed to the list, just ignore ft->setReturnType(type); } else { UnsureType::Ptr retT; if (ft->returnType().cast()) { //qCDebug(DUCHAIN) << "we already have an unsure type"; retT = ft->returnType().cast(); if (type.cast()) { //qCDebug(DUCHAIN) << "add multiple to returnType"; FOREACH_FUNCTION(const IndexedType& t, type.cast()->types) { retT->addType(t); } } else { //qCDebug(DUCHAIN) << "add to returnType"; retT->addType(type->indexed()); } } else { if (type.cast()) { retT = type.cast(); } else { retT = new UnsureType(); retT->addType(type->indexed()); } retT->addType(ft->returnType()->indexed()); } ft->setReturnType(AbstractType::Ptr::staticCast(retT)); } } else { ft->setReturnType(type); } updateCurrentType(); } } AstNode *foreachNode = nullptr; if (node->foreachVar) { foreachNode = node->foreachVar; } else if (node->foreachExpr) { foreachNode = node->foreachExpr; } else if (node->foreachExprAsVar) { foreachNode = node->foreachExprAsVar; } if (foreachNode) { ExpressionVisitor v(editor()); foreachNode->ducontext = currentContext(); v.visitNode(foreachNode); DUChainReadLocker lock(DUChain::lock()); bool foundType = false; if (StructureType::Ptr type = StructureType::Ptr::dynamicCast(v.result().type())) { ClassDeclaration *classDec = dynamic_cast(type->declaration(currentContext()->topContext())); if (!classDec) { ///FIXME: this is just a hack for https://bugs.kde.org/show_bug.cgi?id=269369 /// a proper fix needs full fledged two-pass, i.e. get rid of PreDeclarationBuilder // 0 == global lookup and the delcaration is found again... classDec = dynamic_cast(type->declaration(nullptr)); } if (classDec) { /// Qualified identifier for 'iterator' static const QualifiedIdentifier iteratorQId(QStringLiteral("iterator")); ClassDeclaration* iteratorDecl = dynamic_cast( findDeclarationImport(ClassDeclarationType, iteratorQId).data() ); Q_ASSERT(iteratorDecl); if (classDec->isPublicBaseClass(iteratorDecl, currentContext()->topContext())) { /// Qualified identifier for 'current' static const QualifiedIdentifier currentQId(QStringLiteral("current")); - foreach (Declaration *d, classDec->internalContext()->findDeclarations(currentQId)) { - if (!dynamic_cast(d)) continue; - Q_ASSERT(d->type()); - injectType(d->type()->returnType()); - foundType = true; - // qCDebug(DUCHAIN) << "that's it: " << d->type()->returnType()->toString(); + auto classContext = classDec->internalContext(); + if (classContext) { + foreach (Declaration *d, classContext->findDeclarations(currentQId)) { + if (!dynamic_cast(d)) continue; + Q_ASSERT(d->type()); + injectType(d->type()->returnType()); + foundType = true; + // qCDebug(DUCHAIN) << "that's it: " << d->type()->returnType()->toString(); + } } } } } if (!foundType) { injectType(AbstractType::Ptr(new IntegralType(IntegralType::TypeMixed))); } } } void TypeBuilder::visitCatchItem(Php::CatchItemAst *node) { TypeBuilderBase::visitCatchItem(node); DeclarationPointer dec = findDeclarationImport(ClassDeclarationType, identifierForNamespace(node->catchClass, m_editor)); if (dec && dec->abstractType()) { openAbstractType(dec->abstractType()); closeType(); } } void TypeBuilder::updateCurrentType() { // do nothing } } diff --git a/duchain/tests/duchain_multiplefiles.cpp b/duchain/tests/duchain_multiplefiles.cpp index 68415f2..09237dd 100644 --- a/duchain/tests/duchain_multiplefiles.cpp +++ b/duchain/tests/duchain_multiplefiles.cpp @@ -1,296 +1,324 @@ /* This file is part of KDevelop Copyright 2010 Niko Sams Copyright 2011 Milian Wolff This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "duchain_multiplefiles.h" #include #include #include #include #include #include #include #include #include +#include QTEST_MAIN(Php::TestDUChainMultipleFiles) using namespace KDevelop; using namespace Php; void TestDUChainMultipleFiles::initTestCase() { DUChainTestBase::initTestCase(); TestCore* core = dynamic_cast(ICore::self()); Q_ASSERT(core); m_projectController = new TestProjectController(core); core->setProjectController(m_projectController); } void TestDUChainMultipleFiles::testImportsGlobalFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f1(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); } void TestDUChainMultipleFiles::testImportsBaseClassNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); QVERIFY(ICore::self()->languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testNonExistingBaseClass() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f1(QStringLiteral("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testImportsGlobalFunctionNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); } void TestDUChainMultipleFiles::testNonExistingGlobalFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testImportsStaticFunctionNotYetParsed() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("imports(f1.topContext(), CursorInRevision(0, 0))); } void TestDUChainMultipleFiles::testNonExistingStaticFunction() { TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f2(QStringLiteral("languageController()->backgroundParser()->queuedCount() == 0); } void TestDUChainMultipleFiles::testForeachImportedIdentifier() { // see https://bugs.kde.org/show_bug.cgi?id=269369 TopDUContext::Features features = TopDUContext::VisibleDeclarationsAndContexts; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); // build dependency TestFile f1(QStringLiteral("bar(); foreach($i as $a => $b) {} } \n" " public function bar() { $a = new SomeIterator(); return $a; }\n" " }\n"), QStringLiteral("php"), project); for(int i = 0; i < 2; ++i) { if (i > 0) { features = static_cast(features | TopDUContext::ForceUpdate); } f2.parse(features); QVERIFY(f2.waitForParsed()); QTest::qWait(100); DUChainWriteLocker lock(DUChain::lock()); QCOMPARE(f2.topContext()->childContexts().size(), 1); DUContext* ACtx = f2.topContext()->childContexts().first(); QVERIFY(ACtx); QCOMPARE(ACtx->childContexts().size(), 4); Declaration* iDec = ACtx->childContexts().at(1)->localDeclarations().first(); QVERIFY(iDec); Declaration* SomeIteratorDec = f1.topContext()->localDeclarations().first(); QVERIFY(SomeIteratorDec); if (i == 0) { QEXPECT_FAIL("", "needs a full two-pass (i.e. get rid of PreDeclarationBuilder)", Continue); } QVERIFY(iDec->abstractType()->equals(SomeIteratorDec->abstractType().constData())); QVERIFY(f2.topContext()->imports(f1.topContext(), CursorInRevision(0, 0))); } } void TestDUChainMultipleFiles::testUpdateForeach() { TopDUContext::Features features = TopDUContext::AllDeclarationsContextsAndUses; TestProject* project = new TestProject; m_projectController->closeAllProjects(); m_projectController->addProject(project); TestFile f(QStringLiteral(" $k) {}\n"), QStringLiteral("php"), project); f.parse(features); QVERIFY(f.waitForParsed()); QVERIFY(f.topContext()); { DUChainWriteLocker lock; QVERIFY(f.topContext()->problems().isEmpty()); QCOMPARE(f.topContext()->findDeclarations(Identifier("k")).count(), 1); Declaration* kDec = f.topContext()->findDeclarations(Identifier(QStringLiteral("k"))).first(); QCOMPARE(kDec->rangeInCurrentRevision().start().line(), 1); QCOMPARE(kDec->rangeInCurrentRevision().start().column(), 0); QCOMPARE(kDec->uses().count(), 1); QCOMPARE(kDec->uses().begin()->count(), 1); QCOMPARE(kDec->uses().begin()->begin()->start.line, 2); } // delete $k = null; line f.setFileContents(QStringLiteral(" $k) {}\n")); f.parse(static_cast(features | TopDUContext::ForceUpdate)); QVERIFY(f.waitForParsed()); QVERIFY(f.topContext()); { DUChainWriteLocker lock; QVERIFY(f.topContext()->problems().isEmpty()); QCOMPARE(f.topContext()->findDeclarations(Identifier("k")).count(), 1); Declaration* kDec = f.topContext()->findDeclarations(Identifier(QStringLiteral("k"))).first(); QCOMPARE(kDec->rangeInCurrentRevision().start().line(), 1); QCOMPARE(kDec->rangeInCurrentRevision().start().column(), 25); QCOMPARE(kDec->uses().count(), 0); } } void TestDUChainMultipleFiles::testTodoExtractorReparse() { TestFile file(QStringLiteral("baz();"), QStringLiteral("php")); QVERIFY(KDevelop::ICore::self()->languageController()->completionSettings()->todoMarkerWords().contains("TODO")); auto features = TopDUContext::AllDeclarationsContextsAndUses; for (int i = 0; i < 2; ++i) { if (i == 1) { file.setFileContents(QStringLiteral("asdf();")); features = static_cast(features | TopDUContext::ForceUpdate); } file.parse(features); QVERIFY(file.waitForParsed()); DUChainReadLocker lock; auto top = file.topContext(); QVERIFY(top); QCOMPARE(top->problems().size(), 1); QCOMPARE(top->problems().at(0)->description(), QString("TODO")); QCOMPARE(top->problems().at(0)->range(), RangeInRevision(2, 3, 2, 7)); } } + +void TestDUChainMultipleFiles::testIteratorForeachReparse() { + TestFile file(QStringLiteral("(features | TopDUContext::ForceUpdate); + } + + file.parse(features); + QVERIFY(file.waitForParsed()); + + DUChainReadLocker lock; + auto top = file.topContext(); + QVERIFY(top); + QVERIFY(top->localDeclarations().size() == 2); + QCOMPARE(top->localDeclarations().at(0)->qualifiedIdentifier(), QualifiedIdentifier("a")); + + IntegralType::Ptr type = top->localDeclarations().at(0)->type(); + QVERIFY(type); + //Should actually parse as an TypeInt, but this does not work. + QVERIFY(type->dataType() == IntegralType::TypeMixed); + } +} diff --git a/duchain/tests/duchain_multiplefiles.h b/duchain/tests/duchain_multiplefiles.h index f8972b6..85534e2 100644 --- a/duchain/tests/duchain_multiplefiles.h +++ b/duchain/tests/duchain_multiplefiles.h @@ -1,54 +1,55 @@ /* This file is part of KDevelop Copyright 2010 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef TESTDUCHAINMULTIPLEFILES_H #define TESTDUCHAINMULTIPLEFILES_H #include "duchaintestbase.h" namespace KDevelop { class TestCore; class TestProjectController; } namespace Php { class TestDUChainMultipleFiles : public DUChainTestBase { Q_OBJECT private slots: void initTestCase(); void testImportsGlobalFunction(); void testImportsBaseClassNotYetParsed(); void testNonExistingBaseClass(); void testImportsGlobalFunctionNotYetParsed(); void testNonExistingGlobalFunction(); void testImportsStaticFunctionNotYetParsed(); void testNonExistingStaticFunction(); void testForeachImportedIdentifier(); void testUpdateForeach(); void testTodoExtractorReparse(); + void testIteratorForeachReparse(); private: KDevelop::TestProjectController* m_projectController; }; } #endif