diff --git a/codecompletion/context.cpp b/codecompletion/context.cpp index fbad034..63be26f 100644 --- a/codecompletion/context.cpp +++ b/codecompletion/context.cpp @@ -1,463 +1,453 @@ /************************************************************************************* * 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 * *************************************************************************************/ //Completion code is mostly based on KDevelop QmlJS plugin which should be referenced for more details amd comments #include "context.h" #include #include +#include #include "parser/golexer.h" #include "parser/goparser.h" #include "expressionvisitor.h" #include "types/gostructuretype.h" #include "items/completionitem.h" #include "items/functionitem.h" #include "items/importcompletionitem.h" #include "helper.h" #include "completiondebug.h" #include "types/gofunctiontype.h" using namespace KDevelop; namespace go { CodeCompletionContext::CodeCompletionContext(const KDevelop::DUContextPointer& context, const QString& text, const KDevelop::CursorInRevision& position, int depth): KDevelop::CodeCompletionContext(context, extractLastLine(text), position, depth), m_fullText(text) { } QList< CompletionTreeItemPointer > CodeCompletionContext::completionItems(bool& abort, bool fullCompletion) { qCDebug(COMPLETION) << m_text; QList items; //We shouldn't need anything before last semicolon (previous statements) if(m_text.lastIndexOf(';') != -1) m_text = m_text.mid(m_text.lastIndexOf(';')); //"import" + [optional package alias] + [opening double quote] + [cursor at EOL] if(m_text.contains(QRegExp("import[^\"]*\"[^\"]*$"))) { items << importCompletion(); return items; } if(isInsideCommentOrString()) return items; items << functionCallTips(); QChar lastChar = m_text.size() > 0 ? m_text.at(m_text.size() - 1) : QLatin1Char('\0'); if(lastChar == QLatin1Char('.')) {//imports and member completions items << importAndMemberCompletion(); }else { items << normalCompletion(); } return items; } QList< CompletionTreeItemPointer > CodeCompletionContext::normalCompletion() { //all declarations QList items; DUChainReadLocker lock; auto declarations = m_duContext->allDeclarations(CursorInRevision::invalid(), m_duContext->topContext()); for(const QPair &decl : declarations) { if(decl.first->topContext() != m_duContext->topContext()) continue; if(decl.first->identifier() == globalImportIdentifier() || decl.first->identifier() == globalAliasIdentifier() || decl.first->identifier() == Identifier()) continue; items << itemForDeclaration(decl); } return items; } QList< CompletionTreeItemPointer > CodeCompletionContext::functionCallTips() { QStack stack = expressionStack(m_text); QList items; int depth = 1; bool isTopOfStack = true; while(!stack.empty()) { ExpressionStackEntry entry = stack.pop(); if(isTopOfStack && entry.operatorStart > entry.startPosition) { //for type matching in things like a = %CURSOR //see kdev-qmljs for details //TODO type matching in multiple assignments e.g. "a, b = c, %CURSOR" //don't show matching in var declarations e.g. "a := b" //and expression lists e.g. "a(), b() if(m_text.mid(entry.operatorStart, entry.operatorEnd-entry.operatorStart) != "," && m_text.mid(entry.operatorStart, entry.operatorEnd-entry.operatorStart) != ":=") { AbstractType::Ptr type = lastType(m_text.left(entry.operatorStart)); if(type) m_typeToMatch = type; } } if (entry.startPosition > 0 && m_text.at(entry.startPosition - 1) == QLatin1Char('(')) { DeclarationPointer function = lastDeclaration(m_text.left(entry.startPosition - 1)); if(function && fastCast(function->abstractType().constData())) { FunctionCompletionItem* item = new FunctionCompletionItem(function, depth, entry.commas); depth++; items << CompletionTreeItemPointer(item); if(isTopOfStack && !m_typeToMatch) { GoFunctionType::Ptr ftype(fastCast(function->abstractType().constData())); auto args = ftype->arguments(); if(args.count() != 0) { int argument = entry.commas >= args.count() ? args.count()-1 : entry.commas; m_typeToMatch = args.at(argument); } } } } isTopOfStack = false; } return items; } +QList CodeCompletionContext::getImportableDeclarations(Declaration *sourceDeclaration) +{ + QList items; + auto declarations = getDeclarations(sourceDeclaration->qualifiedIdentifier(), m_duContext.data()); + for(Declaration* declaration : declarations) + { + DUContext* context = declaration->internalContext(); + if(!context) continue; + auto innerDeclarations = context->allDeclarations(CursorInRevision::invalid(), sourceDeclaration->topContext(), false); + for(const QPair innerDeclaration : innerDeclarations) + { + if(innerDeclaration.first == declaration) + continue; + QualifiedIdentifier fullname = innerDeclaration.first->qualifiedIdentifier(); + Identifier identifier = fullname.last(); + if(identifier.toString().size() <= 0) + continue; + //import only declarations that start with capital letter(Go language rule) + if(m_duContext->topContext() != declaration->topContext()) + if(!identifier.toString().at(0).isLetter() || (identifier.toString().at(0) != identifier.toString().at(0).toUpper())) + continue; + items << itemForDeclaration(innerDeclaration); + } + } + return items; +} QList< CompletionTreeItemPointer > CodeCompletionContext::importAndMemberCompletion() { QList items; - AbstractType::Ptr lasttype = lastType(m_text.left(m_text.size()-1)); - if(lasttype) + AbstractType::Ptr type = lastType(m_text.left(m_text.size()-1)); + + if(type) { - //evaluate pointers - if(fastCast(lasttype.constData())) - { - DUChainReadLocker lock; - PointerType* ptype = fastCast(lasttype.constData()); - if(ptype->baseType()) - lasttype = ptype->baseType(); - } - if(fastCast(lasttype.constData())) - {//we have to look for namespace declarations - //TODO handle namespace aliases - DUChainReadLocker lock; - Declaration* lastdeclaration = fastCast(lasttype.constData())->declaration(m_duContext->topContext()); - //if(lastdeclaration->kind() == Declaration::Namespace) - //{ - //namespace could be splitted into multiple contexts - //auto decls = m_duContext->findDeclarations(lastdeclaration->qualifiedIdentifier()); - auto decls = getDeclarations(lastdeclaration->qualifiedIdentifier(), m_duContext.data()); - for(Declaration* declaration : decls) - { - DUContext* context = declaration->internalContext(); - if(!context) continue; - auto declarations = context->allDeclarations(CursorInRevision::invalid(), declaration->topContext(), false); - for(const QPair decl : declarations) - { - if(decl.first == declaration) - continue; - QualifiedIdentifier fullname = decl.first->qualifiedIdentifier(); - Identifier ident = fullname.last(); - if(ident.toString().size() <= 0) - continue; - //import only declarations that start with capital letter(Go language rule) - if(m_duContext->topContext() != declaration->topContext()) - if(!ident.toString().at(0).isLetter() || (ident.toString().at(0) != ident.toString().at(0).toUpper())) - continue; - items << itemForDeclaration(decl); - } - } - // } - } - //this construction will descend through type hierarchy till it hits basic types - //e.g. type mystruct struct{}; type mystruct2 mystruct; ... - int count = 0; - do { - count++; - GoStructureType* structure = fastCast(lasttype.constData()); - if(structure) - {//get members - DUContext* context = structure->context(); - DUChainReadLocker lock; - //auto declarations = context->findDeclarations(identifierForNode(node->selector)); - auto declarations = context->allDeclarations(CursorInRevision::invalid(), m_duContext->topContext(), false); - lock.unlock(); - for(const QPair &decl : declarations) - { - items << itemForDeclaration(decl); - } - } - StructureType* identType = fastCast(lasttype.constData()); - if(identType) - { - DUChainReadLocker lock; - lasttype = identType->declaration(m_duContext->topContext())->abstractType(); - }else - break; - - }while(lasttype && count<100); + if(auto ptype = fastCast(type.constData())) + { + DUChainReadLocker lock; + if(ptype->baseType()) + { + type = ptype->baseType(); + } + } + if(auto structure = fastCast(type.constData())) + { + DUChainReadLocker lock; + Declaration* declaration = structure->declaration(m_duContext->topContext()); + if(declaration) + { + items << getImportableDeclarations(declaration); + } + } + if(auto structure = fastCast(type.constData())) + { + DUContext* context = structure->context(); + DUChainReadLocker lock; + + auto declarations = context->allDeclarations(CursorInRevision::invalid(), m_duContext->topContext(), false); + lock.unlock(); + for(const QPair &decl : declarations) + { + items << itemForDeclaration(decl); + } + } } return items; } QList CodeCompletionContext::importCompletion() { auto searchPaths = Helper::getSearchPaths(); QList items; QString fullPath = m_text.mid(m_text.lastIndexOf('"')+1); //import "parentPackage/childPackage" QStringList pathChain = fullPath.split('/', QString::SkipEmptyParts); qCDebug(COMPLETION) << pathChain; for(const QString& path : searchPaths) { QDir dir(path); if(dir.exists()) { bool isValid = true; for(const QString& nextDir : pathChain) { isValid = dir.cd(nextDir); if(!isValid) break; } if(!dir.exists() || !isValid) continue; for(const QString& package : dir.entryList(QDir::Dirs)) { if(package.startsWith('.')) continue; items << CompletionTreeItemPointer(new ImportCompletionItem(package)); } } } return items; } QStack< CodeCompletionContext::ExpressionStackEntry > CodeCompletionContext::expressionStack(const QString& expression) { //for details see similar function in QmlJS KDevelop plugin QStack stack; QByteArray expr(expression.toUtf8()); KDevPG::QByteArrayIterator iter(expr); Lexer lexer(iter); bool atEnd=false; ExpressionStackEntry entry; entry.startPosition = 0; entry.operatorStart = 0; entry.operatorEnd = 0; entry.commas = 0; stack.push(entry); qint64 line, lineEnd, column, columnEnd; while(!atEnd) { KDevPG::Token token(lexer.read()); switch(token.kind) { case Parser::Token_EOF: atEnd=true; break; case Parser::Token_LBRACE: case Parser::Token_LBRACKET: case Parser::Token_LPAREN: qint64 sline, scolumn; lexer.locationTable()->positionAt(token.begin, &sline, &scolumn); entry.startPosition = scolumn+1; entry.operatorStart = entry.startPosition; entry.operatorEnd = entry.startPosition; entry.commas = 0; stack.push(entry); break; case Parser::Token_RBRACE: case Parser::Token_RBRACKET: case Parser::Token_RPAREN: if (stack.count() > 1) { stack.pop(); } break; case Parser::Token_IDENT: //temporary hack to allow completion in variable declarations //two identifiers in a row is not possible? if(lexer.size() > 0 && lexer.at(lexer.index()-2).kind == Parser::Token_IDENT) { lexer.locationTable()->positionAt(lexer.at(lexer.index()-2).begin, &line, &column); lexer.locationTable()->positionAt(lexer.at(lexer.index()-2).end+1, &lineEnd, &columnEnd); stack.top().operatorStart = column; stack.top().operatorEnd = columnEnd; } break; case Parser::Token_DOT: break; case Parser::Token_COMMA: stack.top().commas++; default: // The last operator of every sub-expression is stored on the stack // so that "A = foo." can know that attributes of foo having the same // type as A should be highlighted. qCDebug(COMPLETION) << token.kind; lexer.locationTable()->positionAt(token.begin, &line, &column); lexer.locationTable()->positionAt(token.end+1, &lineEnd, &columnEnd); stack.top().operatorStart = column; stack.top().operatorEnd = columnEnd; } } return stack; } AbstractType::Ptr CodeCompletionContext::lastType(const QString& expression) { QStack stack = expressionStack(expression); QString lastExpression(expression.mid(stack.top().operatorEnd)); qCDebug(COMPLETION) << lastExpression; ParseSession session(lastExpression.toUtf8(), 0, false); ExpressionAst* expressionAst; if(!session.parseExpression(&expressionAst)) return AbstractType::Ptr(); ExpressionVisitor expVisitor(&session, this->m_duContext.data()); expVisitor.visitExpression(expressionAst); if(expVisitor.lastTypes().size() != 0) { AbstractType::Ptr type = expVisitor.lastTypes().first(); return type; } return AbstractType::Ptr(); } DeclarationPointer CodeCompletionContext::lastDeclaration(const QString& expression) { QStack stack = expressionStack(expression); QString lastExpression(expression.mid(stack.top().operatorEnd)); qCDebug(COMPLETION) << lastExpression; ParseSession session(lastExpression.toUtf8(), 0, false); ExpressionAst* expressionAst; if(!session.parseExpression(&expressionAst)) return DeclarationPointer(); ExpressionVisitor expVisitor(&session, this->m_duContext.data()); expVisitor.visitExpression(expressionAst); if(expVisitor.lastDeclaration()) return expVisitor.lastDeclaration(); return DeclarationPointer(); } CompletionTreeItemPointer CodeCompletionContext::itemForDeclaration(QPair declaration) { if(declaration.first->isFunctionDeclaration()) return CompletionTreeItemPointer(new FunctionCompletionItem(DeclarationPointer(declaration.first))); return CompletionTreeItemPointer(new go::CompletionItem(DeclarationPointer(declaration.first), QExplicitlySharedDataPointer(), declaration.second)); } bool CodeCompletionContext::isInsideCommentOrString() { bool inLineComment = false; bool inComment = false; bool inQuotes = false; bool inDoubleQuotes = false; bool inBackQuotes = false; QString text = ' ' + m_fullText; for(int index = 0; index < text.size()-1; ++index) { const QChar c = text.at(index); const QChar next = text.at(index + 1); if(inLineComment) { if(c == QLatin1Char('\n')) { inLineComment = false; continue; } } if(inComment) { if(c == QLatin1Char('*') && next == QLatin1Char('/')) { inComment = false; continue; } } else if(inQuotes) { if(c != QLatin1Char('\\') && next == QLatin1Char('\'')) { inQuotes = false; continue; } } else if(inDoubleQuotes) { if(c != QLatin1Char('\\') && next == QLatin1Char('\"')) { inDoubleQuotes = false; continue; } } else if(inBackQuotes) { if(c != QLatin1Char('\\') && next == QLatin1Char('\`')) { inBackQuotes = false; continue; } } else { if(c == QLatin1Char('/') && next == QLatin1Char('/')) inLineComment = true; if(c == QLatin1Char('/') && next == QLatin1Char('*')) inComment = true; if(next == QLatin1Char('\'')) inQuotes = true; if(next == QLatin1Char('\"')) inDoubleQuotes = true; if(next == QLatin1Char('\`')) inBackQuotes = true; } } if(inLineComment || inComment || inQuotes || inDoubleQuotes || inBackQuotes) return true; return false; } } \ No newline at end of file diff --git a/codecompletion/context.h b/codecompletion/context.h index 920dd84..4ee4f03 100644 --- a/codecompletion/context.h +++ b/codecompletion/context.h @@ -1,83 +1,85 @@ /************************************************************************************* * 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 GOLANGCOMPLETIONCONTEXT_H #define GOLANGCOMPLETIONCONTEXT_H #include #include #include "kdevgocompletion_export.h" #include #include namespace go { class KDEVGOCOMPLETION_EXPORT CodeCompletionContext : public KDevelop::CodeCompletionContext { public: CodeCompletionContext(const KDevelop::DUContextPointer& context, const QString& text, const KDevelop::CursorInRevision& position, int depth = 0); virtual QList completionItems(bool& abort, bool fullCompletion = true); KDevelop::AbstractType::Ptr typeToMatch() { return m_typeToMatch; } private: //See QmlJS plugin completion for details struct ExpressionStackEntry { int startPosition; int operatorStart; int operatorEnd; int commas; }; QStack expressionStack(const QString& expression); KDevelop::AbstractType::Ptr lastType(const QString& expression); KDevelop::DeclarationPointer lastDeclaration(const QString& expression); + QList getImportableDeclarations(KDevelop::Declaration *sourceDeclaration); + QList importAndMemberCompletion(); QList normalCompletion(); /** * Creates FunctionCallTips and sets m_typeToMatch **/ QList functionCallTips(); QList importCompletion(); /** * Return completion item for declaration. **/ KDevelop::CompletionTreeItemPointer itemForDeclaration(QPair declaration); /** * returns true if cursor is in comment and completion is not needed **/ bool isInsideCommentOrString(); KDevelop::AbstractType::Ptr m_typeToMatch; QString m_fullText; }; } #endif \ No newline at end of file diff --git a/codecompletion/tests/testcompletion.cpp b/codecompletion/tests/testcompletion.cpp index 1db9b61..39b260c 100644 --- a/codecompletion/tests/testcompletion.cpp +++ b/codecompletion/tests/testcompletion.cpp @@ -1,256 +1,258 @@ /************************************************************************************* * 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 "testcompletion.h" #include "context.h" #include "model.h" #include "parser/parsesession.h" #include "builders/declarationbuilder.h" #include #include #include #include #include #include using namespace KDevelop; QTEST_MAIN(TestCompletion); QStandardItemModel& fakeModel() { static QStandardItemModel model; model.setColumnCount(10); model.setRowCount(10); return model; } go::CodeCompletionModel* model = 0; 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; } QList getCompletions(QString code) { int cursorIndex = code.indexOf("%CURSOR"); Q_ASSERT(cursorIndex != -1); DUContext* context = getPackageContext(code.replace(cursorIndex, 7, "")); CursorInRevision cursor(0, cursorIndex); DUChainReadLocker lock; context = context->findContextAt(cursor); Q_ASSERT(context); go::CodeCompletionContext* completion = new go::CodeCompletionContext(DUContextPointer(context), code.mid(0, cursorIndex), cursor); model = new go::CodeCompletionModel(nullptr); model->setCompletionContext(QExplicitlySharedDataPointer(completion)); bool abort = false; return completion->completionItems(abort, true); } void debugPrint(const QList& completions, bool showQuality=false) { qDebug() << "Completions debug print"; QModelIndex prefixIdx = fakeModel().index(0, KDevelop::CodeCompletionModel::Prefix); QModelIndex nameIdx = fakeModel().index(0, KDevelop::CodeCompletionModel::Name); QModelIndex argsIdx = fakeModel().index(0, KDevelop::CodeCompletionModel::Arguments); for(const CompletionTreeItemPointer item : completions) { QStringList name; name << item->data(prefixIdx, Qt::DisplayRole, nullptr).toString(); name << item->data(nameIdx, Qt::DisplayRole, nullptr).toString(); name << item->data(argsIdx, Qt::DisplayRole, nullptr).toString(); if(!showQuality) qDebug() << name.join(' '); else { int quality = item->data(nameIdx, KDevelop::CodeCompletionModel::MatchQuality, model).toInt(); qDebug() << name.join(' ') << quality; } } } bool containsDeclaration(QString declaration, const QList& completions, int depth=-1, int quality=-1) { QModelIndex prefixIdx = fakeModel().index(0, KDevelop::CodeCompletionModel::Prefix); QModelIndex nameIdx = fakeModel().index(0, KDevelop::CodeCompletionModel::Name); QModelIndex argsIdx = fakeModel().index(0, KDevelop::CodeCompletionModel::Arguments); for(const CompletionTreeItemPointer item : completions) { //if(item->declaration()->toString() == declaration) QStringList name; name << item->data(prefixIdx, Qt::DisplayRole, nullptr).toString(); name << item->data(nameIdx, Qt::DisplayRole, nullptr).toString(); name << item->data(argsIdx, Qt::DisplayRole, nullptr).toString(); int itemQuality = item->data(nameIdx, KDevelop::CodeCompletionModel::MatchQuality, model).toInt(); if(name.join(' ') == declaration && (depth == -1 || item->argumentHintDepth() == depth) && (quality == -1 || quality == itemQuality) ) return true; } //print completions if we haven't found anything debugPrint(completions, quality == -1 ? false : true); return false; } void TestCompletion::initTestCase() { AutoTestShell::init(); TestCore::initialize(Core::NoUi); } void TestCompletion::cleanupTestCase() { TestCore::shutdown(); } void TestCompletion::test_basicCompletion() { QString code("package main; func main() { var a int; %CURSOR }"); auto completions = getCompletions(code); QCOMPARE(completions.size(), 3); QVERIFY(containsDeclaration(" main ()", completions)); //function QVERIFY(containsDeclaration("namespace main ", completions)); //package QVERIFY(containsDeclaration("int a ", completions)); //variable } void TestCompletion::test_functionCallTips_data() { QTest::addColumn("declaration"); QTest::addColumn("expression"); QTest::addColumn("result"); QTest::addColumn("depth"); QTest::addColumn("size"); QTest::newRow("normal") << "func myfunc() {};" << "myfunc(%CURSOR)" << " myfunc ()" << 1 << 4; QTest::newRow("args") << "func myfunc(a int) {};" << "myfunc(%CURSOR)" << " myfunc (int a)" << 1 << 4; QTest::newRow("args 2") << "type mytype int; func myfunc(a, b rune, c mytype) {};" << "myfunc(%CURSOR)" << " myfunc (rune a, rune b, main::mytype c)" << 1 << 5; QTest::newRow("rargs") << "func myfunc(a int) float32 {};" << "myfunc(%CURSOR)" << "float32 myfunc (int a)" << 1 << 4; QTest::newRow("rargs 2") << "func myfunc(a int) (float32, bool) {};" << "myfunc(%CURSOR)" << "float32, bool myfunc (int a)" << 1 << 4; QTest::newRow("rargs 3") << "func myfunc(a int) (r rune, b bool) {};" << "myfunc(%CURSOR)" << "rune r, bool b myfunc (int a)" << 1 << 4; QTest::newRow("variadic args") << "func myfunc(a ...int) {};" << "myfunc(%CURSOR)" << " myfunc (int... a)" << 1 << 4; QTest::newRow("variadic args 2") << "func myfunc(a int, i ...interface{}) {};" << "myfunc(%CURSOR)" << " myfunc (int a, interface{}... i)" << 1 << 4; QTest::newRow("var") << "var myvar func(a int);" << "myvar(%CURSOR)" << " myvar (int)" << 1 << 4; QTest::newRow("var 2") << "func functest(t int) rune;" << "myvar := functest; myvar(%CURSOR)" << "rune myvar (int)" << 1 << 5; QTest::newRow("struct") << "type mytype struct { f func(t int) rune; };" << "var myvar mytype; myvar.f(%CURSOR)" << "rune f (int)" << 1 << 5; - QTest::newRow("method") << "type mytype int; func (m mytype) myfunc(c rune) {};" << "var myvar mytype; myvar.myfunc(%CURSOR)" << " myfunc (rune c)" << 1 << 6; + QTest::newRow("method") << "type mytype int; func (m mytype) myfunc(c rune) {};" << "var myvar mytype; myvar.myfunc(%CURSOR)" << " myfunc (rune c)" << 1 << 5; + QTest::newRow("method of embedded struct declared after method") << "type mytype struct {}; func (m mytype) myfunc(c rune) {}; type mytype2 struct {*mytype};" << "var myvar mytype2; myvar.myfunc(%CURSOR)" << " myfunc (rune c)" << 1 << 6; + QTest::newRow("method of embedded struct declared before method") << "type mytype struct {}; func (m mytype) myfunc(c rune) {}; type mytype2 struct {*mytype};" << "var myvar mytype2; myvar.myfunc(%CURSOR)" << " myfunc (rune c)" << 1 << 6; QTest::newRow("paren") << "func myfunc(a int) {};" << "(myfunc)(%CURSOR)" << " myfunc (int a)" << 1 << 4; QTest::newRow("nested") << "func myfunc(a int) {};" << "f := myfunc; myfunc(f(%CURSOR))" << " myfunc (int a)" << 2 << 6; QTest::newRow("nested 2") << "func myfunc(a int) {};" << "f := myfunc; myfunc(f(%CURSOR))" << " f (int)" << 1 << 6; //TODO these don't work yet(they still pass, but they're wrong) //QTest::newRow("lambda") << "" << "f := func() int { \n}(%CURSOR)" << "int ()" << 0 << 3; QTest::newRow("multiple lines") << "func myfunc(a, b int) {};" << "myfunc(5, \n %CURSOR)" << " myfunc (int a, int b)" << 0 << 3; } void TestCompletion::test_functionCallTips() { QFETCH(QString, declaration); QFETCH(QString, expression); QFETCH(QString, result); QFETCH(int, depth); QFETCH(int, size); QString code(QString("package main; %1 func main() { %2 }").arg(declaration).arg(expression)); auto completions = getCompletions(code); //debugPrint(completions); QCOMPARE(completions.size(), size); QVERIFY(containsDeclaration(result, completions, depth)); } void TestCompletion::test_typeMatching_data() { QTest::addColumn("declaration"); QTest::addColumn("expression"); QTest::addColumn("result"); QTest::addColumn("size"); QTest::addColumn("quality"); QTest::newRow("assignment") << "var pi int = 3.14" << "var a int; a = %CURSOR a" << "int pi " << 4 << 10; QTest::newRow("const") << "const pi int = 3.14" << "var a int; a = %CURSOR a" << "int pi " << 4 << 10; QTest::newRow("function") << "func test() int {}" << "var a int; a = %CURSOR a" << "int test ()" << 4 << 10; QTest::newRow("function var") << "func test() int {}" << "f := test; var a int; a = %CURSOR a" << "function () int f " << 5 << 10; QTest::newRow("function type") << "func test() int {}" << "var a func() int; a = %CURSOR a" << "int test ()" << 4 << 10; QTest::newRow("function type var") << "func test() int {}" << "f := test; var a func() int; a = %CURSOR a" << "function () int f " << 5 << 10; QTest::newRow("arguments") << "func test(a int) {}" << "var a int; test(%CURSOR);" << "int a " << 5 << 10; QTest::newRow("sum") << "var a int" << "var b int; b + %CURSOR c" << "int a " << 4 << 10; //QTest::newRow("multiple assignments") << "var a int = 1; var b bool = true" << "a, b = %CURSOR a, b" << "int a " << 4 << 10; //QTest::newRow("multiple assignments 2") << "var a int = 1; var b bool = true" << "a, b = a, %CURSOR b" << "bool b " << 4 << 10; } void TestCompletion::test_typeMatching() { QFETCH(QString, declaration); QFETCH(QString, expression); QFETCH(QString, result); QFETCH(int, size); QFETCH(int, quality); QString code(QString("package main; %1; func main() { %2 }").arg(declaration).arg(expression)); auto completions = getCompletions(code); QCOMPARE(completions.size(), size); QVERIFY(containsDeclaration(result, completions, -1, quality)); } void TestCompletion::test_commentCompletion_data() { QTest::addColumn("expression"); QTest::addColumn("size"); QTest::newRow("line comment") << "// %CURSOR \n" << 0; QTest::newRow("line comment 2") << "// \n %CURSOR" << 2; QTest::newRow("comment") << "/* \"asdf \" %CURSOR */" << 0; QTest::newRow("comment 2") << "/* \'asdf \' */ %CURSOR" << 2; QTest::newRow("comment 3") << "/* \` %CURSOR */" << 0; QTest::newRow("comment 4") << "a := \"/* \` \" %CURSOR " << 3; QTest::newRow("string") << " a := \" %CURSOR \"" << 0; QTest::newRow("string 2") << " a := \" \" %CURSOR" << 3; QTest::newRow("string 3") << " a := \` %CURSOR \`" << 0; QTest::newRow("string 4") << " a := \` \` %CURSOR" << 3; QTest::newRow("rune") << " a := \' %CURSOR\'" << 0; QTest::newRow("rune 2") << " a := \'a\' %CURSOR" << 3; } void TestCompletion::test_commentCompletion() { QFETCH(QString, expression); QFETCH(int, size); QString code(QString("package main; func main() { %1 }").arg(expression)); auto completions = getCompletions(code); QCOMPARE(completions.size(), size); } diff --git a/duchain/builders/declarationbuilder.cpp b/duchain/builders/declarationbuilder.cpp index ca3a9b7..ba5307b 100644 --- a/duchain/builders/declarationbuilder.cpp +++ b/duchain/builders/declarationbuilder.cpp @@ -1,607 +1,654 @@ /************************************************************************************* * 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) { buildFunction(node->signature, node->body, node->funcName, m_session->commentBeforeToken(node->startToken-1)); } 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; + bool openedDeclaration = false; + 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()); + + QList declarations = currentContext()->findLocalDeclarations(identifierForNode(actualtype).last(), CursorInRevision::invalid(), this->topContext()); + + if(declarations.size() >= 1) + { + declaration = declarations.at(0); + } + else + { + openedDeclaration = true; + declaration = openDeclaration(identifierForNode(actualtype), editorFindRange(actualtype, 0)); + declaration->setKind(Declaration::Type); + } + + if(declaration->internalContext()) + { + openContext(declaration->internalContext()); + } + else + { + openContext(node, editorFindRange(node, 0), DUContext::Namespace, identifierForNode(actualtype)); + declaration->setInternalContext(currentContext()); + } } auto functionDeclaration = buildFunction(node->signature, node->body, node->methodName, m_session->commentBeforeToken(node->startToken-1)); if(node->methodRecv->type && functionDeclaration->internalContext()) {//declare method receiver variable('this' or 'self' analog in Go) openContext(functionDeclaration->internalContext()); 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(); closeContext(); } closeContext(); //namespace - closeDeclaration(); //namespace declaration + if(openedDeclaration) + { + closeDeclaration(); + } } 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; + ClassDeclaration* decl; { DUChainWriteLocker lock; - decl = openDeclaration(identifierForNode(node->name), editorFindRange(node->name, 0)); + 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); + decl->setInternalContext(lastContext()); + if(node->type && node->type->complexType && node->type->complexType->structType && node->type->complexType->structType->fieldDeclSequence) + { + auto sequence = node->type->complexType->structType->fieldDeclSequence; + for(int i = 0; icount(); ++i) + { + auto anonymousField = sequence->at(i)->element->anonFieldStar; + if(anonymousField) + { + visitTypeName(anonymousField->typeName); + + StructureType::Ptr baseClassType = lastType().cast(); + if(baseClassType) + { + auto baseClassDeclaration = baseClassType->declaration(topContext()); + if(baseClassDeclaration && baseClassDeclaration->internalContext()) + { + decl->internalContext()->addImportedParentContext(baseClassType->declaration(topContext())->internalContext()); + } + } + } + } + } 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, DUContext* bodyContext) { setComment(comment); DUChainWriteLocker lock; go::GoFunctionDeclaration* dec = openDefinition(identifierForNode(id), editorFindRange(id, 0)); dec->setType(type); dec->setKind(Declaration::Instance); dec->setInternalContext(bodyContext); if(bodyContext) { if(paramContext) { bodyContext->addImportedParentContext(paramContext); dec->setInternalFunctionContext(paramContext); } if(retparamContext) { bodyContext->addImportedParentContext(retparamContext); dec->setReturnArgsContext(retparamContext); } } closeDeclaration(); return dec; } go::GoFunctionDeclaration* DeclarationBuilder::buildFunction(go::SignatureAst* node, go::BlockAst* block, go::IdentifierAst* name, const QByteArray& comment) { DUContext* bodyContext = nullptr; if(block) { visitBlock(block); bodyContext = lastContext(); } return parseSignature(node, true, bodyContext, name, comment); } \ No newline at end of file