diff --git a/codecompletion/context.cpp b/codecompletion/context.cpp index 64777cf..cd6f537 100644 --- a/codecompletion/context.cpp +++ b/codecompletion/context.cpp @@ -1,576 +1,576 @@ /************************************************************************************* * 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 #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" using namespace KDevelop; namespace go { - + CodeCompletionContext::CodeCompletionContext(const KDevelop::DUContextPointer& context, - const QString& text, - const KDevelop::CursorInRevision& position, int depth): + const QString& text, + const KDevelop::CursorInRevision& position, int depth): KDevelop::CodeCompletionContext(context, extractLastExpression(text), position, depth), m_fullText(text) { } bool CodeCompletionContext::isImportAndMemberCompletion() { int pos = m_text.size() - 1; QChar lastChar; do { lastChar = m_text.size() > 0 ? m_text.at(pos) : QLatin1Char('\0'); --pos; } while((lastChar == QLatin1Char(' ') || lastChar == QLatin1Char('\n') || lastChar == QLatin1Char('\t')) && pos >= 0); return lastChar == QLatin1Char('.'); } QList< CompletionTreeItemPointer > CodeCompletionContext::completionItems(bool& abort, bool fullCompletion) { - (void)abort; - (void)fullCompletion; + Q_UNUSED(abort); + Q_UNUSED(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; setTypeToMatch(); items << functionCallTips(); items << (isImportAndMemberCompletion() ? importAndMemberCompletion() : 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 (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_typeMatch.singleType()) { 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_typeMatch.setSingleType(args.at(argument)); } } } } isTopOfStack = false; } return items; } void CodeCompletionContext::setTypeToMatch() { QStack stack = expressionStack(m_text); if(stack.empty()) { return; } ExpressionStackEntry entry = stack.pop(); if(entry.operatorStart <= entry.startPosition) { // handle indexing a[%CURSOR] if(entry.startPosition > 0 && m_text.at(entry.startPosition - 1) == QLatin1Char('[')) { auto declEntry = stack.pop(); auto declText = m_text.mid(declEntry.startPosition, entry.startPosition - 1 - declEntry.startPosition); auto type = lastType(declText); if(fastCast(type.data())) { m_typeMatch.setSingleType(GoIntegralType::Ptr(new GoIntegralType(GoIntegralType::TypeInt))); } else if(auto map = fastCast(type.data())) { auto keyType = map->keyType(); m_typeMatch.setSingleType(map->keyType()); } } return; } auto operatorText = m_text.mid(entry.operatorStart, entry.operatorEnd-entry.operatorStart); if(operatorText == "=" || operatorText == ":=") { auto leftText = m_text.mid(entry.startPosition, entry.operatorStart - entry.startPosition); auto rightText = m_text.mid(entry.operatorEnd); // On left side we have comma-separated declarations while on right side we can have function calls // whose can have extra commas. Since entry.commas contains *all* commas in *current* expression, // (e.g. commas inside function calls will not be counted) use it and amount of commas on left side to determine // amount of commas on right side. auto pos = entry.commas - leftText.count(','); auto declarations = leftText.split(','); if(pos == 0 && declarations.size() > 1) { QList types; for(auto i = 0; i < declarations.size(); ++i) { types.append(lastType(declarations[i])); } m_typeMatch.setMultipleTypes(types); } if(declarations.size() > pos) { if(auto type = lastType(declarations[pos])) { m_typeMatch.setSingleType(type); } } } else if(operatorText == "<-") { auto leftText = m_text.left(entry.operatorStart); if(auto type = lastType(leftText)) { if(auto channelType = fastCast(type.data())) { if(channelType->valueType()) { m_typeMatch.setSingleType(channelType->valueType()); } } } } //don't show matching in var declarations e.g. "a := b" //and expression lists e.g. "a(), b() else if(operatorText != ",") { if(auto type = lastType(m_text.left(entry.operatorStart))) { m_typeMatch.setSingleType(type); } } } 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; int pos = m_text.size(); QChar lastChar; do { --pos; lastChar = m_text.size() > 0 ? m_text.at(pos) : QLatin1Char('\0'); } while(lastChar != QLatin1Char('.') && lastChar != QLatin1Char('\0')); AbstractType::Ptr type = lastType(m_text.left(pos)); if(type) { 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 = dynamic_cast(type.data())) { 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? + //two identifiers in a row is not possible? if(lexer.size() > 1 && 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++; break; 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('\"')) + 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('\"')) + if(next == QLatin1Char('"')) inDoubleQuotes = true; if(next == QLatin1Char('`')) inBackQuotes = true; } } if(inLineComment || inComment || inQuotes || inDoubleQuotes || inBackQuotes) return true; return false; } QString CodeCompletionContext::extractLastExpression(const QString &str) { QString result; int prevLineEnd = str.lastIndexOf('\n'); if(prevLineEnd != -1) { result = str.mid(prevLineEnd+1); if(prevLineEnd - 1 >= 0) { auto previousText = str.left(prevLineEnd); if(endsWithDot(previousText)) { result = extractLastExpression(previousText) + "\n" + result; } } } else { result = str; } return result; } bool CodeCompletionContext::endsWithDot(const QString &str) { auto dotPos = str.lastIndexOf('.'); if(dotPos == -1) { return false; } for(auto i = dotPos + 1; i < str.size(); ++i) { if(!str[i].isSpace()) { return false; } } return true; } } diff --git a/codecompletion/items/completionitem.cpp b/codecompletion/items/completionitem.cpp index 62010f3..e52129b 100644 --- a/codecompletion/items/completionitem.cpp +++ b/codecompletion/items/completionitem.cpp @@ -1,156 +1,156 @@ /************************************************************************************* * 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 "completionitem.h" #include #include #include #include #include "context.h" #include "types/gofunctiontype.h" namespace go { CompletionItem::CompletionItem(KDevelop::DeclarationPointer decl, QExplicitlySharedDataPointer< KDevelop::CodeCompletionContext > context, int inheritanceDepth): NormalDeclarationCompletionItem(decl, QExplicitlySharedDataPointer(), 0), m_prefix("") { - (void)context; - (void)inheritanceDepth; + Q_UNUSED(context); + Q_UNUSED(inheritanceDepth); DUChainReadLocker lock; if(!decl) return; //NormalDeclarationCompletionItem fails to get a meaningful prefix in these cases if(decl->abstractType() && decl->abstractType()->whichType() == KDevelop::AbstractType::TypeFunction) m_prefix = decl->abstractType()->toString(); if(decl->kind() == KDevelop::Declaration::Import || decl->kind() == KDevelop::Declaration::NamespaceAlias) m_prefix = "namespace"; } QVariant CompletionItem::data(const QModelIndex& index, int role, const KDevelop::CodeCompletionModel* model) const { switch(role) { case Qt::DisplayRole: { if (index.column() == CodeCompletionModel::Prefix && m_prefix != "") { return m_prefix; } break; } case CodeCompletionModel::BestMatchesCount: return 5; case CodeCompletionModel::MatchQuality: { if(!declaration()) return QVariant(); //type aliases are actually different types if(declaration()->isTypeAlias()) return QVariant(); auto codeCompletionContext = static_cast(model->completionContext().data()); if(!codeCompletionContext->typeToMatch().singleType()) { return QVariant(); } AbstractType::Ptr typeToMatch = codeCompletionContext->typeToMatch().singleType(); if(!typeToMatch) return QVariant(); AbstractType::Ptr declType = declaration()->abstractType(); if (!declType) return QVariant(); //ignore constants typeToMatch->setModifiers(AbstractType::NoModifiers); declType->setModifiers(AbstractType::NoModifiers); if(declType->equals(typeToMatch.constData())) { return 5; } else if(auto gochanTypeToMatch = fastCast(typeToMatch.data())) { // Handle special cases - "Type" placeholder in builtin functions and passing a bidirectional channel in place of single-directional. if(auto gochanType = fastCast(declType.data())) { auto valueTypeToMatch = gochanTypeToMatch->valueType(); bool isTypePlaceholder = valueTypeToMatch->toString() == "Type" && valueTypeToMatch->whichType() == AbstractType::TypeDelayed; if(isTypePlaceholder) { valueTypeToMatch = gochanType->valueType(); } auto valueTypesMatches = valueTypeToMatch->equals(gochanType->valueType().constData()); auto channelTypeMatches = gochanTypeToMatch->kind() == gochanType->kind() || gochanType->kind() == GoChanType::SendAndReceive; if(valueTypesMatches && channelTypeMatches) { return 5; } } } else if(declType->whichType() == AbstractType::TypeFunction) { GoFunctionType* function = fastCast(declType.constData()); auto args = function->returnArguments(); if(args.size() != 0) { auto multipleTypesMatch = codeCompletionContext->typeToMatch().multipleTypes(); if(args.size() == multipleTypesMatch.size()) { bool allTypesMatches = true; for(auto pos = 0; pos < args.size(); ++pos) { auto expected = multipleTypesMatch[pos]; auto actual = args[pos]; expected->setModifiers(AbstractType::NoModifiers); actual->setModifiers(AbstractType::NoModifiers); bool isSkipped; bool isDelayed; { DUChainReadLocker lock; isSkipped = expected->toString() == "_"; isDelayed = expected->whichType() == AbstractType::TypeDelayed; } if(!actual->equals(expected.constData()) && !isSkipped && !isDelayed) { allTypesMatches = false; break; } } if(allTypesMatches) { return 10; } } AbstractType::Ptr first = args.first(); first->setModifiers(AbstractType::NoModifiers); if(args.size() == 1 && first->equals(typeToMatch.constData())) { return 5; } } } else { return QVariant(); } } } return NormalDeclarationCompletionItem::data(index, role, model); } } diff --git a/codecompletion/worker.cpp b/codecompletion/worker.cpp index 792ab75..f983942 100644 --- a/codecompletion/worker.cpp +++ b/codecompletion/worker.cpp @@ -1,43 +1,43 @@ /************************************************************************************* * 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 "worker.h" #include "context.h" #include "completiondebug.h" namespace go { CodeCompletionWorker::CodeCompletionWorker(KDevelop::CodeCompletionModel* model): KDevelop::CodeCompletionWorker(model) { } KDevelop::CodeCompletionContext* CodeCompletionWorker::createCompletionContext(const KDevelop::DUContextPointer& context, - const QString& contextText, - const QString& followingText, + const QString& contextText, + const QString& followingText, const KDevelop::CursorInRevision& position) const { - (void)followingText; + Q_UNUSED(followingText); qCDebug(COMPLETION) << "Completion test"; //return go::CodeCompletionWorker::createCompletionContext(context, contextText, followingText, position); return new go::CodeCompletionContext(context, contextText, position); } } diff --git a/duchain/builders/contextbuilder.cpp b/duchain/builders/contextbuilder.cpp index fe7598e..7c47a7a 100644 --- a/duchain/builders/contextbuilder.cpp +++ b/duchain/builders/contextbuilder.cpp @@ -1,201 +1,200 @@ /************************************************************************************* * 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 #include #include #include "contextbuilder.h" #include "goducontext.h" #include "duchaindebug.h" using namespace KDevelop; ContextBuilder::ContextBuilder() { m_mapAst = false; m_expectMoreImports = true; } ContextBuilder::~ContextBuilder() { } /*KDevelop::ReferencedTopDUContext ContextBuilder::build(const KDevelop::IndexedString& url, go::AstNode* node, const KDevelop::ReferencedTopDUContext& updateContext) { return KDevelop::AbstractContextBuilder< go::AstNode, go::ExpressionAst >::build(url, node, updateContext); }*/ void ContextBuilder::startVisiting(go::AstNode* node) { visitNode(node); } KDevelop::DUContext* ContextBuilder::contextFromNode(go::AstNode* node) { return node->ducontext; } KDevelop::RangeInRevision ContextBuilder::editorFindRange(go::AstNode* fromNode, go::AstNode* toNode) { if(!fromNode) return KDevelop::RangeInRevision(); return m_session->findRange(fromNode, toNode ? toNode : fromNode); } KDevelop::QualifiedIdentifier ContextBuilder::identifierForNode(go::IdentifierAst* node) { if(!node) return QualifiedIdentifier(); return QualifiedIdentifier(m_session->symbol(node->id)); } KDevelop::QualifiedIdentifier ContextBuilder::identifierForIndex(qint64 index) { return QualifiedIdentifier(m_session->symbol(index)); } void ContextBuilder::setContextOnNode(go::AstNode* node, KDevelop::DUContext* context) { node->ducontext = context; } void ContextBuilder::setParseSession(ParseSession* session) { m_session = session; } TopDUContext* ContextBuilder::newTopContext(const RangeInRevision& range, ParsingEnvironmentFile* file) { - + if (!file) { file = new ParsingEnvironmentFile(m_session->currentDocument()); file->setLanguage(m_session->languageString()); } //return ContextBuilderBase::newTopContext(range, file); return new go::GoDUContext(m_session->currentDocument(), range, file); } DUContext* ContextBuilder::newContext(const RangeInRevision& range) { return new go::GoDUContext(range, currentContext()); } QualifiedIdentifier ContextBuilder::createFullName(go::IdentifierAst* package, go::IdentifierAst* typeName) { QualifiedIdentifier id(m_session->symbol(package->id) + "." + m_session->symbol(typeName->id)); return id; } ParseSession* ContextBuilder::parseSession() { return m_session; } go::IdentifierAst* ContextBuilder::identifierAstFromExpressionAst(go::ExpressionAst* node) { if(node && node->unaryExpression && node->unaryExpression->primaryExpr) return node->unaryExpression->primaryExpr->id; return nullptr; } void ContextBuilder::visitIfStmt(go::IfStmtAst* node) { //we need variables, declared in if pre-condition(if any) be available in if-block //and else-block, but not in parent context. We deal with it by opening another context //containing both if-block and else-block. openContext(node, editorFindRange(node, 0), DUContext::Other); DefaultVisitor::visitIfStmt(node); closeContext(); } void ContextBuilder::visitBlock(go::BlockAst* node) { openContext(node, editorFindRange(node, 0), DUContext::Other); go::DefaultVisitor::visitBlock(node); closeContext(); } void ContextBuilder::visitTopLevelDeclaration(go::TopLevelDeclarationAst* node) { if(m_expectMoreImports) { //first TopLevelDeclaration was encountered, no more imports are possible m_expectMoreImports = false; topContext()->updateImportsCache(); } go::DefaultVisitor::visitTopLevelDeclaration(node); } void ContextBuilder::visitPrimaryExpr(go::PrimaryExprAst *node) { if(node->id) { QualifiedIdentifier id(identifierForNode(node->id)); DeclarationPointer declaration = go::getTypeOrVarDeclaration(id, currentContext()); if (!declaration) { declaration = go::getDeclaration(id, currentContext()); } if (declaration && (node->literalValue) && declaration->kind() == Declaration::Type) { if (node->literalValue) { openContext(node->id, editorFindRange(node->literalValue, 0), DUContext::Other, id); visitLiteralValue(node->literalValue); closeContext(); return; } } else { auto primaryExprResolveNode = node->primaryExprResolve; while(primaryExprResolveNode) { if(primaryExprResolveNode->selector) { id = id + identifierForNode(primaryExprResolveNode->selector); } if(primaryExprResolveNode->literalValue) { declaration = go::getTypeOrVarDeclaration(id, currentContext()); if (!declaration) { declaration = go::getDeclaration(id, currentContext()); } if(declaration && declaration->kind() == Declaration::Type) { -// const RangeInRevision &range = editorFindRange(primaryExprResolveNode->literalValue, 0); // Unused openContext(node->id, editorFindRange(primaryExprResolveNode->literalValue, 0), DUContext::Other, id); visitPrimaryExprResolve(node->primaryExprResolve); closeContext(); return; } } primaryExprResolveNode = primaryExprResolveNode->primaryExprResolve; } } } go::DefaultVisitor::visitPrimaryExpr(node); } diff --git a/duchain/builders/declarationbuilder.cpp b/duchain/builders/declarationbuilder.cpp index b4b5bc1..4c88a97 100644 --- a/duchain/builders/declarationbuilder.cpp +++ b/duchain/builders/declarationbuilder.cpp @@ -1,735 +1,735 @@ /************************************************************************************* * 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 "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, const KDevelop::ReferencedTopDUContext& updateContext_) { qCDebug(DUCHAIN) << "DeclarationBuilder start"; ReferencedTopDUContext updateContext(updateContext_); 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); 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) { if(type != nullptr) { type->setModifiers(declareConstant ? AbstractType::ConstModifier : AbstractType::NoModifiers); } } if(declareConstant) m_constAutoTypes = types; 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; declareVariable(iter->element, types.at(typeIndex)); iter = iter->next; typeIndex++; } while (iter != end); } } void DeclarationBuilder::declareVariable(go::IdentifierAst* id, const AbstractType::Ptr& type) { auto identifier = identifierForNode(id); auto declaration = go::getDeclaration(identifier, currentContext(), false); auto wasDeclaredInCurrentContext = declaration && declaration.data()->range() != editorFindRange(id, 0); if(identifier.toString() != "_" && !wasDeclaredInCurrentContext) { 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 { DeclarationBuilderBase::visitPrimaryExpr(node); } } void DeclarationBuilder::visitMethodDeclaration(go::MethodDeclarationAst* node) { go::GoFunctionDeclaration* functionDeclaration = nullptr; QualifiedIdentifier typeIdentifier; auto containerType = go::getMethodRecvTypeIdentifier(node->methodRecv); { DUChainWriteLocker lock; typeIdentifier = identifierForNode(containerType); auto typeDeclaration = go::getTypeDeclaration(typeIdentifier, currentContext()); auto toBeInjectedContext = typeDeclaration ? typeDeclaration->internalContext() : nullptr; if(!typeDeclaration || !typeDeclaration->internalContext()) { openContext(node->methodName, node->methodName, DUContext::ContextType::Class, typeIdentifier); if(typeDeclaration) { typeDeclaration->setInternalContext(currentContext()); } toBeInjectedContext = currentContext(); closeContext(); } injectContext(toBeInjectedContext); auto range = RangeInRevision(currentContext()->range().start, currentContext()->range().start); functionDeclaration = openDeclaration(identifierForNode(node->methodName).last(), range); functionDeclaration->setAutoDeclaration(true); closeDeclaration(); closeInjectedContext(); } QualifiedIdentifier identifier; { DUChainReadLocker lock; identifier = functionDeclaration->qualifiedIdentifier(); } openContext(node, editorFindRange(node, 0), DUContext::ContextType::Class, typeIdentifier); DUChainWriteLocker lock; auto functionDefinition = buildMethod(node->signature, node->body, node->methodName, functionDeclaration, m_session->commentBeforeToken(node->startToken-1), identifier); functionDeclaration->setType(functionDefinition->type()); functionDeclaration->setKind(Declaration::Instance); lock.unlock(); if(node->methodRecv->type && functionDefinition->internalContext()) { //declare method receiver variable('this' or 'self' analog in Go) openContext(functionDefinition->internalContext()); buildTypeName(node->methodRecv->type); if(node->methodRecv->star!= -1) { PointerType* ptype = new PointerType(); ptype->setBaseType(lastType()); injectType(PointerType::Ptr(ptype)); } DUChainWriteLocker n; functionDefinition->setDeclaration(functionDeclaration); auto methodReceiverName = node->methodRecv->nameOrType; Declaration* thisVariable = openDeclaration(identifierForNode(methodReceiverName).last(), editorFindRange(methodReceiverName, 0)); thisVariable->setAbstractType(lastType()); closeDeclaration(); closeContext(); } closeContext(); } 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); ClassDeclaration* 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()); auto structType = fastCast(lastType().data()); if(structType) { structType->setDeclaration(decl); } if(!lastContext()) { openContext(node->name, DUContext::ContextType::Class, identifierForNode(node->name)); closeContext(); } 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()); } } } auto varId = sequence->at(i)->element->varid; if(varId && !sequence->at(i)->element->type) { buildTypeName(varId); 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()), + 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; auto 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::GoFunctionDefinition* DeclarationBuilder::declareMethod(go::IdentifierAst *id, const go::GoFunctionType::Ptr &type, DUContext *paramContext, DUContext *retparamContext, const QByteArray &comment, DUContext *bodyContext, go::GoFunctionDeclaration *declaration, const QualifiedIdentifier &identifier) { - (void)declaration; + Q_UNUSED(declaration); setComment(comment); DUChainWriteLocker lock; auto dec = openDefinition(identifier, 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(); } DUContext* returnArgsContext = nullptr; DUContext* parametersContext = nullptr; auto type = parseSignature(node, true, ¶metersContext, &returnArgsContext, identifierForNode(name), comment); return declareFunction(name, type, parametersContext, returnArgsContext, comment, bodyContext); } go::GoFunctionDefinition* DeclarationBuilder::buildMethod(go::SignatureAst *node, go::BlockAst *block, go::IdentifierAst *name, go::GoFunctionDeclaration *declaration, const QByteArray &comment, const QualifiedIdentifier &identifier) { DUContext* bodyContext = nullptr; if(block) { visitBlock(block); bodyContext = lastContext(); } DUContext* parametersContext = nullptr; DUContext* returnArgsContext = nullptr; auto type = parseSignature(node, true, ¶metersContext, &returnArgsContext, identifier, comment); return declareMethod(name, type, parametersContext, returnArgsContext, comment, bodyContext, declaration, identifier); } diff --git a/duchain/builders/typebuilder.cpp b/duchain/builders/typebuilder.cpp index 8713e7b..9a6dbd1 100644 --- a/duchain/builders/typebuilder.cpp +++ b/duchain/builders/typebuilder.cpp @@ -1,437 +1,437 @@ /************************************************************************************* * 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 "typebuilder.h" #include #include #include "types/gointegraltype.h" #include "types/gostructuretype.h" #include "types/gomaptype.h" #include "types/gochantype.h" #include "helper.h" using namespace KDevelop; namespace go { void TypeBuilder::visitTypeName(go::TypeNameAst* node) { buildTypeName(node->name, node->type_resolve->fullName); } void TypeBuilder::buildTypeName(IdentifierAst* typeName, IdentifierAst* fullName) { uint type = IntegralType::TypeNone; QualifiedIdentifier id = identifierForNode(typeName); QString name = id.toString(); //Builtin types if(name == "uint8") type = go::GoIntegralType::TypeUint8; else if(name == "uint16") type = go::GoIntegralType::TypeUint16; else if(name == "uint32") type = go::GoIntegralType::TypeUint32; else if(name == "uint64") type = go::GoIntegralType::TypeUint64; else if(name == "int8") type = go::GoIntegralType::TypeUint8; else if(name == "int16") type = go::GoIntegralType::TypeInt16; else if(name == "int32") type = go::GoIntegralType::TypeInt32; else if(name == "int64") type = go::GoIntegralType::TypeInt64; else if(name == "float32") type = go::GoIntegralType::TypeFloat32; else if(name == "float64") type = go::GoIntegralType::TypeFloat64; else if(name == "complex64") type = go::GoIntegralType::TypeComplex64; else if(name == "complex128") type = go::GoIntegralType::TypeComplex128; else if(name == "rune") type = go::GoIntegralType::TypeRune; else if(name == "int") type = go::GoIntegralType::TypeInt; else if(name == "uint") type = go::GoIntegralType::TypeUint; else if(name == "uintptr") type = go::GoIntegralType::TypeUintptr; else if(name == "string") type = go::GoIntegralType::TypeString; else if(name == "bool") type = go::GoIntegralType::TypeBool; else if(name == "byte") type = go::GoIntegralType::TypeByte; if(type == IntegralType::TypeNone) { //in Go one can create variable of package type, like 'fmt fmt' //TODO support such declarations QualifiedIdentifier id(identifierForNode(typeName)); if(fullName) id.push(identifierForNode(fullName)); DeclarationPointer decl = go::getTypeDeclaration(id, currentContext()); if(decl) { DUChainReadLocker lock; StructureType* type = new StructureType(); type->setDeclaration(decl.data()); injectType(AbstractType::Ptr(type)); //kDebug() << decl->range(); return; } DelayedType* unknown = new DelayedType(); unknown->setIdentifier(IndexedTypeIdentifier(id)); injectType(AbstractType::Ptr(unknown)); return; } if(type != IntegralType::TypeNone) { injectType(AbstractType::Ptr(new go::GoIntegralType(type))); } } void TypeBuilder::visitArrayOrSliceType(go::ArrayOrSliceTypeAst* node) { if(node->arrayOrSliceResolve->array) visitType(node->arrayOrSliceResolve->array); else if(node->arrayOrSliceResolve->slice) visitType(node->arrayOrSliceResolve->slice); else //error injectType(AbstractType::Ptr()); //TODO create custom classes GoArrayType and GoSliceType //to properly distinguish between go slices and arrays ArrayType* array = new ArrayType(); //kDebug() << lastType()->toString(); array->setElementType(lastType()); injectType(ArrayType::Ptr(array)); } void TypeBuilder::visitPointerType(go::PointerTypeAst* node) { PointerType* type = new PointerType(); visitType(node->type); type->setBaseType(lastType()); injectType(PointerType::Ptr(type)); } void TypeBuilder::visitStructType(go::StructTypeAst* node) { openType(go::GoStructureType::Ptr(new go::GoStructureType)); { DUChainWriteLocker lock; openContext(node, editorFindRange(node, 0), DUContext::ContextType::Class, m_contextIdentifier); } TypeBuilderBase::visitStructType(node); { DUChainWriteLocker lock; currentType()->setContext(currentContext()); closeContext(); } currentType()->setPrettyName(m_session->textForNode(node)); currentType()->setStructureType(); closeType(); } void TypeBuilder::visitFieldDecl(go::FieldDeclAst* node) { StructureType::Ptr structure = currentType(); QList names; if(node->anonFieldStar) { PointerType* type = new PointerType(); visitTypeName(node->anonFieldStar->typeName); type->setBaseType(lastType()); go::IdentifierAst* id = node->anonFieldStar->typeName->type_resolve->fullName ? node->anonFieldStar->typeName->type_resolve->fullName : node->anonFieldStar->typeName->name; injectType(PointerType::Ptr(type)); names.append(id); }else if(node->type) { visitType(node->type); names.append(node->varid); if(node->idList) { auto elem = node->idList->idSequence->front(); while(true) { names.append(elem->element); if(elem->hasNext()) elem = elem->next; else break; } } }else { buildTypeName(node->varid, node->fullname); go::IdentifierAst* id = node->fullname ? node->fullname : node->varid; names.append(id); } for(auto name : names) { declareVariable(name, lastType()); } } void TypeBuilder::visitInterfaceType(go::InterfaceTypeAst* node) { openType(go::GoStructureType::Ptr(new go::GoStructureType)); //ClassDeclaration* decl; { DUChainWriteLocker lock; //decl = openDeclaration(QualifiedIdentifier(), RangeInRevision()); openContext(node, editorFindRange(node, 0), DUContext::ContextType::Class, m_contextIdentifier); } TypeBuilderBase::visitInterfaceType(node); { DUChainWriteLocker lock; //decl->setInternalContext(currentContext()); //decl->setClassType(ClassDeclarationData::Interface); currentType()->setContext(currentContext()); closeContext(); //closeDeclaration(); //currentType()->setDeclaration(decl); //decl->setIdentifier(Identifier(QString("interface type"))); } currentType()->setPrettyName(m_session->textForNode(node)); currentType()->setInterfaceType(); closeType(); } void TypeBuilder::visitMethodSpec(go::MethodSpecAst* node) { if(node->signature) { DUContext *parametersContext = nullptr; DUContext *returnArgsContext = nullptr; auto type = parseSignature(node->signature, true, ¶metersContext, &returnArgsContext, identifierForNode(node->methodName)); declareFunction(node->methodName, type, parametersContext, returnArgsContext); }else{ buildTypeName(node->methodName, node->fullName); go::IdentifierAst* id = node->fullName ? node->fullName : node->methodName; { declareVariable(id, lastType()); } } } void TypeBuilder::visitMapType(go::MapTypeAst* node) { go::GoMapType* type = new go::GoMapType(); visitType(node->keyType); type->setKeyType(lastType()); visitType(node->elemType); type->setValueType(lastType()); injectType(AbstractType::Ptr(type)); } void TypeBuilder::visitChanType(go::ChanTypeAst* node) { visitType(node->rtype ? node->rtype : node->stype); go::GoChanType::Ptr type(new go::GoChanType()); if(node->stype) type->setKind(go::GoChanType::Receive); else if(node->send != -1) type->setKind(go::GoChanType::Send); else type->setKind(go::GoChanType::SendAndReceive); DUChainReadLocker lock; type->setValueType(lastType()); injectType(type); } void TypeBuilder::visitFunctionType(go::FunctionTypeAst* node) { parseSignature(node->signature, false); } void TypeBuilder::visitParameter(go::ParameterAst* node) { //parameter grammar rule is written in such a way that full types won't be parsed automatically //so we do it manually(see go.g:parameter) //also if type is just identifier it is impossible to say right now whether it is really a type //or a identifier. This will be decided in parseParameres method if(node->idOrType && node->fulltype) buildTypeName(node->idOrType, node->fulltype); TypeBuilderBase::visitParameter(node); } go::GoFunctionType::Ptr TypeBuilder::parseSignature(go::SignatureAst *node, bool declareParameters, DUContext **parametersContext, DUContext **returnArgsContext, const QualifiedIdentifier &identifier, const QByteArray &comment) { - (void)comment; + Q_UNUSED(comment); go::GoFunctionType::Ptr type(new go::GoFunctionType()); openType(type); if(declareParameters) { *parametersContext = openContext(node->parameters, editorFindRange(node->parameters, 0), DUContext::ContextType::Function, identifier); } parseParameters(node->parameters, true, declareParameters); if(declareParameters) { closeContext(); } if(node->result) { visitResult(node->result); if(node->result->parameters) { if(declareParameters) { *returnArgsContext = openContext(node->result, editorFindRange(node->result, 0), DUContext::ContextType::Function, identifier); } parseParameters(node->result->parameters, false, declareParameters); if(declareParameters) { closeContext(); } } if(!node->result->parameters && lastType()) { type->addReturnArgument(lastType()); } } closeType(); return type; } void TypeBuilder::parseParameters(go::ParametersAst* node, bool parseArguments, bool declareParameters) { //code below is a bit ugly because of problems with parsing go parameter list(see details at parser/go.g:331) go::GoFunctionType::Ptr function; function = currentType(); if(node->parameter) { QList paramNames; go::ParameterAst* param=node->parameter; visitParameter(param); //variadic arguments if(param->unnamedvartype || param->vartype) { function->setModifiers(go::GoFunctionType::VariadicArgument); ArrayType* atype = new ArrayType(); atype->setElementType(lastType()); injectType(AbstractType::Ptr(atype)); } if(!param->complexType && !param->parenType && !param->unnamedvartype && !param->type && !param->vartype && !param->fulltype) paramNames.append(param->idOrType); //we only have an identifier else { addArgumentHelper(function, lastType(), parseArguments); //if we have a parameter name(but it's not part of fullname) open declaration if(param->idOrType && !param->fulltype && declareParameters) declareVariable(param->idOrType, lastType()); } if(node->parameterListSequence) { auto elem = node->parameterListSequence->front(); while(true) { go::ParameterAst* param=elem->element; visitParameter(param); //variadic arguments if(param->unnamedvartype || param->vartype) { function->setModifiers(go::GoFunctionType::VariadicArgument); ArrayType* atype = new ArrayType(); atype->setElementType(lastType()); injectType(AbstractType::Ptr(atype)); } if(param->complexType || param->parenType || param->unnamedvartype || param->fulltype) {//we have a unnamed parameter list of types AbstractType::Ptr lType = lastType(); for(auto id : paramNames) { buildTypeName(id); addArgumentHelper(function, lastType(), parseArguments); } addArgumentHelper(function, lType, parseArguments); paramNames.clear(); }else if(!param->complexType && !param->parenType && !param->unnamedvartype && !param->type && !param->vartype && !param->fulltype) {//just another identifier paramNames.append(param->idOrType); }else {//identifier with type, all previous identifiers are of the same type for(auto id : paramNames) { addArgumentHelper(function, lastType(), parseArguments); if(declareParameters) declareVariable(id, lastType()); } addArgumentHelper(function, lastType(), parseArguments); if(declareParameters) declareVariable(param->idOrType, lastType()); paramNames.clear(); } if(elem->hasNext()) elem = elem->next; else break; } if(!paramNames.empty()) {//we have only identifiers which means they are all type names //foreach(auto id, paramNames) for(auto id : paramNames) { buildTypeName(id); addArgumentHelper(function, lastType(), parseArguments); } paramNames.clear(); } }else if(!paramNames.empty()) { //one identifier that we have is a type buildTypeName(param->idOrType); addArgumentHelper(function, lastType(), parseArguments); } } } void TypeBuilder::addArgumentHelper(go::GoFunctionType::Ptr function, AbstractType::Ptr argument, bool parseArguments) { DUChainWriteLocker lock; if(argument) { if(parseArguments) function->addArgument(argument); else function->addReturnArgument(argument); } } } diff --git a/duchain/expressionvisitor.cpp b/duchain/expressionvisitor.cpp index 28f55a4..5eea5f5 100644 --- a/duchain/expressionvisitor.cpp +++ b/duchain/expressionvisitor.cpp @@ -1,778 +1,778 @@ /************************************************************************************* * 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 "expressionvisitor.h" #include #include #include #include "types/gointegraltype.h" #include "types/gostructuretype.h" #include "types/gomaptype.h" #include "types/gochantype.h" #include "helper.h" #include "duchaindebug.h" using namespace KDevelop; -namespace go +namespace go { ExpressionVisitor::ExpressionVisitor(ParseSession* session, DUContext* context, DeclarationBuilder* builder) : m_session(session), m_context(context), m_builder(builder) { } QList< AbstractType::Ptr > ExpressionVisitor::lastTypes() { return m_types; } void ExpressionVisitor::visitExpression(ExpressionAst* node) { if(node->binary_op) { if( (node->binary_op->logicaland != -1) || (node->binary_op->logicalor != -1) || node->binary_op->rel_op ) {//if we have relation operator then result will always be boolean //however we still visit subexpressions to build uses visitUnaryExpression(node->unaryExpression); visitExpression(node->expression); pushType(AbstractType::Ptr(new GoIntegralType(GoIntegralType::TypeBool))); return; } } visitUnaryExpression(node->unaryExpression); if(node->binary_op) { //operands of binary operator should match unless one of them is untyped constant etc...(see golang specification) //TODO properly return types auto types = lastTypes(); visitExpression(node->expression); popTypes(); for(const AbstractType::Ptr &type : types) addType(type); } - + //push unknown type if we failed to find anything else if(m_types.size() == 0) pushType(AbstractType::Ptr(new IntegralType(IntegralType::TypeNone))); } void ExpressionVisitor::visitUnaryExpression(UnaryExpressionAst* node) { DefaultVisitor::visitUnaryExpression(node); if(node->unary_op || node->unsafe_unary_op ) { if(lastTypes().size() == 0) { pushType(AbstractType::Ptr(new IntegralType(IntegralType::TypeNone))); return; } AbstractType::Ptr type = lastTypes().first(); if(node->unsafe_unary_op && node->unsafe_unary_op->star != -1) {//dereferencing if(fastCast(type.constData())) { PointerType::Ptr ptype(fastCast(type.constData())); pushType(ptype->baseType()); } }else if(node->unsafe_unary_op && node->unsafe_unary_op->leftchan != -1) {//chan retrieval if(fastCast(type.constData())) { GoChanType::Ptr ctype(fastCast(type.constData())); pushType(ctype->valueType()); addType(AbstractType::Ptr(new GoIntegralType(GoIntegralType::TypeBool))); } }else if(node->unary_op && node->unary_op->ampersand != -1) {//taking an address PointerType* ptype = new PointerType(); ptype->setBaseType(type); pushType(AbstractType::Ptr(ptype)); }else if(node->unary_op && node->unary_op->bang != -1) { pushType(AbstractType::Ptr(new GoIntegralType(GoIntegralType::TypeBool))); } } } void ExpressionVisitor::visitPrimaryExpr(PrimaryExprAst* node) { if(node->id) { //first try to handle builtin functions, so we can allow identifiers like "make" - if(!handleBuiltinFunction(node)) + if(!handleBuiltinFunction(node)) { QualifiedIdentifier id(identifierForNode(node->id)); DeclarationPointer decl = go::getTypeOrVarDeclaration(id, m_context); if(!decl) {//first try to get declaration which has a real type //if failed - only at the beginning of a primary expression there can be namespace identifier(import) //reason for this is we have multiple declarations for each type, one of them is actual type declaration(Declaration::Type) //others are its method declarations(Declaration::Namespace) //we want to get real type declaration so we can access its internal context, not the method namespace decl = go::getDeclaration(id, m_context); if(!decl) { DelayedType* unknown = new DelayedType(); unknown->setIdentifier(IndexedTypeIdentifier(identifierForNode(node->id))); pushType(AbstractType::Ptr(unknown)); return; } } m_declaration = decl; //qCDebug(DUCHAIN) << "Expression Visitor for "<< id; - + //this handles stuff like mytype{}, mytype() if((node->literalValue || node->callOrBuiltinParam) && decl->kind() == Declaration::Type) { //type aliases are custom types pushUse(node->id, decl.data()); StructureType* type = new StructureType(); DUChainReadLocker lock; type->setDeclaration(decl.data()); pushType(AbstractType::Ptr( type )); } else if(node->callOrBuiltinParam) { //TODO properly check arguments to handle overloads AbstractType::Ptr funcType = resolveTypeAlias(decl->abstractType());//to get actual function type from variables, storing functions if(fastCast(funcType.constData())) { GoFunctionType::Ptr type(fastCast(funcType.constData())); popTypes(); for(const AbstractType::Ptr& arg : type->returnArguments()) addType(arg); pushUse(node->id, decl.data()); visitCallOrBuiltinParam(node->callOrBuiltinParam); //if function was called, last declaration will no longer be of this function //this is needed to prevent wrong function calltips m_declaration = DeclarationPointer(); } } else { pushUse(node->id, decl.data()); if(decl->kind() == Declaration::Namespace || decl->kind() == Declaration::NamespaceAlias)//import identifier { StructureType* type = new StructureType(); DUChainReadLocker lock; type->setDeclaration(decl.data()); pushType(AbstractType::Ptr(type)); } else if(decl->kind() == Declaration::Instance)//variable { pushType(decl->abstractType()); } - } + } } } else if(node->expression) { visitExpression(node->expression); } else { handleLiteralsAndConversions(node); //DefaultVisitor::visitPrimaryExpr(node); } if(node->primaryExprResolve) visitPrimaryExprResolve(node->primaryExprResolve); } void ExpressionVisitor::visitPrimaryExprResolve(PrimaryExprResolveAst* node) { if(node->selector) { QList types = lastTypes(); if(types.size() == 0) return; //we will be looking for 2 types of declarations: namespace declarations and actual members //first type is methods assigned to custom type and imported declarations //if previous expression resulted in IdentifiedType(StructureType) //then it was variable of custom type, or custom type literal or import identifier //note that methods do NOT inherit from type to type //e.g. type mystruct int; type mystruct2 mystruct; func (m mystruct) method() {}; //in this example mystruct2 will NOT have method method() - + //second type is field members of Go structs and interfaces(e.g. struct{ abc int}{10}. or mystruct{10}. // ^ ^ // ^ - we are here - //note that members are inherited(e.g. type mystruct struct { var1 int}; type mystruct2 mystruct; + //note that members are inherited(e.g. type mystruct struct { var1 int}; type mystruct2 mystruct; //in this example mystruct2 will have member named var1 //hope this explanation helps understand this code and some code in completion classes - - + + bool success=false; AbstractType::Ptr type = types.first(); //evaluate pointers if(fastCast(type.constData())) { DUChainReadLocker lock; PointerType::Ptr ptype(fastCast(type.constData())); if(ptype->baseType()) type = ptype->baseType(); } - + if(fastCast(type.constData())) {//we have to look for namespace declarations DUChainReadLocker lock; Declaration* declaration = fastCast(type.constData())->declaration(m_context->topContext()); if(declaration) { //if(decl->kind() == Declaration::Namespace || decl->kind() == Declaration::NamespaceAlias) //{ QualifiedIdentifier id(declaration->qualifiedIdentifier()); id.push(identifierForNode(node->selector)); lock.unlock(); DeclarationPointer decl = getTypeOrVarDeclaration(id, m_context); if(decl) { if(!handleComplexLiteralsAndConversions(node, decl.data())) //handle things like imp.mytype{2} { pushUse(node->selector, decl.data()); pushType(decl->abstractType()); } m_declaration = decl; success = true; } //} } } //this construction will descend through type hierarchy till it hits basic types //e.g. type mystruct struct{}; type mystruct2 mystruct; ... int count=0; if(!success) { do { count++; GoStructureType::Ptr structure(dynamic_cast(type.data())); if(structure) {//get members DUContext* context = structure->context(); //qCDebug(DUCHAIN) << context->range() << m_context->range(); DeclarationPointer decl = getTypeOrVarDeclaration(identifierForNode(node->selector), context); if(decl) { DUChainReadLocker lock; pushUse(node->selector, decl.data()); pushType(decl->abstractType()); m_declaration = decl; } break; } StructureType::Ptr identType(fastCast(type.constData())); if(identType) { DUChainReadLocker lock; auto typeDecl = identType->declaration(m_context->topContext()); if(typeDecl) { type = typeDecl->abstractType(); } } else break; - + } while(type && count < 100); //if we descended 100 times and still didn't find anything - something is wrong } } else if(node->callParam) { //TODO properly check arguments to handle overloads if(lastTypes().size() == 0) return; AbstractType::Ptr funcType = resolveTypeAlias(lastTypes().first());//to get actual function type from variables, storing functions if(fastCast(funcType.constData())) { GoFunctionType::Ptr type(fastCast(funcType.constData())); popTypes(); for(const AbstractType::Ptr& arg : type->returnArguments()) addType(arg); //pushUse(node->id, decl.data()); visitCallParam(node->callParam); } } else if(node->index != -1) {//index expression if(lastTypes().size() == 0) return; AbstractType::Ptr type = popTypes().first(); go::DefaultVisitor::visitPrimaryExprResolve(node); //build uses //slice expressions(e.g. a[low:high]) return slices and strings if(node->colon != -1) { if(fastCast(type.constData())) {//pointer to arrays return slices in slice expressions PointerType::Ptr ptype(fastCast(type.constData())); pushType(ptype->baseType()); return; } pushType(type); return; } if(fastCast(type.constData())) { GoIntegralType::Ptr itype(fastCast(type.constData())); if(itype->dataType() == GoIntegralType::TypeString) { pushType(AbstractType::Ptr(new GoIntegralType(GoIntegralType::TypeByte))); } } else if(fastCast(type.constData())) { ArrayType::Ptr atype(fastCast(type.constData())); pushType(atype->elementType()); } else if(fastCast(type.constData())) {//pointers to array are automatically dereferenced PointerType::Ptr ptype(fastCast(type.constData())); if(fastCast(ptype->baseType().constData())) { ArrayType::Ptr atype(fastCast(ptype->baseType().constData())); pushType(atype->elementType()); } } else if(fastCast(type.constData())) { GoMapType::Ptr mtype(fastCast(type.constData())); //TODO check if expression and key type match, open a problem if not pushType(mtype->valueType()); addType(AbstractType::Ptr(new GoIntegralType(GoIntegralType::TypeBool))); } else { //unrecognized index expression, return whatever type was before index pushType(type); } } else if(node->typeAssertion && m_builder) { m_builder->visitType(node->typeAssertion); pushType(m_builder->getLastType()); } if(node->primaryExprResolve) visitPrimaryExprResolve(node->primaryExprResolve); } void ExpressionVisitor::visitCallOrBuiltinParam(CallOrBuiltinParamAst* node) { //TODO properly check arguments //types of argument in function call will not affect type that expression will return //so just save it for now, and then push them auto types = popTypes(); DefaultVisitor::visitCallOrBuiltinParam(node); popTypes(); for(const AbstractType::Ptr &type : types) addType(type); } void ExpressionVisitor::visitCallParam(CallParamAst* node) { auto types = popTypes(); go::DefaultVisitor::visitCallParam(node); popTypes(); for(const AbstractType::Ptr &type : types) addType(type); } void ExpressionVisitor::visitBasicLit(BasicLitAst* node) { if(node->integer != -1) pushType(AbstractType::Ptr(new GoIntegralType(GoIntegralType::TypeInt))); else if(node->flt != -1) pushType(AbstractType::Ptr(new GoIntegralType(GoIntegralType::TypeFloat64))); else if(node->cpx != -1) pushType(AbstractType::Ptr(new GoIntegralType(GoIntegralType::TypeComplex128))); else if(node->rune != -1) pushType(AbstractType::Ptr(new GoIntegralType(GoIntegralType::TypeRune))); else if(node->string != -1) pushType(AbstractType::Ptr(new GoIntegralType(GoIntegralType::TypeString))); } void ExpressionVisitor::visitStructType(StructTypeAst* node) { if(m_builder) { m_builder->visitStructType(node); pushType(m_builder->getLastType()); } } void ExpressionVisitor::visitMapType(MapTypeAst* node) { if(m_builder) { m_builder->visitMapType(node); pushType(m_builder->getLastType()); } } void ExpressionVisitor::visitPointerType(PointerTypeAst* node) { if(m_builder) { m_builder->visitPointerType(node); pushType(m_builder->getLastType()); } } void ExpressionVisitor::visitInterfaceType(InterfaceTypeAst* node) { if(m_builder) { m_builder->visitInterfaceType(node); pushType(m_builder->getLastType()); } } void ExpressionVisitor::visitChanType(ChanTypeAst* node) { if(m_builder) { m_builder->visitChanType(node); pushType(m_builder->getLastType()); } } void ExpressionVisitor::visitParenType(ParenTypeAst* node) { if(m_builder) { m_builder->visitParenType(node); if(node->type->typeName && fastCast(m_builder->getLastType().constData())) {//if we haven't found type it could be parenthesized function call '(funcname)(argument)' QualifiedIdentifier id(identifierForNode(node->type->typeName->name)); if(node->type->typeName->type_resolve->fullName) id.push(identifierForNode(node->type->typeName->type_resolve->fullName)); DeclarationPointer decl = go::getTypeOrVarDeclaration(id, m_context); if(!decl) return; AbstractType::Ptr funcType = resolveTypeAlias(decl->abstractType());//to get actual function type from variables, storing functions if(fastCast(funcType.constData())) { GoFunctionType::Ptr type(fastCast(funcType.constData())); popTypes(); for(const AbstractType::Ptr& arg : type->returnArguments()) addType(arg); pushUse(node->type->typeName->name, decl.data()); m_declaration = decl; return; } //TODO there also can be builtin functions '(new)(int)' } pushType(m_builder->getLastType()); } } void ExpressionVisitor::handleLiteralsAndConversions(PrimaryExprAst* node) { if(!node) return; //build uses if(node->literalValue) visitLiteralValue(node->literalValue); if(node->convArg) visitConversionArgument(node->convArg); if(node->basicLit) visitBasicLit(node->basicLit); else if(node->structType) visitStructType(node->structType); else if(node->mapType) visitMapType(node->mapType); else if(node->signature) { if(m_builder) { m_builder->buildFunction(node->signature, node->body); pushType(m_builder->getLastType()); } } else if(node->pointerType) visitPointerType(node->pointerType); else if(node->interfaceType) visitInterfaceType(node->interfaceType); else if(node->chanType) visitChanType(node->chanType); else if(node->parenType) visitParenType(node->parenType); else if(node->array != -1) { if(m_builder) { if(node->tripledot != -1) { m_builder->visitType(node->element); }else { if(node->arrayOrSliceResolve->array) m_builder->visitType(node->arrayOrSliceResolve->array); else if(node->arrayOrSliceResolve->slice) m_builder->visitType(node->arrayOrSliceResolve->slice); } ArrayType* array = new ArrayType(); array->setElementType(m_builder->getLastType()); pushType(AbstractType::Ptr(array)); } } } void ExpressionVisitor::visitBlock(BlockAst* node) { - (void)node; + Q_UNUSED(node); } void ExpressionVisitor::pushType(AbstractType::Ptr type) { //if(type.isTypeAlias) m_types.clear(); m_types.append(type); } AbstractType::Ptr ExpressionVisitor::resolveTypeAlias(AbstractType::Ptr type) { if(fastCast(type.constData()) && type->whichType() == AbstractType::TypeAlias) { DUChainReadLocker lock; return fastCast(type.constData())->declaration(m_context->topContext())->abstractType(); } return type; } QList< AbstractType::Ptr > ExpressionVisitor::popTypes() { QList types =lastTypes(); m_types.clear(); return types; } void ExpressionVisitor::addType(AbstractType::Ptr type) { m_types.append(type); } void ExpressionVisitor::pushUse(IdentifierAst* node, Declaration* declaration) { m_ids.append(node); m_declarations.append(DeclarationPointer(declaration)); } QList< DeclarationPointer> ExpressionVisitor::allDeclarations() { return m_declarations; } QList< IdentifierAst* > ExpressionVisitor::allIds() { return m_ids; } void ExpressionVisitor::clearAll() { m_declarations.clear(); m_ids.clear(); m_types.clear(); } QualifiedIdentifier ExpressionVisitor::identifierForNode(IdentifierAst* node) { if(!node) return QualifiedIdentifier(); return QualifiedIdentifier(m_session->symbol(node->id)); } bool ExpressionVisitor::handleComplexLiteralsAndConversions(PrimaryExprResolveAst* node, Declaration* decl) { //we have to separately handle expressions like imp.mytype{3} because of the way grammar was written if(node->primaryExprResolve && (node->primaryExprResolve->literalValue || node->primaryExprResolve->callParam) && decl->kind() == Declaration::Type) { pushUse(node->selector, decl); StructureType* type = new StructureType(); DUChainReadLocker lock; type->setDeclaration(decl); pushType(AbstractType::Ptr( type )); return true; } return false; } bool ExpressionVisitor::handleBuiltinFunction(PrimaryExprAst* node) { if(!node->callOrBuiltinParam) return false; //we can't find types without builder if(!m_builder) {//but we can build uses anyway visitCallOrBuiltinParam(node->callOrBuiltinParam); return false; } if(node->callOrBuiltinParam) { //QualifiedIdentifier builtinFunction = identifierForNode(node->id); QString builtinFunction = identifierForNode(node->id).toString(); if(builtinFunction == "make" && node->callOrBuiltinParam->type) {//for make first argument must be slice, map or channel type //TODO check types and open problem if they not what they should be m_builder->visitType(node->callOrBuiltinParam->type); AbstractType::Ptr type = m_builder->getLastType(); visitCallOrBuiltinParam(node->callOrBuiltinParam); pushType(type); return true; } else if(builtinFunction == "append" && node->callOrBuiltinParam->expression) { visitExpression(node->callOrBuiltinParam->expression); auto types = popTypes(); if(!types.empty()) { auto type = types.first(); pushType(type); } return true; } else if(builtinFunction == "new") { AbstractType::Ptr type; if(!node->callOrBuiltinParam->type) {//extract type name from expression ExpressionAst* exp = node->callOrBuiltinParam->expression; while(exp && exp->unaryExpression && exp->unaryExpression->primaryExpr) { if(exp->unaryExpression->primaryExpr->expression) {//type enclosed in parentheses exp = exp->unaryExpression->primaryExpr->expression; continue; } if(exp->unaryExpression->primaryExpr->id) { IdentifierAst* fullname=0; if(exp->unaryExpression->primaryExpr->primaryExprResolve && exp->unaryExpression->primaryExpr->primaryExprResolve->selector) fullname = exp->unaryExpression->primaryExpr->primaryExprResolve->selector; m_builder->buildTypeName(exp->unaryExpression->primaryExpr->id, fullname); type = m_builder->getLastType(); } break; } if(!type) return false; } else { m_builder->visitType(node->callOrBuiltinParam->type); type = m_builder->getLastType(); } visitCallOrBuiltinParam(node->callOrBuiltinParam); if(type) { PointerType* ptype = new PointerType(); ptype->setBaseType(type); pushType(AbstractType::Ptr(ptype)); } return true; } return false; } return false; } void ExpressionVisitor::visitTypeName(TypeNameAst* node) { QualifiedIdentifier id(identifierForNode(node->name)); if(node->type_resolve->fullName) id.push(identifierForNode(node->type_resolve->fullName)); DeclarationPointer decl = getTypeDeclaration(id, m_context); if(decl) { if(node->type_resolve->fullName) { //TODO can we do that more efficient? DeclarationPointer package = getFirstDeclaration(decl->topContext()); pushUse(node->name, package.data()); pushUse(node->type_resolve->fullName, decl.data()); } else pushUse(node->name, decl.data()); } } void ExpressionVisitor::visitRangeClause(ExpressionAst* node) { visitExpression(node); if(lastTypes().size() == 0) return; AbstractType::Ptr type = popTypes().first(); //go::DefaultVisitor::visitPrimaryExprResolve(node); //build uses //dereference pointers if(fastCast(type.constData())) { PointerType::Ptr ptype = PointerType::Ptr(fastCast(type.constData())); type = ptype->baseType(); } //descend to underlying types through custom ones int recursionPrev = 0; //prevent recursion in case user defined circular types while(fastCast(type.constData()) && recursionPrev < 100) { DUChainReadLocker lock; Declaration* declaration = fastCast(type.constData())->declaration(m_context->topContext()); if(!declaration || !declaration->abstractType()) return; type = declaration->abstractType(); //underlying type recursionPrev++; } if(fastCast(type.constData())) { GoIntegralType::Ptr itype(fastCast(type.constData())); if(itype->dataType() == GoIntegralType::TypeString) { pushType(AbstractType::Ptr(new GoIntegralType(GoIntegralType::TypeInt))); addType(AbstractType::Ptr(new GoIntegralType(GoIntegralType::TypeRune))); } }else if(fastCast(type.constData())) { ArrayType::Ptr atype(fastCast(type.constData())); pushType(AbstractType::Ptr(new GoIntegralType(GoIntegralType::TypeInt))); addType(atype->elementType()); }else if(fastCast(type.constData())) { GoMapType::Ptr mtype(fastCast(type.constData())); //TODO check if expression and key type match, open a problem if not pushType(mtype->keyType()); addType(mtype->valueType()); }else if(fastCast(type.constData())) { GoChanType::Ptr ctype(fastCast(type.constData())); pushType(ctype->valueType()); }else { //unrecognized index expression, return whatever type was before index pushType(type); } } } diff --git a/duchain/helper.cpp b/duchain/helper.cpp index 0ff4472..995a356 100644 --- a/duchain/helper.cpp +++ b/duchain/helper.cpp @@ -1,246 +1,246 @@ /************************************************************************************* * 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 "helper.h" #include #include #include #include #include #include #include #include #include #include #include namespace go { QList Helper::m_CachedSearchPaths; QString Helper::builtinFile; DUChainPointer Helper::builtinContext; QList< QString > Helper::getSearchPaths(QUrl document) { QList paths; if(document != QUrl()) {//try to find path automatically for opened documents QDir currentDir(document.adjusted(QUrl::RemoveFilename).path()); //qCDebug(Go) << currentDir.dirName(); while(currentDir.exists() && currentDir.dirName() != "src") if(!currentDir.cdUp()) break; if(currentDir.exists() && currentDir.dirName() == "src") paths.append(currentDir.absolutePath()); } if(Helper::m_CachedSearchPaths.empty()) { //check $GOPATH env var //since Go 1.8 $GOPATH may be not set because it defaults to $HOME/go //so use go tool which can correctly handle default case. QByteArray result = getGoEnv("GOPATH"); if(!result.isEmpty()) { QDir path(result); if(path.exists() && path.cd("src") && path.exists()) m_CachedSearchPaths.append(path.absolutePath()); } //then check $GOROOT //these days most people don't set GOROOT manually //instead go tool can find correct value for GOROOT on its own //in order for this to work go exec must be in $PATH result = getGoEnv("GOROOT"); if(!result.isEmpty()) { //since Go 1.4 stdlib packages are stored in $GOROOT/src/ //but we also support old layout $GOROOT/src/pkg/ QDir path = QDir(result); if(path.exists() && path.cd("src") && path.exists()) { m_CachedSearchPaths.append(path.absolutePath()); if(path.cd("pkg") && path.exists()) m_CachedSearchPaths.append(path.absolutePath()); } } } paths.append(m_CachedSearchPaths); return paths; } QByteArray Helper::getGoEnv(QString name) { QProcess p; p.start("go env " + name); p.waitForFinished(); QByteArray result = p.readAllStandardOutput(); if(result.endsWith("\n")) result.remove(result.length()-1, 1); return result; } QString Helper::getBuiltinFile() { if(Helper::builtinFile.isNull()) Helper::builtinFile = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdev-go/builtins.go"); return Helper::builtinFile; } ReferencedTopDUContext Helper::getBuiltinContext() { if(Helper::builtinContext) { return ReferencedTopDUContext(Helper::builtinContext.data()); }else { DUChainReadLocker lock; IndexedString file = IndexedString(Helper::getBuiltinFile()); if(file.isEmpty()) return ReferencedTopDUContext(0); Helper::builtinContext = DUChain::self()->chainForDocument(file); if(!Helper::builtinContext) { //if builtins were not parsed, schedule for parsing with high priority KDevelop::ICore::self()->languageController()->backgroundParser()->addDocument(file, KDevelop::TopDUContext::ForceUpdate, BackgroundParser::BestPriority, 0, ParseJob::FullSequentialProcessing); return ReferencedTopDUContext(0); } return ReferencedTopDUContext(Helper::builtinContext.data()); } } DeclarationPointer getDeclaration(QualifiedIdentifier id, DUContext* context, bool searchInParent) { DUChainReadLocker lock; if(context) { auto declarations = context->findDeclarations(id, CursorInRevision(INT_MAX, INT_MAX), AbstractType::Ptr(), nullptr, searchInParent ? DUContext::NoSearchFlags : DUContext::DontSearchInParent); for(Declaration* decl: declarations) { //import declarations are just decorations and need not be returned if(decl->kind() == Declaration::Import) continue; return DeclarationPointer(decl); } if(searchInParent && context->parentContext()) { return getDeclaration(id, context->parentContext(), searchInParent); } } return DeclarationPointer(); } DeclarationPointer getTypeOrVarDeclaration(QualifiedIdentifier id, DUContext* context, bool searchInParent) { DUChainReadLocker lock; if(context) { auto declarations = context->findDeclarations(id, CursorInRevision(INT_MAX, INT_MAX)); for(Declaration* decl : declarations) { if((decl->kind() == Declaration::Import) || (decl->kind() == Declaration::Namespace) || (decl->kind() == Declaration::NamespaceAlias)) - continue; + continue; return DeclarationPointer(decl); } if(searchInParent && context->parentContext()) { return getTypeOrVarDeclaration(id, context->parentContext(), searchInParent); } } return DeclarationPointer(); } DeclarationPointer getTypeDeclaration(QualifiedIdentifier id, DUContext* context, bool searchInParent) { DUChainReadLocker lock; if(context) { auto declarations = context->findDeclarations(id, CursorInRevision(INT_MAX, INT_MAX)); for(Declaration* decl : declarations) { //TODO change this to just decl->kind() != Declaration::Type - if((decl->kind() == Declaration::Import) || (decl->kind() == Declaration::Namespace) + if((decl->kind() == Declaration::Import) || (decl->kind() == Declaration::Namespace) || (decl->kind() == Declaration::NamespaceAlias) || (decl->kind() == Declaration::Instance)) - continue; + continue; return DeclarationPointer(decl); } if(searchInParent && context->parentContext()) { return getTypeDeclaration(id, context->parentContext(), searchInParent); } } return DeclarationPointer(); } QList< Declaration* > getDeclarations(QualifiedIdentifier id, DUContext* context, bool searchInParent) { - (void)searchInParent; + Q_UNUSED(searchInParent); DUChainReadLocker lock; if(context) { QList decls; auto declarations = context->findDeclarations(id, CursorInRevision(INT_MAX, INT_MAX)); for(Declaration* decl: declarations) { if(decl->kind() == Declaration::Import) continue; decls << decl; } return decls; } return QList(); } IdentifierAst* getMethodRecvTypeIdentifier(go::MethodRecvAst* methodRecv) { go::IdentifierAst* actualtype = nullptr; if(methodRecv->ptype) actualtype = methodRecv->ptype; else if(methodRecv->type) actualtype = methodRecv->type; else actualtype = methodRecv->nameOrType; return actualtype; } DeclarationPointer getFirstDeclaration(DUContext* context, bool searchInParent) { DUChainReadLocker lock; auto declarations = context->allDeclarations(CursorInRevision::invalid(), context->topContext(), searchInParent); if(declarations.size()>0) return DeclarationPointer(declarations.first().first); return DeclarationPointer(); } DeclarationPointer checkPackageDeclaration(Identifier id, TopDUContext* context) { DUChainReadLocker lock; auto declarations = context->findLocalDeclarations(id); if(declarations.size() > 0) return DeclarationPointer(declarations.first()); return DeclarationPointer(); } } diff --git a/golangparsejob.cpp b/golangparsejob.cpp index 44f9cac..7523b49 100644 --- a/golangparsejob.cpp +++ b/golangparsejob.cpp @@ -1,236 +1,236 @@ /************************************************************************************* * 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 "golangparsejob.h" #include #include #include #include #include #include #include #include #include #include #include #include "parsesession.h" #include "duchain/builders/declarationbuilder.h" #include "duchain/builders/usebuilder.h" #include "duchain/helper.h" #include "godebug.h" using namespace KDevelop; QHash GoParseJob::canonicalImports; GoParseJob::GoParseJob(const KDevelop::IndexedString& url, KDevelop::ILanguageSupport* languageSupport): ParseJob(url, languageSupport) { } void GoParseJob::run(ThreadWeaver::JobPointer self, ThreadWeaver::Thread *thread) { - (void)self; - (void)thread; - qCDebug(Go) << "GoParseJob succesfully created for document " << document(); + Q_UNUSED(self); + Q_UNUSED(thread); + qCDebug(Go) << "GoParseJob succesfully created for document " << document(); UrlParseLock urlLock(document()); if (abortRequested() || !isUpdateRequired(ParseSession::languageString())) { return; } ProblemPointer p = readContents(); if(p) { return; } QByteArray code = contents().contents; while(code.endsWith('\0')) code.chop(1); //ParseSession session(QString(contents().contents).toUtf8(), priority()); ParseSession session(code, parsePriority()); - + session.setCurrentDocument(document()); session.setFeatures(minimumFeatures()); if(abortRequested()) return; ReferencedTopDUContext context; { DUChainReadLocker lock; context = DUChainUtils::standardContextForUrl(document().toUrl()); } - + //ParsingEnvironmentFilePointer filep = context->parsingEnvironmentFile(); - + if (context) { translateDUChainToRevision(context); context->setRange(RangeInRevision(0, 0, INT_MAX, INT_MAX)); } qCDebug(Go) << "Job features: " << (quint16) minimumFeatures(); qCDebug(Go) << "Job priority: " << parsePriority(); qCDebug(Go) << document(); bool result = session.startParsing(); //this is useful for testing parser, comment it for actual working //Q_ASSERT(result); //When switching between files(even if they are not modified) KDevelop decides they need to be updated //and calls parseJob with VisibleDeclarations feature //so for now feature, identifying import will be AllDeclarationsAndContexts, without Uses bool forExport = false; if((minimumFeatures() & TopDUContext::AllDeclarationsContextsAndUses) == TopDUContext::AllDeclarationsAndContexts) forExport = true; //qCDebug(Go) << contents().contents; if(!forExport) session.setIncludePaths(go::Helper::getSearchPaths(document().toUrl())); else session.setIncludePaths(go::Helper::getSearchPaths()); if(canonicalImports.empty()) parseCanonicalImports(); session.setCanonicalImports(&canonicalImports); if(result) { QReadLocker parseLock(languageSupport()->parseLock()); if(abortRequested()) return abortJob(); //qCDebug(Go) << QString(contents().contents); DeclarationBuilder builder(&session, forExport); context = builder.build(document(), session.ast(), context); if(!forExport) { go::UseBuilder useBuilder(&session); useBuilder.buildUses(session.ast()); } //this notifies other opened files of changes //session.reparseImporters(context); } if(!context){ DUChainWriteLocker lock; ParsingEnvironmentFile* file = new ParsingEnvironmentFile(document()); file->setLanguage(ParseSession::languageString()); context = new TopDUContext(document(), RangeInRevision(0, 0, INT_MAX, INT_MAX), file); DUChain::self()->addDocumentChain(context); } - + setDuChain(context); { DUChainWriteLocker lock; context->setProblems(session.problems()); context->setFeatures(minimumFeatures()); ParsingEnvironmentFilePointer file = context->parsingEnvironmentFile(); Q_ASSERT(file); file->setModificationRevision(contents().modification); DUChain::self()->updateContextEnvironment(context->topContext(), file.data()); } highlightDUChain(); DUChain::self()->emitUpdateReady(document(), duChain()); if(result) qCDebug(Go) << "===Success===" << document().str(); else qCDebug(Go) << "===Failed===" << document().str(); } void GoParseJob::parseCanonicalImports() { QList importPaths = go::Helper::getSearchPaths(); for(const QString& path : importPaths) { QDirIterator iterator(path, QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); while(iterator.hasNext()) { iterator.next(); QDir dir(iterator.filePath()); for(const QString& file : dir.entryList(QStringList("*.go"), QDir::Files | QDir::NoSymLinks)) { QFile f(dir.filePath(file)); f.open(QIODevice::ReadOnly); QByteArray contents = f.readAll(); f.close(); QString canonicalImport = extractCanonicalImport(QString(contents)); if(canonicalImport.length() != 0) { qCDebug(Go) << "Found canonical import for package " << iterator.filePath() << " import: " << canonicalImport; canonicalImports[canonicalImport] = iterator.filePath(); break; } } } } //if no canonical imports were found add stab value to map //so we won't search for them again if(canonicalImports.empty()) canonicalImports[""] = QString("none"); } QString GoParseJob::extractCanonicalImport(QString string) { int i = 0; while(i < string.length()) { if(string[i].isSpace()) i++; else if(string[i] == '/') { if(i + 1 < string.length() && string[i+1] == '/') { i += 2; while(i * * * * 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 "parsesession.h" #include "parser/golexer.h" #include "parser/goparser.h" #include "parser/goast.h" #include "parser/gotokentext.h" #include #include #include #include #include #include #include #include "kdev-pg-memory-pool.h" #include "kdev-pg-token-stream.h" using namespace KDevelop; ParseSession::ParseSession(const QByteArray& contents, int priority, bool appendWithNewline) : m_pool(new KDevPG::MemoryPool()), m_parser(new go::Parser()), //converting to and back from QString eliminates all the \000 at the end of contents //m_contents(QString(contents).toUtf8()), m_contents(contents), m_priority(priority), m_features(TopDUContext::AllDeclarationsAndContexts) { //appending with new line helps lexer to set correct semicolons //(lexer sets semicolons on newlines if some conditions are met because // in Go user can omit semicolons after statements, but grammar still needs them) // See more in Go Language Specification http://golang.org/ref/spec#Semicolons if(appendWithNewline) m_contents.append("\n"); - KDevPG::QByteArrayIterator iter(m_contents); + KDevPG::QByteArrayIterator iter(m_contents); m_lexer = new go::Lexer(iter); m_parser->setMemoryPool(m_pool); m_parser->setTokenStream(m_lexer); forExport=false; - + } ParseSession::~ParseSession() { delete m_pool; delete m_parser; //delete m_iter; } KDevelop::IndexedString ParseSession::languageString() { static const KDevelop::IndexedString langString("go"); return langString; } bool ParseSession::startParsing() { if(!lex()) return false; - + return m_parser->parseStart(&m_ast); } bool ParseSession::lex() { -// KDevPG::Token token; int kind = go::Parser::Token_EOF; while((kind = m_lexer->read().kind) != go::Parser::Token_EOF) { //qDebug() << go::tokenText(kind); if(kind == go::Parser::Token_TEST) { qint64 line, column; m_lexer->locationTable()->positionAt(m_lexer->index(), &line, &column); qDebug() << "Lexer error at: " << line << " : " << column; return false; } } m_parser->rewind(0); return true; } bool ParseSession::parseExpression(go::ExpressionAst** node) { if(!lex()) return false; - + return m_parser->parseExpression(node); } go::StartAst* ParseSession::ast() { return m_ast; } QString ParseSession::symbol(qint64 index) { if(index >= m_lexer->size()) return QString(); return QString(m_contents.mid(m_lexer->at(index).begin, m_lexer->at(index).end - m_lexer->at(index).begin+1)); } KDevelop::RangeInRevision ParseSession::findRange(go::AstNode* from, go::AstNode* to) { qint64 line, column, lineEnd, columnEnd; m_lexer->locationTable()->positionAt(m_lexer->at(from->startToken).begin, &line, &column); m_lexer->locationTable()->positionAt(m_lexer->at(to->endToken).end+1, &lineEnd, &columnEnd); //for some reason I need to shift columns when not on a first line if(line != 0) column++; if(lineEnd != 0) columnEnd++; return KDevelop::RangeInRevision(KDevelop::CursorInRevision(line, column), KDevelop::CursorInRevision(lineEnd, columnEnd)); } KDevelop::IndexedString ParseSession::currentDocument() { return m_document; } void ParseSession::setCurrentDocument(const KDevelop::IndexedString& document) { m_document = document; m_parser->setCurrentDocument(m_document); } /** * Currently priority order works in this way * -1: Direct imports of opened file * 0: opened files * ... * 99... imports of imports of imports.... * 99998: Imports of direct imports(needed to resolve types of some function) * 99999: Reparse of direct imports, after its imports are finished * 100000: reparse of opened file, after all recursive imports * layers higher than 99998 are NOT parsed right now because its too slow */ QList ParseSession::contextForImport(QString package) { package = package.mid(1, package.length()-2); QStringList files; //try canonical paths first if(m_canonicalImports && m_canonicalImports->contains(package)) { QDir path((*m_canonicalImports)[package]); if(path.exists()) { for(const QString& file : path.entryList(QStringList("*.go"), QDir::Files | QDir::NoSymLinks)) files.append(path.filePath(file)); } } if(files.empty()) { for(const QString& pathname : m_includePaths) { QDir path(pathname); if(path.exists() && path.cd(package)) { for(const QString& file : path.entryList(QStringList("*.go"), QDir::Files | QDir::NoSymLinks)) files.append(path.filePath(file)); break; } } } QList contexts; bool shouldReparse=false; //reduce priority if it is recursive import //int priority = forExport ? m_priority + 2 : m_priority - 1; int priority = BackgroundParser::WorstPriority; if(!forExport) priority = -1; //parse direct imports as soon as possible else if(m_priority<=-1) priority = BackgroundParser::WorstPriority-2;//imports of direct imports to the stack bottom else priority = m_priority - 2;//currently parsejob does not get created in this cases to reduce recursion for(QString filename : files) { QFile file(filename); if(!file.exists()) continue; //test files are not part of binary package, so we can exclude them //we parse test files only if we open them in KDevelop if(filename.endsWith("_test.go")) continue; IndexedString url(filename); DUChainReadLocker lock; ReferencedTopDUContext context = DUChain::self()->chainForDocument(url); lock.unlock(); if(context) contexts.append(context); else { if(scheduleForParsing(url, priority, (TopDUContext::Features)(TopDUContext::ForceUpdate | TopDUContext::AllDeclarationsAndContexts))) shouldReparse = true; } } - if(shouldReparse) + if(shouldReparse) //reparse this file after its imports are done scheduleForParsing(m_document, priority+1, (TopDUContext::Features)(m_features | TopDUContext::ForceUpdate)); if(!forExport && m_priority != BackgroundParser::WorstPriority)//always schedule last reparse after all recursive imports are done scheduleForParsing(m_document, BackgroundParser::WorstPriority, (TopDUContext::Features)(m_features | TopDUContext::ForceUpdate)); return contexts; } bool ParseSession::scheduleForParsing(const IndexedString& url, int priority, TopDUContext::Features features) { BackgroundParser* bgparser = KDevelop::ICore::self()->languageController()->backgroundParser(); //TopDUContext::Features features = (TopDUContext::Features)(TopDUContext::ForceUpdate | TopDUContext::VisibleDeclarationsAndContexts);//(TopDUContext::Features) //(TopDUContext::ForceUpdate | TopDUContext::AllDeclarationsContextsAndUses); //currently recursive imports work really slow, nor they usually needed //so disallow recursive imports int levels=1; //allowed levels of recursion if(forExport && priority >= BackgroundParser::InitialParsePriority && priority < BackgroundParser::WorstPriority - 2*levels) //if(forExport) return false; - - if (bgparser->isQueued(url)) + + if (bgparser->isQueued(url)) { - if (bgparser->priorityForDocument(url) > priority ) + if (bgparser->priorityForDocument(url) > priority ) // Remove the document and re-queue it with a greater priority bgparser->removeDocument(url); - else + else return true; } bgparser->addDocument(url, features, priority, 0, ParseJob::FullSequentialProcessing); return true; } void ParseSession::rescheduleThisFile() { BackgroundParser* bgparser = KDevelop::ICore::self()->languageController()->backgroundParser(); if(!bgparser->isQueued(m_document)) bgparser->addDocument(m_document, (TopDUContext::Features)(m_features | TopDUContext::ForceUpdate), m_priority, 0, ParseJob::FullSequentialProcessing); } /** * Reparse files that import current context. * Only works for opened files, so another opened files get notified of changed context */ void ParseSession::reparseImporters(DUContext* context) { DUChainReadLocker lock; if(forExport || m_priority!=0) return; for (DUContext* importer : context->importers()) { scheduleForParsing(importer->url(), BackgroundParser::WorstPriority, (TopDUContext::Features)(importer->topContext()->features() | TopDUContext::ForceUpdate)); } } QList< ReferencedTopDUContext > ParseSession::contextForThisPackage(IndexedString package) { QList contexts; QUrl url = package.toUrl(); QDir path(url.adjusted(QUrl::RemoveFilename).path()); if(path.exists()) { int priority = BackgroundParser::WorstPriority; if(!forExport) priority = -1; //import this package as soon as possible else if(m_priority<=-1) priority = BackgroundParser::WorstPriority-2;//all needed files should be scheduled already else priority = m_priority;//currently parsejob does not get created in this cases to reduce recursion QStringList files = path.entryList(QStringList("*.go"), QDir::Files | QDir::NoSymLinks); bool shouldReparse=false; for(QString filename : files) { filename = path.filePath(filename); QFile file(filename); if(!file.exists()) continue; if(forExport && filename.endsWith("_test.go")) continue; - + IndexedString url(filename); - DUChainReadLocker lock; + DUChainReadLocker lock; ReferencedTopDUContext context = DUChain::self()->chainForDocument(url); lock.unlock(); if(context) contexts.append(context); else { if(scheduleForParsing(url, priority, (TopDUContext::Features)(TopDUContext::ForceUpdate | TopDUContext::AllDeclarationsAndContexts))) shouldReparse=true; } - + } if(shouldReparse) scheduleForParsing(m_document, priority+1, (TopDUContext::Features)(m_features | TopDUContext::ForceUpdate)); } return contexts; } void ParseSession::setFeatures(TopDUContext::Features features) { m_features = features; if((m_features & TopDUContext::AllDeclarationsContextsAndUses) == TopDUContext::AllDeclarationsAndContexts) forExport = true; } QString ParseSession::textForNode(go::AstNode* node) { return QString(m_contents.mid(m_lexer->at(node->startToken).begin, m_lexer->at(node->endToken).end - m_lexer->at(node->startToken).begin+1)); } void ParseSession::setIncludePaths(const QList& paths) { m_includePaths = paths; } QByteArray ParseSession::commentBeforeToken(qint64 token) { int commentEnd = m_lexer->at(token).begin; int commentStart = 0; if(token - 1 >= 0) commentStart = m_lexer->at(token-1).end+1; QString comment = m_contents.mid(commentStart, commentEnd-commentStart); //in lexer, when we insert semicolons after newline //inserted token's end contains '\n' position //so in order not to lose this newline we prepend it if(commentStart > 0 && m_contents[commentStart-1] == '\n') comment.prepend('\n'); //any comment must have at least single '/' if(comment.indexOf('/') == -1) return QByteArray(); int i = 0; int start=-1, end=-1, lineStart=-1, lineEnd=-1; int currentLine = 0; //this flag is true when multiple single-lined comments have been encountered in a row bool contigiousComments = false; while(i < comment.length()) { if(comment[i] == '\n') { contigiousComments = false; currentLine++; i++; } else if(comment[i].isSpace()) { i++; }else if(comment[i] == '/') { if(i + 1 < comment.length() && comment[i+1] == '/') { if(!contigiousComments) { start = i+2; lineStart = currentLine; contigiousComments = true; } i += 2; while(i* imports) { m_canonicalImports = imports; } QList ParseSession::problems() const { return m_parser->problems(); } diff --git a/parser/parsesession.h b/parser/parsesession.h index 337b0a8..e2931fd 100644 --- a/parser/parsesession.h +++ b/parser/parsesession.h @@ -1,142 +1,142 @@ /************************************************************************************* * 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 KDEVGOLANGPARSESESSION_H #define KDEVGOLANGPARSESESSION_H #include #include #include #include #include #include "kdevgoparser_export.h" #include "parser/goast.h" namespace KDevPG { class MemoryPool; } namespace go { class Lexer; class Parser; class StartAst; } typedef QPair SimpleUse; class KDEVGOPARSER_EXPORT ParseSession { public: - + ParseSession(const QByteArray& contents, int priority, bool appendWithNewline=true); - + virtual ~ParseSession(); - + static KDevelop::IndexedString languageString(); - + bool startParsing(); - + bool parseExpression(go::ExpressionAst **node); - + go::StartAst* ast(); - + QString symbol(qint64 index); KDevelop::RangeInRevision findRange(go::AstNode* from, go::AstNode* to); KDevelop::IndexedString currentDocument(); void setCurrentDocument(const KDevelop::IndexedString& document); KDevelop::IndexedString url(); QList contextForImport(QString package); QList contextForThisPackage(KDevelop::IndexedString package); QList problems() const; /** * Schedules for parsing with given priority and features. * NOTE this will not schedule recursive imports (imports of imports and so on) * to schedule any file use BackgroundParser instead **/ bool scheduleForParsing(const KDevelop::IndexedString& url, int priority, KDevelop::TopDUContext::Features features); /** * Reschedules this file with same features. **/ void rescheduleThisFile(); void reparseImporters(KDevelop::DUContext* context); void setFeatures(KDevelop::TopDUContext::Features features); QString textForNode(go::AstNode* node); void setIncludePaths(const QList &paths); void setCanonicalImports(QHash* imports); /** * Returns doc comment preceding given token. * GoDoc comments are multilined dash-star-style comments (/\*) * or several consecutive single-lined dash-dash-style comments (//) * with no empty line between them. * Comment must start on a new line and end a line before given declaration. **/ QByteArray commentBeforeToken(qint64 token); /** * Don't use this function! * Most of the times you don't need to access lexer of parseSession directly, * This only exists, because parser test application uses DebugVisitor, which needs a lexer */ friend go::Lexer* getLexer(const ParseSession& session) { return session.m_lexer; } - + void mapAstUse(go::AstNode* node, const SimpleUse& use) { Q_UNUSED(node); Q_UNUSED(use); } private: - + bool lex(); - + KDevPG::MemoryPool* m_pool; go::Lexer* m_lexer; go::Parser* m_parser; go::StartAst* m_ast; QByteArray m_contents; - + int m_priority; KDevelop::IndexedString m_document; KDevelop::TopDUContext::Features m_features; bool forExport; QList m_includePaths; QHash* m_canonicalImports; QList m_problems; }; #endif