diff --git a/duchain/builders/declarationbuilder.cpp b/duchain/builders/declarationbuilder.cpp index 8ef4f29..0fbe588 100644 --- a/duchain/builders/declarationbuilder.cpp +++ b/duchain/builders/declarationbuilder.cpp @@ -1,614 +1,626 @@ /************************************************************************************* * Copyright (C) 2014 by Pavel Petrushkov * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU 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 General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #include "declarationbuilder.h" #include #include #include #include #include #include #include #include #include #include #include "expressionvisitor.h" #include "helper.h" #include "duchaindebug.h" using namespace KDevelop; DeclarationBuilder::DeclarationBuilder(ParseSession* session, bool forExport) : m_export(forExport), m_preBuilding(false), m_lastTypeComment(), m_lastConstComment() { setParseSession(session); } KDevelop::ReferencedTopDUContext DeclarationBuilder::build(const KDevelop::IndexedString& url, go::AstNode* node, KDevelop::ReferencedTopDUContext updateContext) { qCDebug(DUCHAIN) << "DeclarationBuilder start"; if(!m_preBuilding) { qCDebug(DUCHAIN) << "Running prebuilder"; DeclarationBuilder preBuilder(m_session, m_export); preBuilder.m_preBuilding = true; updateContext = preBuilder.build(url, node, updateContext); } return DeclarationBuilderBase::build(url, node, updateContext); } void DeclarationBuilder::startVisiting(go::AstNode* node) { { DUChainWriteLocker lock; topContext()->clearImportedParentContexts(); topContext()->updateImportsCache(); } return DeclarationBuilderBase::startVisiting(node); } void DeclarationBuilder::visitVarSpec(go::VarSpecAst* node) { if(node->type) {//if type is supplied we don't visit expressions declareVariablesWithType(node->id, node->idList, node->type, false); }else if(node->expression) { declareVariables(node->id, node->idList, node->expression, node->expressionList, false); } } void DeclarationBuilder::visitShortVarDecl(go::ShortVarDeclAst* node) { declareVariables(node->id, node->idList, node->expression, node->expressionList, false); } void DeclarationBuilder::declareVariablesWithType(go::IdentifierAst* id, go::IdListAst* idList, go::TypeAst* type, bool declareConstant) { m_contextIdentifier = identifierForNode(id); visitType(type); if(!lastType()) injectType(AbstractType::Ptr(new IntegralType(IntegralType::TypeNone))); lastType()->setModifiers(declareConstant ? AbstractType::ConstModifier : AbstractType::NoModifiers); if(identifierForNode(id).toString() != "_") { declareVariable(id, lastType()); } if(declareConstant) m_constAutoTypes.append(lastType()); if(idList) { auto iter = idList->idSequence->front(), end = iter; do { if(identifierForNode(iter->element).toString() != "_") { declareVariable(iter->element, lastType()); } if(declareConstant) m_constAutoTypes.append(lastType()); iter = iter->next; } while (iter != end); } } void DeclarationBuilder::declareVariables(go::IdentifierAst* id, go::IdListAst* idList, go::ExpressionAst* expression, go::ExpressionListAst* expressionList, bool declareConstant) { m_contextIdentifier = identifierForNode(id); QList types; if(!expression) return; go::ExpressionVisitor exprVisitor(m_session, currentContext(), this); exprVisitor.visitExpression(expression); Q_ASSERT(exprVisitor.lastTypes().size() != 0); if(!expressionList) types = exprVisitor.lastTypes(); else { types.append(exprVisitor.lastTypes().first()); auto iter = expressionList->expressionsSequence->front(), end = iter; do { exprVisitor.clearAll(); exprVisitor.visitExpression(iter->element); Q_ASSERT(exprVisitor.lastTypes().size() != 0); types.append(exprVisitor.lastTypes().first()); iter = iter->next; } while (iter != end); } if(types.size() == 0) return; for(AbstractType::Ptr& type : types) type->setModifiers(declareConstant ? AbstractType::ConstModifier : AbstractType::NoModifiers); if(declareConstant) m_constAutoTypes = types; if(identifierForNode(id).toString() != "_") { declareVariable(id, types.first()); } if(idList) { int typeIndex = 1; auto iter = idList->idSequence->front(), end = iter; do { if(typeIndex >= types.size()) //not enough types to declare all variables return; if(identifierForNode(iter->element).toString() != "_") { declareVariable(iter->element, types.at(typeIndex)); } iter = iter->next; typeIndex++; } while (iter != end); } } void DeclarationBuilder::declareVariable(go::IdentifierAst* id, const AbstractType::Ptr& type) { if(type->modifiers() & AbstractType::ConstModifier) setComment(m_lastConstComment); DUChainWriteLocker lock; Declaration* dec = openDeclaration(identifierForNode(id), editorFindRange(id, 0)); dec->setType(type); dec->setKind(Declaration::Instance); closeDeclaration(); } void DeclarationBuilder::visitConstDecl(go::ConstDeclAst* node) { m_constAutoTypes.clear(); m_lastConstComment = m_session->commentBeforeToken(node->startToken); //adding const declaration code, just like in GoDoc m_lastConstComment.append(m_session->textForNode(node).toUtf8()); go::DefaultVisitor::visitConstDecl(node); m_lastConstComment = QByteArray(); } void DeclarationBuilder::visitConstSpec(go::ConstSpecAst* node) { if(node->type) { declareVariablesWithType(node->id, node->idList, node->type, true); }else if(node->expression) { declareVariables(node->id, node->idList, node->expression, node->expressionList, true); }else {//this can only happen after a previous constSpec with some expressionList //in this case identifiers assign same types as previous constSpec(http://golang.org/ref/spec#Constant_declarations) if(m_constAutoTypes.size() == 0) return; { declareVariable(node->id, m_constAutoTypes.first()); } if(node->idList) { int typeIndex = 1; auto iter = node->idList->idSequence->front(), end = iter; do { if(typeIndex >= m_constAutoTypes.size()) //not enough types to declare all constants return; declareVariable(iter->element, m_constAutoTypes.at(typeIndex)); iter = iter->next; typeIndex++; } while (iter != end); } } } void DeclarationBuilder::visitFuncDeclaration(go::FuncDeclarationAst* node) { go::GoFunctionDeclaration* decl = parseSignature(node->signature, true, node->funcName, m_session->commentBeforeToken(node->startToken-1)); if(!node->body) return; //a context will be opened when visiting block, but we still open another one here //so we can import arguments into it.(same goes for methodDeclaration) DUContext* bodyContext = openContext(node->body, DUContext::ContextType::Function, node->funcName); {//import parameters into body context DUChainWriteLocker lock; if(decl->internalContext()) currentContext()->addImportedParentContext(decl->internalContext()); if(decl->returnArgsContext()) currentContext()->addImportedParentContext(decl->returnArgsContext()); } visitBlock(node->body); { DUChainWriteLocker lock; lastContext()->setType(DUContext::Function); decl->setInternalFunctionContext(lastContext()); //inner block context decl->setKind(Declaration::Instance); } closeContext(); //body wrapper context } +void DeclarationBuilder::visitPrimaryExpr(go::PrimaryExprAst *node) +{ + if(node->signature && !node->convArg) // func type literal and not conversion. + { + buildFunction(node->signature, node->body); + } + else + { + go::DefaultVisitor::visitPrimaryExpr(node); + } +} + void DeclarationBuilder::visitMethodDeclaration(go::MethodDeclarationAst* node) { Declaration* declaration=0; if(node->methodRecv) { go::IdentifierAst* actualtype=0; if(node->methodRecv->ptype) actualtype = node->methodRecv->ptype; else if(node->methodRecv->type) actualtype = node->methodRecv->type; else actualtype = node->methodRecv->nameOrType; DUChainWriteLocker lock; declaration = openDeclaration(identifierForNode(actualtype), editorFindRange(actualtype, 0)); declaration->setKind(Declaration::Namespace); openContext(node, editorFindRange(node, 0), DUContext::Namespace, identifierForNode(actualtype)); declaration->setInternalContext(currentContext()); } go::GoFunctionDeclaration* decl = parseSignature(node->signature, true, node->methodName, m_session->commentBeforeToken(node->startToken-1)); if(!node->body) return; DUContext* bodyContext = openContext(node->body, DUContext::ContextType::Function, node->methodName); {//import parameters into body context DUChainWriteLocker lock; if(decl->internalContext()) currentContext()->addImportedParentContext(decl->internalContext()); if(decl->returnArgsContext()) currentContext()->addImportedParentContext(decl->returnArgsContext()); } if(node->methodRecv->type) {//declare method receiver variable('this' or 'self' analog in Go) buildTypeName(node->methodRecv->type); if(node->methodRecv->star!= -1) { PointerType* ptype = new PointerType(); ptype->setBaseType(lastType()); injectType(PointerType::Ptr(ptype)); } DUChainWriteLocker n; Declaration* thisVariable = openDeclaration(identifierForNode(node->methodRecv->nameOrType), editorFindRange(node->methodRecv->nameOrType, 0)); thisVariable->setAbstractType(lastType()); closeDeclaration(); } visitBlock(node->body); { DUChainWriteLocker lock; lastContext()->setType(DUContext::Function); decl->setInternalFunctionContext(lastContext()); //inner block context decl->setKind(Declaration::Instance); } closeContext(); //body wrapper context closeContext(); //namespace closeDeclaration(); //namespace declaration } void DeclarationBuilder::visitTypeSpec(go::TypeSpecAst* node) { //first try setting comment before type name //if it doesn't exists, set comment before type declaration QByteArray comment = m_session->commentBeforeToken(node->startToken); if(comment.size() == 0) comment = m_lastTypeComment; setComment(comment); Declaration* decl; { DUChainWriteLocker lock; decl = openDeclaration(identifierForNode(node->name), editorFindRange(node->name, 0)); //decl->setKind(Declaration::Namespace); decl->setKind(Declaration::Type); //force direct here because otherwise DeclarationId will mess up actual type declaration and method declarations //TODO perhaps we can do this with specialization or additional identity? decl->setAlwaysForceDirect(true); } m_contextIdentifier = identifierForNode(node->name); visitType(node->type); DUChainWriteLocker lock; //qCDebug(DUCHAIN) << lastType()->toString(); decl->setType(lastType()); decl->setIsTypeAlias(true); closeDeclaration(); //qCDebug(DUCHAIN) << "Type" << identifierForNode(node->name) << " exit"; } void DeclarationBuilder::visitImportSpec(go::ImportSpecAst* node) { //prevent recursive imports //without preventing recursive imports. importing standart go library(2000+ files) takes minutes and sometimes never stops //thankfully go import mechanism doesn't need recursive imports(I think) //if(m_export) //return; QString import(identifierForIndex(node->importpath->import).toString()); QList contexts = m_session->contextForImport(import); if(contexts.empty()) return; //usually package name matches directory, so try searching for that first QualifiedIdentifier packageName(import.mid(1, import.length()-2)); bool firstContext = true; for(const ReferencedTopDUContext& context : contexts) { //don't import itself if(context.data() == topContext()) continue; DeclarationPointer decl = go::checkPackageDeclaration(packageName.last(), context); if(!decl && firstContext) { decl = go::getFirstDeclaration(context); //package name differs from directory, so get the real name if(!decl) continue; DUChainReadLocker lock; packageName = decl->qualifiedIdentifier(); } if(!decl) //contexts belongs to a different package continue; DUChainWriteLocker lock; if(firstContext) //only open declarations once per import(others are redundant) { setComment(decl->comment()); if(node->packageName) {//create alias for package QualifiedIdentifier id = identifierForNode(node->packageName); NamespaceAliasDeclaration* decl = openDeclaration(id, editorFindRange(node->importpath, 0)); decl->setKind(Declaration::NamespaceAlias); decl->setImportIdentifier(packageName); //this needs to be actual package name closeDeclaration(); }else if(node->dot != -1) {//anonymous import NamespaceAliasDeclaration* decl = openDeclaration(QualifiedIdentifier(globalImportIdentifier()), editorFindRange(node->importpath, 0)); decl->setKind(Declaration::NamespaceAlias); decl->setImportIdentifier(packageName); //this needs to be actual package name closeDeclaration(); }else { Declaration* decl = openDeclaration(packageName, editorFindRange(node->importpath, 0)); decl->setKind(Declaration::Import); closeDeclaration(); } } topContext()->addImportedParentContext(context.data()); firstContext = false; } DUChainWriteLocker lock; } void DeclarationBuilder::visitSourceFile(go::SourceFileAst* node) { setComment(m_session->commentBeforeToken(node->startToken)); DUChainWriteLocker lock; Declaration* packageDeclaration = openDeclaration(identifierForNode(node->packageClause->packageName), editorFindRange(node->packageClause->packageName, 0)); packageDeclaration->setKind(Declaration::Namespace); openContext(node, editorFindRange(node, 0), DUContext::Namespace, identifierForNode(node->packageClause->packageName)); packageDeclaration->setInternalContext(currentContext()); lock.unlock(); m_thisPackage = identifierForNode(node->packageClause->packageName); //import package this context belongs to importThisPackage(); importBuiltins(); go::DefaultVisitor::visitSourceFile(node); closeContext(); closeDeclaration(); } void DeclarationBuilder::importThisPackage() { QList contexts = m_session->contextForThisPackage(document()); if(contexts.empty()) return; for(const ReferencedTopDUContext& context : contexts) { if(context.data() == topContext()) continue; //import only contexts with the same package name DeclarationPointer decl = go::checkPackageDeclaration(m_thisPackage.last(), context); if(!decl) continue; //if our package doesn't have comment, but some file in out package does, copy it if(currentDeclaration()->comment().size() == 0 && decl->comment().size() != 0) currentDeclaration()->setComment(decl->comment()); DUChainWriteLocker lock; //TODO Since package names are identical duchain should find declarations without namespace alias, right? //NamespaceAliasDeclaration* import = openDeclaration(QualifiedIdentifier(globalImportIdentifier()), RangeInRevision()); //import->setKind(Declaration::NamespaceAlias); //import->setImportIdentifier(packageName); //this needs to be actual package name //closeDeclaration(); topContext()->addImportedParentContext(context.data()); } DUChainWriteLocker lock; } void DeclarationBuilder::importBuiltins() { QString builtinFile = go::Helper::getBuiltinFile(); if(builtinFile.isEmpty() || IndexedString(builtinFile) == document()) return; ReferencedTopDUContext builtins = go::Helper::getBuiltinContext(); if(!builtins) {//schedule this file for reparsing, if builtins were not parsed yet m_session->rescheduleThisFile(); }else { DUChainWriteLocker lock; //import builtins NamespaceAliasDeclaration* decl = openDeclaration(QualifiedIdentifier(globalImportIdentifier()), RangeInRevision()); decl->setKind(Declaration::NamespaceAlias); decl->setImportIdentifier(QualifiedIdentifier("_builtins")); closeDeclaration(); topContext()->addImportedParentContext(builtins); } } void DeclarationBuilder::visitForStmt(go::ForStmtAst* node) { openContext(node, editorFindRange(node, 0), DUContext::Other); //wrapper context if(node->range != -1 && node->autoassign != -1) {//manually infer types go::ExpressionVisitor exprVisitor(m_session, currentContext(), this); exprVisitor.visitRangeClause(node->rangeExpression); auto types = exprVisitor.lastTypes(); if(!types.empty()) { declareVariable(identifierAstFromExpressionAst(node->expression), types.first()); if(types.size() > 1 && node->expressionList) { int typeIndex = 1; auto iter = node->expressionList->expressionsSequence->front(), end = iter; do { if(typeIndex >= types.size()) //not enough types to declare all variables break; declareVariable(identifierAstFromExpressionAst(iter->element), types.at(typeIndex)); iter = iter->next; typeIndex++; } while (iter != end); } } } DeclarationBuilderBase::visitForStmt(node); closeContext(); } void DeclarationBuilder::visitSwitchStmt(go::SwitchStmtAst* node) { openContext(node, editorFindRange(node, 0), DUContext::Other); //wrapper context if(node->typeSwitchStatement && node->typeSwitchStatement->typeSwitchGuard) { go::TypeSwitchGuardAst* typeswitch = node->typeSwitchStatement->typeSwitchGuard; go::ExpressionVisitor expVisitor(m_session, currentContext(), this); expVisitor.visitPrimaryExpr(typeswitch->primaryExpr); if(!expVisitor.lastTypes().empty()) { declareVariable(typeswitch->ident, expVisitor.lastTypes().first()); m_switchTypeVariable = identifierForNode(typeswitch->ident); } } DeclarationBuilderBase::visitSwitchStmt(node); closeContext(); //wrapper context m_switchTypeVariable.clear(); } void DeclarationBuilder::visitTypeCaseClause(go::TypeCaseClauseAst* node) { openContext(node, editorFindRange(node, 0), DUContext::Other); const KDevPG::ListNode* typeIter = 0; if(node->typelistSequence) typeIter = node->typelistSequence->front(); if(node->defaultToken == -1 && typeIter && typeIter->next == typeIter) {//if default is not specified and only one type is listed //we open another declaration of listed type visitType(typeIter->element); lastType()->setModifiers(AbstractType::NoModifiers); DUChainWriteLocker lock; if(lastType()->toString() != "nil" && !m_switchTypeVariable.isEmpty()) {//in that case we also don't open declaration Declaration* decl = openDeclaration(m_switchTypeVariable, editorFindRange(typeIter->element, 0)); decl->setAbstractType(lastType()); closeDeclaration(); } } go::DefaultVisitor::visitTypeCaseClause(node); closeContext(); } void DeclarationBuilder::visitExprCaseClause(go::ExprCaseClauseAst* node) { openContext(node, editorFindRange(node, 0), DUContext::Other); go::DefaultVisitor::visitExprCaseClause(node); closeContext(); } void DeclarationBuilder::visitCommCase(go::CommCaseAst* node) { if(node->autoassign != -1) { go::ExpressionVisitor expVisitor(m_session, currentContext(), this); expVisitor.visitExpression(node->recvExp); auto types = expVisitor.lastTypes(); if(types.size() == 2) //expression must be a receive operator, returning two values { declareVariable(identifierAstFromExpressionAst(node->sendOrRecv), types.first()); if(node->expressionList) { auto secondVariable = node->expressionList->expressionsSequence->front(); declareVariable(identifierAstFromExpressionAst(secondVariable->element), types.at(1)); } } } DeclarationBuilderBase::visitCommCase(node); } void DeclarationBuilder::visitCommClause(go::CommClauseAst* node) { openContext(node, editorFindRange(node, 0), DUContext::Other); //wrapper context DeclarationBuilderBase::visitCommClause(node); closeContext(); } void DeclarationBuilder::visitTypeDecl(go::TypeDeclAst* node) { m_lastTypeComment = m_session->commentBeforeToken(node->startToken); go::DefaultVisitor::visitTypeDecl(node); m_lastTypeComment = QByteArray(); } go::GoFunctionDeclaration* DeclarationBuilder::declareFunction(go::IdentifierAst* id, const go::GoFunctionType::Ptr& type, DUContext* paramContext, DUContext* retparamContext, const QByteArray& comment) { setComment(comment); DUChainWriteLocker lock; go::GoFunctionDeclaration* dec = openDefinition(identifierForNode(id), editorFindRange(id, 0)); dec->setType(type); //dec->setKind(Declaration::Type); dec->setKind(Declaration::Instance); dec->setInternalContext(paramContext); if(retparamContext) dec->setReturnArgsContext(retparamContext); //dec->setInternalFunctionContext(bodyContext); closeDeclaration(); return dec; } diff --git a/duchain/builders/declarationbuilder.h b/duchain/builders/declarationbuilder.h index a664395..3d44a47 100644 --- a/duchain/builders/declarationbuilder.h +++ b/duchain/builders/declarationbuilder.h @@ -1,115 +1,116 @@ /************************************************************************************* * Copyright (C) 2014 by Pavel Petrushkov * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU 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 General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #ifndef KDEVGOLANGDECLBUILDER_H #define KDEVGOLANGDECLBUILDER_H #include #include #include "duchain/kdevgoduchain_export.h" #include "contextbuilder.h" #include "typebuilder.h" #include "parser/parsesession.h" #include "parser/goast.h" typedef KDevelop::AbstractDeclarationBuilder DeclarationBuilderBase; class KDEVGODUCHAIN_EXPORT DeclarationBuilder : public DeclarationBuilderBase { public: DeclarationBuilder(ParseSession* session, bool forExport); virtual KDevelop::ReferencedTopDUContext build(const KDevelop::IndexedString& url, go::AstNode* node, KDevelop::ReferencedTopDUContext updateContext = KDevelop::ReferencedTopDUContext()); virtual void startVisiting(go::AstNode* node); virtual void visitVarSpec(go::VarSpecAst* node); virtual void visitShortVarDecl(go::ShortVarDeclAst* node); virtual void visitConstSpec(go::ConstSpecAst* node); virtual void visitConstDecl(go::ConstDeclAst* node); virtual void visitFuncDeclaration(go::FuncDeclarationAst* node); virtual void visitMethodDeclaration(go::MethodDeclarationAst* node); virtual void visitTypeSpec(go::TypeSpecAst* node); virtual void visitImportSpec(go::ImportSpecAst* node); virtual void visitSourceFile(go::SourceFileAst* node); virtual void visitForStmt(go::ForStmtAst* node); virtual void visitSwitchStmt(go::SwitchStmtAst* node); virtual void visitTypeCaseClause(go::TypeCaseClauseAst* node); virtual void visitExprCaseClause(go::ExprCaseClauseAst* node); + virtual void visitPrimaryExpr(go::PrimaryExprAst *node); /** * this handles variable declaration in select statements, e.g. * select { case i := <-mychan: bla bla... } * NOTE: right hand side expression must be a receive operator, returning two values */ virtual void visitCommCase(go::CommCaseAst* node); virtual void visitCommClause(go::CommClauseAst* node); virtual void visitTypeDecl(go::TypeDeclAst* node); /*struct GoImport{ GoImport(bool anon, KDevelop::TopDUContext* ctx) : anonymous(anon), context(ctx) {} bool anonymous; KDevelop::TopDUContext* context; };*/ private: /** * Deduces types of expression with ExpressionVisitor and declares variables * from idList with respective types. If there is a single expression, returning multiple types * idList will get assigned those types. Otherwise we get only first type no matter how many of them * expression returns.(I believe this is how it works in Go, correct it if I'm wrong) * @param declareConstant whether to declare usual variables or constants */ void declareVariables(go::IdentifierAst* id, go::IdListAst* idList, go::ExpressionAst* expression, go::ExpressionListAst* expressionList, bool declareConstant); /** * declares variables or constants with names from id and idList of type type. */ void declareVariablesWithType(go::IdentifierAst* id, go::IdListAst* idList, go::TypeAst* type, bool declareConstant); /** * Declares variable with identifier @param id of type @param type **/ virtual void declareVariable(go::IdentifierAst* id, const AbstractType::Ptr& type) override; /** * Declares GoFunction and assigns contexts to it. * Called from typebuilder when building functions and methods **/ virtual go::GoFunctionDeclaration* declareFunction(go::IdentifierAst* id, const go::GoFunctionType::Ptr& type, DUContext* paramContext, DUContext* retparamContext, const QByteArray& comment=QByteArray()) override; void importThisPackage(); void importBuiltins(); bool m_export; //QHash m_anonymous_imports; bool m_preBuilding; QList m_constAutoTypes; QualifiedIdentifier m_thisPackage; QualifiedIdentifier m_switchTypeVariable; QByteArray m_lastTypeComment, m_lastConstComment; }; #endif \ No newline at end of file diff --git a/duchain/tests/testduchain.cpp b/duchain/tests/testduchain.cpp index d9c2ffe..b2fef7e 100644 --- a/duchain/tests/testduchain.cpp +++ b/duchain/tests/testduchain.cpp @@ -1,627 +1,655 @@ /************************************************************************************* * Copyright (C) 2014 by Pavel Petrushkov * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU 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 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 "testduchain.h" #include "parser/parsesession.h" #include "builders/declarationbuilder.h" #include "types/gointegraltype.h" #include "helper.h" #include //#include #include #include #include QTEST_MAIN(TestDuchain); using namespace KDevelop; DUContext* getPackageContext(const QString& code); DUContext* getMainContext(const QString& code); void TestDuchain::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); //build builtins file, because it won't be scheduled automatically QString builtinsFile = go::Helper::getBuiltinFile(); QFile f(builtinsFile); f.open(QIODevice::ReadOnly); QByteArray contents = f.readAll(); f.close(); ParseSession session(QString(contents).toUtf8(), 0); session.setCurrentDocument(IndexedString(builtinsFile)); if(session.startParsing()) { DeclarationBuilder builder(&session, false); builder.build(session.currentDocument(), session.ast()); } } void TestDuchain::cleanupTestCase() { TestCore::shutdown(); } void TestDuchain::sanityCheck() { QString code("package main; func main() {}"); ParseSession session(code.toUtf8(), 0); session.setCurrentDocument(IndexedString("file:///temp/1")); QVERIFY(session.startParsing()); DeclarationBuilder builder(&session, false); ReferencedTopDUContext context = builder.build(session.currentDocument(), session.ast()); QVERIFY(context.data()); DUChainReadLocker lock; auto decls = context->localDeclarations(); QCOMPARE(decls.size(), 1); Declaration* packageDeclaration = decls.first(); QVERIFY(packageDeclaration); DUContext* packageContext = packageDeclaration->internalContext(); QVERIFY(packageContext); decls = packageContext->localDeclarations(); QCOMPARE(decls.size(), 2); Declaration* funcDeclaration = decls.at(1); QVERIFY(funcDeclaration); QCOMPARE(funcDeclaration->identifier().toString(), QString("main")); } void TestDuchain::builtinFunctions_data() { QTest::addColumn("expression"); QTest::addColumn("type"); QTest::newRow("make slice") << "make([]int)" << "int[]"; QTest::newRow("make slice 2") << "make([]mytype)" << "main::mytype[]"; QTest::newRow("make chan") << "make(chan float64)" << "chan float64"; QTest::newRow("make chan 2") << "make(chan unknown)" << "chan unknown"; QTest::newRow("make map") << "make(map[uint]rune)" << "map[uint]rune"; QTest::newRow("make map 2") << "make(map[string]mytype)" << "map[string]main::mytype"; QTest::newRow("new") << "new(map[byte]mytype)" << "map[byte]main::mytype*"; QTest::newRow("new 2") << "new(mytype)" << "main::mytype*"; QTest::newRow("new 2") << "new([]unknown)" << "unknown[]*"; QTest::newRow("append") << "append([]int{0}, 1, 2)" << "int[]"; QTest::newRow("append 2") << "append(myslice, \"a\")" << "string[]"; QTest::newRow("cap") << "cap(myvar)" << "int"; QTest::newRow("copy") << "copy(a, b)" << "int"; QTest::newRow("len") << "len(array)" << "int"; QTest::newRow("real") << "real(5i)" << "float64"; QTest::newRow("real 2") << "real(myvar)" << "float64"; QTest::newRow("imag") << "imag(2i)" << "float64"; QTest::newRow("imag 2") << "imag(myvar)" << "float64"; QTest::newRow("imag 3") << "imag(2+3i)" << "float64"; QTest::newRow("complex") << "complex(-1, 0)" << "complex128"; QTest::newRow("recover") << "recover()" << "interface {}"; QTest::newRow("real + complex") << "real(complex(1, 1))" << "float64"; } void TestDuchain::builtinFunctions() { QFETCH(QString, expression); QFETCH(QString, type); QString code(QString("package main; type mytype int; var myvar complex64; var myslice []string; func main() { \ testvar := %1; }").arg(expression)); DUContext* context = getMainContext(code); QVERIFY(context); DUChainReadLocker lock; auto decls = context->findDeclarations(QualifiedIdentifier("testvar")); QCOMPARE(decls.size(), 1); Declaration* decl = decls.first(); AbstractType::Ptr result = decl->abstractType(); QCOMPARE(result->toString(), type); } void TestDuchain::test_declareVariables() { QString code("package main; func multitest() (int, bool) { return 1, true; } \n \ func singletest() rune { return 'a'; } \n func main() { test1, test2 := multitest(); \ test3, test4 := singletest(), 3., 100; var test5, test6, test7 = multitest(), singletest(); var _, test8, _ = 0., 5, true;}"); DUContext* context = getMainContext(code); DUChainReadLocker lock; Declaration* decl = context->findDeclarations(QualifiedIdentifier("test1")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeInt)); QVERIFY((decl->abstractType()->modifiers() & AbstractType::NoModifiers) == AbstractType::NoModifiers); decl = context->findDeclarations(QualifiedIdentifier("test2")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeBool)); decl = context->findDeclarations(QualifiedIdentifier("test3")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeRune)); decl = context->findDeclarations(QualifiedIdentifier("test4")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeFloat64)); decl = context->findDeclarations(QualifiedIdentifier("test5")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeInt)); decl = context->findDeclarations(QualifiedIdentifier("test6")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeRune)); auto declarations = context->findDeclarations(QualifiedIdentifier("test7")); QCOMPARE(declarations.size(), 0); decl = context->findDeclarations(QualifiedIdentifier("test8")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeInt)); declarations = context->findDeclarations(QualifiedIdentifier("_")); QCOMPARE(declarations.size(), 0); } +void TestDuchain::test_declareVariablesInParametersOfNestedFunction() +{ + QString code("package main; func main() {\n" + "a := func(test1 int) { return test1 }\n" + "func(test2 int) { return test2 } (5)\n" + "pass(func(test3 int) {return test3 })\n}\n"); + auto mainContext = getMainContext(code); + DUChainReadLocker lock; + + auto firstFunctionContext = mainContext->childContexts().at(0); + auto declarations = firstFunctionContext->findDeclarations(QualifiedIdentifier("test1")); + QCOMPARE(declarations.size(), 1); + auto declaration = declarations.first(); + QCOMPARE(fastCast(declaration->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeInt)); + + auto secondFunctionContext = mainContext->childContexts().at(2); + declarations = secondFunctionContext->findDeclarations(QualifiedIdentifier("test2")); + QCOMPARE(declarations.size(), 1); + declaration = declarations.first(); + QCOMPARE(fastCast(declaration->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeInt)); + + auto thirdFunctionContext = mainContext->childContexts().at(4); + declarations = thirdFunctionContext->findDeclarations(QualifiedIdentifier("test3")); + QCOMPARE(declarations.size(), 1); + declaration = declarations.first(); + QCOMPARE(fastCast(declaration->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeInt)); +} + void TestDuchain::test_constants() { QString code("package main; const const1, const2 float32 = 1, 2; const ( const3, const4, const5 = 'a', 3, \"abc\"; ); "); DUContext* context = getPackageContext(code); DUChainReadLocker lock; Declaration* decl = context->findDeclarations(QualifiedIdentifier("const1")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeFloat32)); QVERIFY(decl->abstractType()->modifiers() & AbstractType::ConstModifier); decl = context->findDeclarations(QualifiedIdentifier("const2")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeFloat32)); QVERIFY(decl->abstractType()->modifiers() & AbstractType::ConstModifier); decl = context->findDeclarations(QualifiedIdentifier("const3")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeRune)); QVERIFY(decl->abstractType()->modifiers() & AbstractType::ConstModifier); decl = context->findDeclarations(QualifiedIdentifier("const4")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeInt)); QVERIFY(decl->abstractType()->modifiers() & AbstractType::ConstModifier); decl = context->findDeclarations(QualifiedIdentifier("const5")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeString)); QVERIFY(decl->abstractType()->modifiers() & AbstractType::ConstModifier); } void TestDuchain::test_constants_omittedType() { QString code("package main; const ( const1, const2 uint = 1, 2; const3, const4; ); const ( const5, const6 = 3, 4.; const7, const8; ); const const9;"); DUContext* context = getPackageContext(code); DUChainReadLocker lock; Declaration* decl = context->findDeclarations(QualifiedIdentifier("const1")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeUint)); QVERIFY(decl->abstractType()->modifiers() & AbstractType::ConstModifier); decl = context->findDeclarations(QualifiedIdentifier("const2")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeUint)); QVERIFY(decl->abstractType()->modifiers() & AbstractType::ConstModifier); decl = context->findDeclarations(QualifiedIdentifier("const3")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeUint)); QVERIFY(decl->abstractType()->modifiers() & AbstractType::ConstModifier); decl = context->findDeclarations(QualifiedIdentifier("const4")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeUint)); QVERIFY(decl->abstractType()->modifiers() & AbstractType::ConstModifier); decl = context->findDeclarations(QualifiedIdentifier("const5")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeInt)); QVERIFY(decl->abstractType()->modifiers() & AbstractType::ConstModifier); decl = context->findDeclarations(QualifiedIdentifier("const6")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeFloat64)); QVERIFY(decl->abstractType()->modifiers() & AbstractType::ConstModifier); decl = context->findDeclarations(QualifiedIdentifier("const7")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeInt)); QVERIFY(decl->abstractType()->modifiers() & AbstractType::ConstModifier); decl = context->findDeclarations(QualifiedIdentifier("const8")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeFloat64)); QVERIFY(decl->abstractType()->modifiers() & AbstractType::ConstModifier); QCOMPARE(context->findDeclarations(QualifiedIdentifier("const9")).size(), 0); } void TestDuchain::test_indexexpressions_data() { QTest::addColumn("vardecl"); QTest::addColumn("indexexpr"); QTest::addColumn("type"); QTest::addColumn("mapaccess"); QTest::newRow("array index") << "var array [5]int" << "array[1]" << "int" << false; QTest::newRow("slice index") << "var slice []string" << "slice[4]" << "string" << false; QTest::newRow("array pointer index") << "var array *[5]int" << "array[1]" << "int" << false; QTest::newRow("string index") << "var str string" << "str[4]" << "byte" << false; QTest::newRow("map index") << "var mymap map[int][]string" << "mymap[2*2]" << "string[]" << true; QTest::newRow("map index 2") << "var mymap map[rune]*mytype" << "mymap[\'o\']" << "main::mytype*" << true; QTest::newRow("slice expression") << "var slice []int" << "slice[1:4]" << "int[]" << false; QTest::newRow("slice expression 2") << "var slice []int" << "slice[:4]" << "int[]" << false; QTest::newRow("slice expression 3") << "var slice []int" << "slice[1:]" << "int[]" << false; QTest::newRow("slice expression 4") << "var slice []int" << "slice[:]" << "int[]" << false; QTest::newRow("array expression") << "var array [5-3]bool" << "array[0:2]" << "bool[]" << false; QTest::newRow("string expression") << "var str string" << "str[0:]" << "string" << false; QTest::newRow("array pointer expression") << "var array *[]bool" << "array[0:2]" << "bool[]" << false; QTest::newRow("full slice expression") << "var slice []int" << "slice[1:3:5]" << "int[]" << false; QTest::newRow("full slice expression 2") << "var slice []mytype" << "slice[:3:5]" << "main::mytype[]" << false; } void TestDuchain::test_indexexpressions() { QFETCH(QString, vardecl); QFETCH(QString, indexexpr); QFETCH(QString, type); QString code(QString("package main; type mytype int; func main() { %1; testvar, ok := %2; }").arg(vardecl).arg(indexexpr)); DUContext* context = getMainContext(code); QVERIFY(context); DUChainReadLocker lock; auto decls = context->findDeclarations(QualifiedIdentifier("testvar")); QCOMPARE(decls.size(), 1); Declaration* decl = decls.first(); AbstractType::Ptr result = decl->abstractType(); QCOMPARE(result->toString(), type); QFETCH(bool, mapaccess); decls = context->findDeclarations(QualifiedIdentifier("ok")); if(mapaccess) { QCOMPARE(decls.size(), 1); decl = decls.first(); result = decl->abstractType(); QCOMPARE(result->toString(), QString("bool")); }else QCOMPARE(decls.size(), 0); } void TestDuchain::test_ifcontexts() { QString code("package main; func main() { var test1 int; if test2:=0; true { var test3 int; } else if false { } else { var test4 int; } }"); DUContext* context = getMainContext(code); DUChainReadLocker lock; QCOMPARE(context->findDeclarations(QualifiedIdentifier("test1")).size(), 1); QCOMPARE(context->findDeclarations(QualifiedIdentifier("test2")).size(), 0); QCOMPARE(context->findDeclarations(QualifiedIdentifier("test3")).size(), 0); QCOMPARE(context->findDeclarations(QualifiedIdentifier("test4")).size(), 0); DUContext* childContext = context->findContextAt(CursorInRevision(0, 65)); //if block context QVERIFY(context); QCOMPARE(childContext->findDeclarations(QualifiedIdentifier("test1")).size(), 1); QCOMPARE(childContext->findDeclarations(QualifiedIdentifier("test2")).size(), 1); QCOMPARE(childContext->findDeclarations(QualifiedIdentifier("test3")).size(), 1); QCOMPARE(childContext->findDeclarations(QualifiedIdentifier("test4")).size(), 0); childContext = context->findContextAt(CursorInRevision(0, 97)); //else-if block context QVERIFY(context); QCOMPARE(childContext->findDeclarations(QualifiedIdentifier("test1")).size(), 1); QCOMPARE(childContext->findDeclarations(QualifiedIdentifier("test2")).size(), 1); QCOMPARE(childContext->findDeclarations(QualifiedIdentifier("test3")).size(), 0); QCOMPARE(childContext->findDeclarations(QualifiedIdentifier("test4")).size(), 0); childContext = context->findContextAt(CursorInRevision(0, 116)); //else-else block context QVERIFY(context); QCOMPARE(childContext->findDeclarations(QualifiedIdentifier("test1")).size(), 1); QCOMPARE(childContext->findDeclarations(QualifiedIdentifier("test2")).size(), 1); QCOMPARE(childContext->findDeclarations(QualifiedIdentifier("test3")).size(), 0); QCOMPARE(childContext->findDeclarations(QualifiedIdentifier("test4")).size(), 1); } void TestDuchain::test_funccontexts() { QString code("package main; type mytype int; func (i mytype) main(a, b string, c float64) (d, e int) { var f mytype; }"); DUContext* context = getPackageContext(code); QVERIFY(context); DUChainReadLocker lock; auto decls = context->findDeclarations(QualifiedIdentifier("mytype::main")); QCOMPARE(decls.size(), 1); AbstractFunctionDeclaration* function = dynamic_cast(decls.first()); QVERIFY(function); context = function->internalFunctionContext(); QCOMPARE(context->localDeclarations().size(), 1); QCOMPARE(context->findDeclarations(QualifiedIdentifier("i")).size(), 1); QCOMPARE(context->findDeclarations(QualifiedIdentifier("a")).size(), 1); QCOMPARE(context->findDeclarations(QualifiedIdentifier("b")).size(), 1); QCOMPARE(context->findDeclarations(QualifiedIdentifier("c")).size(), 1); QCOMPARE(context->findDeclarations(QualifiedIdentifier("d")).size(), 1); QCOMPARE(context->findDeclarations(QualifiedIdentifier("e")).size(), 1); QCOMPARE(context->findDeclarations(QualifiedIdentifier("f")).size(), 1); } void TestDuchain::test_rangeclause_data() { QTest::addColumn("vardecl"); QTest::addColumn("rangeexpr"); QTest::addColumn("type1"); QTest::addColumn("type2"); QTest::newRow("array range") << "var array [5]int" << "array" << "int" << "int"; QTest::newRow("slice range") << "var slice []string" << "slice" << "int" << "string"; QTest::newRow("array pointer range") << "var longestshittyname *[]string" << "longestshittyname" << "int" << "string"; QTest::newRow("string range") << "var str string" << "str" << "int" << "rune"; QTest::newRow("map range") << "var mymap map[int][]string" << "mymap" << "int" << "string[]"; QTest::newRow("map range 2") << "var mymap map[rune]*mytype" << "mymap" << "rune" << "main::mytype*"; QTest::newRow("chan range") << "var mychan chan *mytype" << "mychan" << "main::mytype*" << "nil"; QTest::newRow("chan range 2") << "var mychan <- chan []int" << "mychan" << "int[]" << "nil"; QTest::newRow("chan range 3") << "var mychan chan <- struct{ b int}" << "mychan" << "struct{ b int}" << "nil"; QTest::newRow("named type") << "var myvar mytype" << "myvar" << "int" << "float64"; QTest::newRow("named type pointer") << "var myvar *mytype" << "myvar" << "int" << "float64"; } void TestDuchain::test_rangeclause() { QFETCH(QString, vardecl); QFETCH(QString, rangeexpr); QFETCH(QString, type1); QFETCH(QString, type2); QString code(QString("package main; type mytype []float64; func main() { %1; for test, test2 := range %2 { } }").arg(vardecl).arg(rangeexpr)); DUContext* context = getMainContext(code); QVERIFY(context); DUChainReadLocker lock; context = context->findContextAt(CursorInRevision(0, 90)); QVERIFY(context); auto decls = context->findDeclarations(QualifiedIdentifier("test")); QCOMPARE(decls.size(), 1); Declaration* decl = decls.first(); AbstractType::Ptr result = decl->abstractType(); QCOMPARE(result->toString(), type1); if(type2 != "nil") { decls = context->findDeclarations(QualifiedIdentifier("test2")); QCOMPARE(decls.size(), 1); Declaration* decl = decls.first(); result = decl->abstractType(); QCOMPARE(result->toString(), type2); }else { QCOMPARE(context->findDeclarations(QualifiedIdentifier("test2")).size(), 0); } } void TestDuchain::test_typeswitch() { QString code("package main; type mytype int; func main() { var test1 int \n \ switch test2:=2; test3:=test1.(type) { case rune: test4:=4. \n \ case func(int) string: test5:=\'a\' \n \ case nil: \n \ case byte, float32: \n \ default: \n } }"); DUContext* context = getMainContext(code); QVERIFY(context); DUChainReadLocker lock; //DUContext* ctx = context->findContextAt(CursorInRevision(1, 0)); // QVERIFY(context); QCOMPARE(context->findDeclarations(QualifiedIdentifier("test1")).first()->abstractType()->toString(), QString("int")); QCOMPARE(context->findDeclarations(QualifiedIdentifier("test2")).size(), 0); QCOMPARE(context->findDeclarations(QualifiedIdentifier("test3")).size(), 0); QCOMPARE(context->findDeclarations(QualifiedIdentifier("test4")).size(), 0); QCOMPARE(context->findDeclarations(QualifiedIdentifier("test5")).size(), 0); DUContext* ctx = context->findContextAt(CursorInRevision(1, 61)); //first case QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test1")).first()->abstractType()->toString(), QString("int")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test2")).first()->abstractType()->toString(), QString("int")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test3")).first()->abstractType()->toString(), QString("rune")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test4")).first()->abstractType()->toString(), QString("float64")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test5")).size(), 0); ctx = context->findContextAt(CursorInRevision(2, 30)); //second case QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test1")).first()->abstractType()->toString(), QString("int")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test2")).first()->abstractType()->toString(), QString("int")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test3")).first()->abstractType()->toString(), QString("function (int) string")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test4")).size(), 0); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test5")).first()->abstractType()->toString(), QString("rune")); ctx = context->findContextAt(CursorInRevision(3, 30)); //third case QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test1")).first()->abstractType()->toString(), QString("int")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test2")).first()->abstractType()->toString(), QString("int")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test3")).first()->abstractType()->toString(), QString("int")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test4")).size(), 0); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test5")).size(), 0); ctx = context->findContextAt(CursorInRevision(4, 30)); //fourth case QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test1")).first()->abstractType()->toString(), QString("int")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test2")).first()->abstractType()->toString(), QString("int")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test3")).first()->abstractType()->toString(), QString("int")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test4")).size(), 0); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test5")).size(), 0); ctx = context->findContextAt(CursorInRevision(5, 30)); //fifth case QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test1")).first()->abstractType()->toString(), QString("int")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test2")).first()->abstractType()->toString(), QString("int")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test3")).first()->abstractType()->toString(), QString("int")); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test4")).size(), 0); QCOMPARE(ctx->findDeclarations(QualifiedIdentifier("test5")).size(), 0); } void TestDuchain::test_funcparams_data() { QTest::addColumn("params"); QTest::addColumn("result"); QTest::newRow("zero params") << "()" << "function () "; QTest::newRow("simple param") << "(a int)" << "function (int) "; QTest::newRow("unnamed param") << "(int)" << "function (int) "; QTest::newRow("unknown param") << "(unknown)" << "function (unknown) "; QTest::newRow("complex param") << "([]int)" << "function (int[]) "; QTest::newRow("complex params") << "(*int, [10]rune)" << "function (int*, rune[]) "; QTest::newRow("two separate params") << "(a int, b float32)" << "function (int, float32) "; QTest::newRow("two combined params") << "(a, b rune)" << "function (rune, rune) "; QTest::newRow("two complex params") << "(a, b chan <- rune)" << "function (chan <- rune, chan <- rune) "; QTest::newRow("three params") << "(_, c, d float32)" << "function (float32, float32, float32) "; QTest::newRow("three separate params") << "(a []uint8, b *rune, c interface{})" << "function (uint8[], rune*, interface{}) "; QTest::newRow("return param") << "(test []mytype) *byte" << "function (main::mytype[]) byte*"; QTest::newRow("multiple return params") << "(chan mytype) (b, e string)" << "function (chan main::mytype) (string, string)"; QTest::newRow("multiple return params") << "(<- chan func (string)) (mytype, mytype)" << "function (<- chan function (string) ) (main::mytype, main::mytype)"; QTest::newRow("three types") << "(a, b, int)" << "function (a, b, int) "; QTest::newRow("three types 2") << "(a, b, []int)" << "function (a, b, int[]) "; QTest::newRow("three types 3") << "(a, _ int, z float32) bool" << "function (int, int, float32) bool"; QTest::newRow("return func type") << "(n int) func(p *T)" << "function (int) function (T*) "; QTest::newRow("variadic param") << "(a ...int) mytype" << "function (int[]) main::mytype"; QTest::newRow("variadic param 2") << "(a, b int, z float64, opt ...interface{}) (success bool)" << "function (int, int, float64, interface{}[]) bool"; } void TestDuchain::test_funcparams() { QFETCH(QString, params); QFETCH(QString, result); QString code(QString("package main; type mytype int; func main%1 { }").arg(params)); DUContext* context = getPackageContext(code); QVERIFY(context); DUChainReadLocker lock; Declaration* decl = context->findDeclarations(QualifiedIdentifier("main::main")).first(); QVERIFY(decl); go::GoFunctionType::Ptr func = decl->abstractType().cast(); QVERIFY(func); QCOMPARE(func->toString(), result); } void TestDuchain::test_literals_data() { QTest::addColumn("expr"); QTest::addColumn("type"); QTest::newRow("basic literal") << "1" << "int"; QTest::newRow("basic literal 2") << ".2" << "float64"; QTest::newRow("basic literal 3") << "1E6i" << "complex128"; QTest::newRow("basic literal 4") << "\'y\'" << "rune"; QTest::newRow("basic literal 5") << "\"str\"" << "string"; QTest::newRow("named type literal") << "mytype{2}" << "main::mytype"; QTest::newRow("named type conversion") << "mytype(2)" << "main::mytype"; //not call QTest::newRow("named type conversion 2") << "(mytype)(2)" << "main::mytype";//not paren expression with resolve QTest::newRow("named type literal 2") << "unknown{2}" << "unknown"; QTest::newRow("struct type literal") << "struct { a int }{2}" << "struct { a int }"; QTest::newRow("struct type literal 2") << "struct { f func(); b rune }{f:main, b:3}" << "struct { f func(); b rune }"; QTest::newRow("struct type conversion") << "struct { mytype }(100)" << "struct { mytype }"; QTest::newRow("struct type access") << "struct { c rune }{}.c" << "rune"; QTest::newRow("array type literal") << "[10]int{1, 2, 3}" << "int[]"; QTest::newRow("slice type conversion") << "[]mytype(anotherslice)" << "main::mytype[]"; QTest::newRow("slice special form") << "[...]float32{-1., 0., 1.}" << "float32[]"; QTest::newRow("map type literal") << "map[int]rune{}" << "map[int]rune"; QTest::newRow("map type conversion") << "(map[int]rune)(f)" << "map[int]rune"; QTest::newRow("map type conversion access") << "(map[int]rune)(f)[0]" << "rune"; QTest::newRow("func type literal") << "func() int { return 0; }" << "function () int"; QTest::newRow("func type literal 2") << "func(a, b mytype) (b bool) { c := 2; return true; }" << "function (main::mytype, main::mytype) bool"; QTest::newRow("func type literal 3") << "func() int { var b int; }" << "function () int"; QTest::newRow("func type conversion") << "(func())(main) " << "function () "; QTest::newRow("func type conversion 2") << "func() (i int)(main) " << "function () int"; QTest::newRow("func type call") << "func(f []int) float64 {} ( []int{1, 2} )" << "float64"; QTest::newRow("pointer type conversion") << "(*mytype)(v) " << "main::mytype*"; QTest::newRow("pointer type conversion 2") << "(*unnamed)(v) " << "unnamed*"; QTest::newRow("interface type conversion") << "interface{}(main) " << "interface{}"; QTest::newRow("interface type conversion 2") << "interface{ Read(a int) string }(Reader) " << "interface{ Read(a int) string }"; QTest::newRow("chan") << "<-chan int(c) " << "int"; QTest::newRow("chan conversion") << "(<-chan int)(c) " << "<- chan int"; QTest::newRow("chan conversion 2") << "chan<- mytype(tt) " << "chan <- main::mytype"; } void TestDuchain::test_literals() { QFETCH(QString, expr); QFETCH(QString, type); QString code(QString("package main; type mytype int; func main() { test := %1 }").arg(expr)); DUContext* context = getMainContext(code); QVERIFY(context); DUChainReadLocker lock; auto decls = context->findDeclarations(QualifiedIdentifier("test")); QCOMPARE(decls.size(), 1); QCOMPARE(decls.first()->abstractType()->toString(), type); } void TestDuchain::test_unaryOps_data() { QTest::addColumn("pointer"); QTest::addColumn("expr"); QTest::addColumn("result"); QTest::newRow("pointer") << "new(int)" << "*pointer" << "int"; QTest::newRow("pointer 2") << "new(mytype)" << "*pointer" << "main::mytype"; QTest::newRow("address") << "int(2)" << "&pointer" << "int*"; QTest::newRow("address 2") << "mytype{2}" << "&pointer" << "main::mytype*"; QTest::newRow("chan") << "make(chan rune)" << "<- pointer" << "rune"; QTest::newRow("chan 2") << "make(<- chan []mytype)" << "<-pointer" << "main::mytype[]"; QTest::newRow("not") << "1 != 2" << "! pointer" << "bool"; QTest::newRow("func test") << "func () float32 { return 1; }" << "(pointer)()" << "float32"; QTest::newRow("func test 2") << "func (a float64) float64 { return a; }" << "(pointer)(2.0)" << "float64"; QTest::newRow("func test 3") << "func (a, b float64) float64 { return a; }" << "(pointer)(2.0, 3)" << "float64"; } void TestDuchain::test_unaryOps() { QFETCH(QString, pointer); QFETCH(QString, expr); QFETCH(QString, result); QString code(QString("package main; type mytype int; func main() { pointer := %1; test := %2 }").arg(pointer).arg(expr)); DUContext* context = getMainContext(code); QVERIFY(context); DUChainReadLocker lock; auto decls = context->findDeclarations(QualifiedIdentifier("test")); QCOMPARE(decls.size(), 1); QCOMPARE(decls.first()->abstractType()->toString(), result); } void TestDuchain::test_typeAssertions() { QString code("package main; type mytype int; func main() { var a int; test := a.(float64); test2 := a.(*uint); \ test3 := a.(map[int]string); test4 := a.([]rune); test5 := a.(mytype)}"); DUContext* context = getMainContext(code); DUChainReadLocker lock; Declaration* decl = context->findDeclarations(QualifiedIdentifier("test")).first(); QCOMPARE(fastCast(decl->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeFloat64)); decl = context->findDeclarations(QualifiedIdentifier("test2")).first(); QCOMPARE(decl->abstractType()->toString(), QString("uint*")); decl = context->findDeclarations(QualifiedIdentifier("test3")).first(); QCOMPARE(decl->abstractType()->toString(), QString("map[int]string")); decl = context->findDeclarations(QualifiedIdentifier("test4")).first(); QCOMPARE(decl->abstractType()->toString(), QString("rune[]")); decl = context->findDeclarations(QualifiedIdentifier("test5")).first(); QCOMPARE(decl->abstractType()->toString(), QString("main::mytype")); } void TestDuchain::test_selectCases() { QString code("package main; type mytype int; func main() { mychan := make(chan mytype, 1); select { case test, ok := <- mychan: mychan <- mytype{1}; } }"); DUContext* context = getMainContext(code); DUChainReadLocker lock; QCOMPARE(context->findDeclarations(QualifiedIdentifier("test")).size(), 0); QCOMPARE(context->findDeclarations(QualifiedIdentifier("ok")).size(), 0); context = context->findContextAt(CursorInRevision(0, 120)); Declaration* decl = context->findDeclarations(QualifiedIdentifier("test")).first(); Declaration* ok = context->findDeclarations(QualifiedIdentifier("ok")).first(); QCOMPARE(decl->abstractType()->toString(), QString("main::mytype")); QCOMPARE(fastCast(ok->abstractType().constData())->dataType(), uint(go::GoIntegralType::TypeBool)); } DUContext* getPackageContext(const QString& code) { ParseSession session(code.toUtf8(), 0); static int testNumber = 0; session.setCurrentDocument(IndexedString(QString("file:///temp/%1").arg(testNumber++))); if(!session.startParsing()) return 0; DeclarationBuilder builder(&session, false); ReferencedTopDUContext context = builder.build(session.currentDocument(), session.ast()); if(!context) return 0; DUChainReadLocker lock; auto decls = context->localDeclarations(); if(decls.size() != 1) return 0; Declaration* packageDeclaration = decls.first(); DUContext* packageContext = packageDeclaration->internalContext(); return packageContext; } DUContext* getMainContext(const QString& code) { DUContext* package = getPackageContext(code); if(!package) return 0; DUChainReadLocker lock; auto decls = package->findDeclarations(QualifiedIdentifier("main")); if(decls.size() == 0) return 0; AbstractFunctionDeclaration* function = dynamic_cast(decls.first()); if(!function) return 0; return function->internalFunctionContext(); } diff --git a/duchain/tests/testduchain.h b/duchain/tests/testduchain.h index 33b6695..1481bbb 100644 --- a/duchain/tests/testduchain.h +++ b/duchain/tests/testduchain.h @@ -1,54 +1,55 @@ /************************************************************************************* * Copyright (C) 2014 by Pavel Petrushkov * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU 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 General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA * *************************************************************************************/ #ifndef GOLANGTESTDUCHAIN_H #define GOLANGTESTDUCHAIN_H #include class TestDuchain : public QObject { Q_OBJECT private slots: void initTestCase(); void sanityCheck(); void cleanupTestCase(); void builtinFunctions_data(); void builtinFunctions(); void test_declareVariables(); void test_constants(); void test_constants_omittedType(); void test_indexexpressions_data(); void test_indexexpressions(); void test_ifcontexts(); void test_funccontexts(); void test_rangeclause_data(); void test_rangeclause(); void test_typeswitch(); void test_funcparams_data(); void test_funcparams(); void test_literals_data(); void test_literals(); void test_unaryOps_data(); void test_unaryOps(); void test_typeAssertions(); void test_selectCases(); + void test_declareVariablesInParametersOfNestedFunction(); }; #endif \ No newline at end of file