diff --git a/src/plugins/clangcodemodel/CMakeLists.txt b/src/plugins/clangcodemodel/CMakeLists.txt index 7d485f7a6f8..9d08105a6a2 100644 --- a/src/plugins/clangcodemodel/CMakeLists.txt +++ b/src/plugins/clangcodemodel/CMakeLists.txt @@ -16,6 +16,7 @@ add_qtc_plugin(ClangCodeModel clangcompletioncontextanalyzer.cpp clangcompletioncontextanalyzer.h clangconstants.h clangdclient.cpp clangdclient.h + clangdast.cpp clangdast.h clangdiagnostictooltipwidget.cpp clangdiagnostictooltipwidget.h clangdquickfixfactory.cpp clangdquickfixfactory.h clangdqpropertyhighlighter.cpp clangdqpropertyhighlighter.h diff --git a/src/plugins/clangcodemodel/clangcodemodel.qbs b/src/plugins/clangcodemodel/clangcodemodel.qbs index 94d92a19483..1c0ad2e7641 100644 --- a/src/plugins/clangcodemodel/clangcodemodel.qbs +++ b/src/plugins/clangcodemodel/clangcodemodel.qbs @@ -30,6 +30,8 @@ QtcPlugin { "clangcompletioncontextanalyzer.cpp", "clangcompletioncontextanalyzer.h", "clangconstants.h", + "clangdast.cpp", + "clangdast.h", "clangdclient.cpp", "clangdclient.h", "clangdiagnostictooltipwidget.cpp", diff --git a/src/plugins/clangcodemodel/clangdast.cpp b/src/plugins/clangcodemodel/clangdast.cpp new file mode 100644 index 00000000000..9e46c603b21 --- /dev/null +++ b/src/plugins/clangcodemodel/clangdast.cpp @@ -0,0 +1,410 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "clangdast.h" + +#include +#include +#include +#include +#include + +#include + +using namespace Core; +using namespace LanguageClient; +using namespace LanguageServerProtocol; +using namespace Utils; + +namespace ClangCodeModel::Internal { + +static constexpr char roleKey[] = "role"; +static constexpr char arcanaKey[] = "arcana"; + +QString ClangdAstNode::role() const { return typedValue(roleKey); } +QString ClangdAstNode::kind() const { return typedValue(kindKey); } +optional ClangdAstNode::detail() const { return optionalValue(detailKey); } +optional ClangdAstNode::arcana() const { return optionalValue(arcanaKey); } +Range ClangdAstNode::range() const { return typedValue(rangeKey); } +bool ClangdAstNode::hasRange() const { return contains(rangeKey); } +bool ClangdAstNode::isValid() const { return contains(roleKey) && contains(kindKey); } + +optional > ClangdAstNode::children() const +{ + return optionalArray(childrenKey); +} + +bool ClangdAstNode::arcanaContains(const QString &s) const +{ + const optional arcanaString = arcana(); + return arcanaString && arcanaString->contains(s); +} + +bool ClangdAstNode::isMemberFunctionCall() const +{ + return role() == "expression" && (kind() == "CXXMemberCall" + || (kind() == "Member" && arcanaContains("member function"))); +} + +bool ClangdAstNode::isPureVirtualDeclaration() const +{ + return role() == "declaration" && kind() == "CXXMethod" && arcanaContains("virtual pure"); +} + +bool ClangdAstNode::isPureVirtualDefinition() const +{ + return role() == "declaration" && kind() == "CXXMethod" && arcanaContains("' pure"); +} + +bool ClangdAstNode::mightBeAmbiguousVirtualCall() const +{ + if (!isMemberFunctionCall()) + return false; + bool hasBaseCast = false; + bool hasRecordType = false; + const QList childList = children().value_or(QList()); + for (const ClangdAstNode &c : childList) { + if (!hasBaseCast && c.detailIs("UncheckedDerivedToBase")) + hasBaseCast = true; + if (!hasRecordType && c.role() == "specifier" && c.kind() == "TypeSpec") + hasRecordType = true; + if (hasBaseCast && hasRecordType) + return false; + } + return true; +} + +bool ClangdAstNode::isTemplateParameterDeclaration() const +{ + return role() == "declaration" && (kind() == "TemplateTypeParm" + || kind() == "NonTypeTemplateParm"); +} + +QString ClangCodeModel::Internal::ClangdAstNode::type() const +{ + const optional arcanaString = arcana(); + if (!arcanaString) + return {}; + return typeFromPos(*arcanaString, 0); +} + +QString ClangdAstNode::typeFromPos(const QString &s, int pos) const +{ + const int quote1Offset = s.indexOf('\'', pos); + if (quote1Offset == -1) + return {}; + const int quote2Offset = s.indexOf('\'', quote1Offset + 1); + if (quote2Offset == -1) + return {}; + if (s.mid(quote2Offset + 1, 2) == ":'") + return typeFromPos(s, quote2Offset + 2); + return s.mid(quote1Offset + 1, quote2Offset - quote1Offset - 1); +} + +HelpItem::Category ClangdAstNode::qdocCategoryForDeclaration(HelpItem::Category fallback) +{ + const auto childList = children(); + if (!childList || childList->size() < 2) + return fallback; + const ClangdAstNode c1 = childList->first(); + if (c1.role() != "type" || c1.kind() != "Auto") + return fallback; + QList typeCandidates = {childList->at(1)}; + while (!typeCandidates.isEmpty()) { + const ClangdAstNode n = typeCandidates.takeFirst(); + if (n.role() == "type") { + if (n.kind() == "Enum") + return HelpItem::Enum; + if (n.kind() == "Record") + return HelpItem::ClassOrNamespace; + return fallback; + } + typeCandidates << n.children().value_or(QList()); + } + return fallback; +} + +bool ClangdAstNode::hasConstType() const +{ + QString theType = type(); + if (theType.endsWith("const")) + theType.chop(5); + + // We don't care about the "inner" type of templates. + const int openAngleBracketPos = theType.indexOf('<'); + if (openAngleBracketPos != -1) { + const int closingAngleBracketPos = theType.lastIndexOf('>'); + if (closingAngleBracketPos > openAngleBracketPos) { + theType = theType.left(openAngleBracketPos) + + theType.mid(closingAngleBracketPos + 1); + } + } + const int xrefCount = theType.count("&&"); + const int refCount = theType.count('&') - 2 * xrefCount; + const int ptrRefCount = theType.count('*') + refCount; + const int constCount = theType.count("const"); + if (ptrRefCount == 0) + return constCount > 0 || detailIs("LValueToRValue") || arcanaContains("xvalue"); + return ptrRefCount <= constCount; +} + +bool ClangdAstNode::childContainsRange(int index, const LanguageServerProtocol::Range &range) const +{ + const optional> childList = children(); + return childList && childList->size() > index && childList->at(index).range().contains(range); +} + +bool ClangdAstNode::hasChildWithRole(const QString &role) const +{ + return Utils::contains(children().value_or(QList()), + [&role](const ClangdAstNode &c) { return c.role() == role; }); +} + +QString ClangdAstNode::operatorString() const +{ + if (kind() == "BinaryOperator") + return detail().value_or(QString()); + QTC_ASSERT(kind() == "CXXOperatorCall", return {}); + const optional arcanaString = arcana(); + if (!arcanaString) + return {}; + const int closingQuoteOffset = arcanaString->lastIndexOf('\''); + if (closingQuoteOffset <= 0) + return {}; + const int openingQuoteOffset = arcanaString->lastIndexOf('\'', closingQuoteOffset - 1); + if (openingQuoteOffset == -1) + return {}; + return arcanaString->mid(openingQuoteOffset + 1, closingQuoteOffset + - openingQuoteOffset - 1); +} + +ClangdAstNode::FileStatus ClangdAstNode::fileStatus(const FilePath &thisFile) const +{ + const optional arcanaString = arcana(); + if (!arcanaString) + return FileStatus::Unknown; + + // Example arcanas: + // "FunctionDecl 0x7fffb5d0dbd0 line:1:6 func 'void ()'" + // "VarDecl 0x7fffb5d0dcf0 /tmp/test.cpp:2:10 b 'bool' cinit" + // The second one is for a particularly silly construction where the RHS of an + // initialization comes from an included header. + const int openPos = arcanaString->indexOf('<'); + if (openPos == -1) + return FileStatus::Unknown; + const int closePos = arcanaString->indexOf('>', openPos + 1); + if (closePos == -1) + return FileStatus::Unknown; + bool hasOurs = false; + bool hasOther = false; + for (int startPos = openPos + 1; startPos < closePos;) { + int colon1Pos = arcanaString->indexOf(':', startPos); + if (colon1Pos == -1 || colon1Pos > closePos) + break; + if (HostOsInfo::isWindowsHost()) + colon1Pos = arcanaString->indexOf(':', colon1Pos + 1); + if (colon1Pos == -1 || colon1Pos > closePos) + break; + const int colon2Pos = arcanaString->indexOf(':', colon1Pos + 2); + if (colon2Pos == -1 || colon2Pos > closePos) + break; + const int line = subViewEnd(*arcanaString, colon1Pos + 1, colon2Pos).toInt(); + if (line == 0) + break; + const QStringView fileOrLineString = subViewEnd(*arcanaString, startPos, colon1Pos); + if (fileOrLineString != QLatin1String("line")) { + if (FilePath::fromUserInput(fileOrLineString.toString()) == thisFile) + hasOurs = true; + else + hasOther = true; + } + const int commaPos = arcanaString->indexOf(',', colon2Pos + 2); + if (commaPos != -1) + startPos = commaPos + 2; + else + break; + } + if (hasOurs) + return hasOther ? FileStatus::Mixed : FileStatus::Ours; + return hasOther ? FileStatus::Foreign : FileStatus::Unknown; +} + +void ClangdAstNode::print(int indent) const +{ + (qDebug().noquote() << QByteArray(indent, ' ')).quote() << role() << kind() + << detail().value_or(QString()) << arcana().value_or(QString()) << range(); + for (const ClangdAstNode &c : children().value_or(QList())) + c.print(indent + 2); +} + +QStringView subViewLen(const QString &s, qsizetype start, qsizetype length) +{ + if (start < 0 || length < 0 || start + length > s.length()) + return {}; + return QStringView(s).mid(start, length); +} + +QStringView subViewEnd(const QString &s, qsizetype start, qsizetype end) +{ + return subViewLen(s, start, end - start); +} + +class AstPathCollector +{ +public: + AstPathCollector(const ClangdAstNode &root, const Range &range) + : m_root(root), m_range(range) {} + + ClangdAstPath collectPath() + { + if (!m_root.isValid()) + return {}; + visitNode(m_root, true); + return m_done ? m_path : m_longestSubPath; + } + +private: + void visitNode(const ClangdAstNode &node, bool isRoot = false) + { + if (!isRoot && (!node.hasRange() || !node.range().contains(m_range))) + return; + m_path << node; + + class PathDropper { + public: + PathDropper(AstPathCollector &collector) : m_collector(collector) {}; + ~PathDropper() { + if (m_collector.m_done) + return; + if (m_collector.m_path.size() > m_collector.m_longestSubPath.size()) + m_collector.m_longestSubPath = m_collector.m_path; + m_collector.m_path.removeLast(); + } + private: + AstPathCollector &m_collector; + } pathDropper(*this); + + // Still traverse the children, because they could have the same range. + if (node.range() == m_range) + m_done = true; + + const auto children = node.children(); + if (!children) + return; + + QList childrenToCheck; + if (node.kind() == "Function" || node.role() == "expression") { + // Functions and expressions can contain implicit nodes that make the list unsorted. + // They cannot be ignored, as we need to consider them in certain contexts. + // Therefore, the binary search cannot be used here. + childrenToCheck = *children; + } else { + for (auto it = std::lower_bound(children->cbegin(), children->cend(), m_range, + leftOfRange); + it != children->cend() && !m_range.isLeftOf(it->range()); ++it) { + childrenToCheck << *it; + } + } + + const bool wasDone = m_done; + for (const ClangdAstNode &child : qAsConst(childrenToCheck)) { + visitNode(child); + if (m_done && !wasDone) + break; + } + } + + static bool leftOfRange(const ClangdAstNode &node, const Range &range) + { + // Class and struct nodes can contain implicit constructors, destructors and + // operators, which appear at the end of the list, but whose range is the same + // as the class name. Therefore, we must force them not to compare less to + // anything else. + return node.range().isLeftOf(range) && !node.arcanaContains(" implicit "); + }; + + const ClangdAstNode &m_root; + const Range &m_range; + ClangdAstPath m_path; + ClangdAstPath m_longestSubPath; + bool m_done = false; +}; + +ClangdAstPath getAstPath(const ClangdAstNode &root, const Range &range) +{ + return AstPathCollector(root, range).collectPath(); +} + +ClangdAstPath getAstPath(const ClangdAstNode &root, const Position &pos) +{ + return getAstPath(root, Range(pos, pos)); +} + +MessageId requestAst(Client *client, const FilePath &filePath, const Range range, + const AstHandler &handler) +{ + class AstParams : public JsonObject + { + public: + AstParams(const TextDocumentIdentifier &document, const Range &range = {}) + { + setTextDocument(document); + if (range.isValid()) + setRange(range); + } + + using JsonObject::JsonObject; + + // The open file to inspect. + TextDocumentIdentifier textDocument() const + { return typedValue(textDocumentKey); } + void setTextDocument(const TextDocumentIdentifier &id) { insert(textDocumentKey, id); } + + // The region of the source code whose AST is fetched. The highest-level node that entirely + // contains the range is returned. + optional range() const { return optionalValue(rangeKey); } + void setRange(const Range &range) { insert(rangeKey, range); } + + bool isValid() const override { return contains(textDocumentKey); } + }; + + class AstRequest : public Request + { + public: + using Request::Request; + explicit AstRequest(const AstParams ¶ms) : Request("textDocument/ast", params) {} + }; + + AstRequest request(AstParams(TextDocumentIdentifier(DocumentUri::fromFilePath(filePath)), + range)); + request.setResponseCallback([handler, reqId = request.id()](AstRequest::Response response) { + const auto result = response.result(); + handler(result ? *result : ClangdAstNode(), reqId); + }); + client->sendContent(request, Client::SendDocUpdates::Ignore); + return request.id(); +} + +} // namespace ClangCodeModel::Internal diff --git a/src/plugins/clangcodemodel/clangdast.h b/src/plugins/clangcodemodel/clangdast.h new file mode 100644 index 00000000000..6bb46216d05 --- /dev/null +++ b/src/plugins/clangcodemodel/clangdast.h @@ -0,0 +1,123 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include +#include + +#include + +namespace LanguageClient { class Client; } +namespace LanguageServerProtocol { +class MessageId; +class Position; +class Range; +} +namespace Utils { class FilePath; } + +QT_BEGIN_NAMESPACE +class QStringView; +QT_END_NAMESPACE + +namespace ClangCodeModel::Internal { + +// string view helpers +QStringView subViewLen(const QString &s, qsizetype start, qsizetype length); +QStringView subViewEnd(const QString &s, qsizetype start, qsizetype end); + +class ClangdAstNode : public LanguageServerProtocol::JsonObject +{ +public: + using JsonObject::JsonObject; + + // The general kind of node, such as “expression”. Corresponds to clang’s base AST node type, + // such as Expr. The most common are “expression”, “statement”, “type” and “declaration”. + QString role() const; + + // The specific kind of node, such as “BinaryOperator”. Corresponds to clang’s concrete + // node class, with Expr etc suffix dropped. + QString kind() const; + + // Brief additional details, such as ‘||’. Information present here depends on the node kind. + Utils::optional detail() const; + + // One line dump of information, similar to that printed by clang -Xclang -ast-dump. + // Only available for certain types of nodes. + Utils::optional arcana() const; + + // The part of the code that produced this node. Missing for implicit nodes, nodes produced + // by macro expansion, etc. + LanguageServerProtocol::Range range() const; + + // Descendants describing the internal structure. The tree of nodes is similar to that printed + // by clang -Xclang -ast-dump, or that traversed by clang::RecursiveASTVisitor. + Utils::optional> children() const; + + bool hasRange() const; + bool arcanaContains(const QString &s) const; + bool detailIs(const QString &s) const { return detail() && *detail() == s; } + bool isMemberFunctionCall() const; + bool isPureVirtualDeclaration() const; + bool isPureVirtualDefinition() const; + bool mightBeAmbiguousVirtualCall() const; + bool isNamespace() const { return role() == "declaration" && kind() == "Namespace"; } + bool isTemplateParameterDeclaration() const;; + QString type() const; + QString typeFromPos(const QString &s, int pos) const; + Core::HelpItem::Category qdocCategoryForDeclaration(Core::HelpItem::Category fallback); + + // Returns true <=> the type is "recursively const". + // E.g. returns true for "const int &", "const int *" and "const int * const *", + // and false for "int &" and "const int **". + // For non-pointer types such as "int", we check whether they are used as lvalues + // or rvalues. + bool hasConstType() const; + + bool childContainsRange(int index, const LanguageServerProtocol::Range &range) const; + bool hasChildWithRole(const QString &role) const; + QString operatorString() const; + + enum class FileStatus { Ours, Foreign, Mixed, Unknown }; + FileStatus fileStatus(const Utils::FilePath &thisFile) const; + + // For debugging. + void print(int indent = 0) const; + + bool isValid() const override; +}; +using ClangdAstPath = QList; + +ClangdAstPath getAstPath(const ClangdAstNode &root, const LanguageServerProtocol::Range &range); +ClangdAstPath getAstPath(const ClangdAstNode &root, const LanguageServerProtocol::Position &pos); + +using AstHandler = std::function; +LanguageServerProtocol::MessageId requestAst(LanguageClient::Client *client, + const Utils::FilePath &filePath, const LanguageServerProtocol::Range range, + const AstHandler &handler); + +} // namespace ClangCodeModel::Internal + diff --git a/src/plugins/clangcodemodel/clangdclient.cpp b/src/plugins/clangcodemodel/clangdclient.cpp index 39a3d2d6307..ef045b5da41 100644 --- a/src/plugins/clangcodemodel/clangdclient.cpp +++ b/src/plugins/clangcodemodel/clangdclient.cpp @@ -27,6 +27,7 @@ #include "clangcompletioncontextanalyzer.h" #include "clangconstants.h" +#include "clangdast.h" #include "clangdlocatorfilters.h" #include "clangdqpropertyhighlighter.h" #include "clangmodelmanagersupport.h" @@ -116,368 +117,7 @@ static Q_LOGGING_CATEGORY(clangdLogCompletion, "qtc.clangcodemodel.clangd.comple QtWarningMsg); static QString indexingToken() { return "backgroundIndexProgress"; } -static QStringView subViewLen(const QString &s, qsizetype start, qsizetype length) -{ - if (start < 0 || length < 0 || start + length > s.length()) - return {}; - return QStringView(s).mid(start, length); -} - -static QStringView subViewEnd(const QString &s, qsizetype start, qsizetype end) -{ - return subViewLen(s, start, end - start); -} - -class AstNode : public JsonObject -{ -public: - using JsonObject::JsonObject; - - static constexpr char roleKey[] = "role"; - static constexpr char arcanaKey[] = "arcana"; - - // The general kind of node, such as “expression”. Corresponds to clang’s base AST node type, - // such as Expr. The most common are “expression”, “statement”, “type” and “declaration”. - QString role() const { return typedValue(roleKey); } - - // The specific kind of node, such as “BinaryOperator”. Corresponds to clang’s concrete - // node class, with Expr etc suffix dropped. - QString kind() const { return typedValue(kindKey); } - - // Brief additional details, such as ‘||’. Information present here depends on the node kind. - Utils::optional detail() const { return optionalValue(detailKey); } - - // One line dump of information, similar to that printed by clang -Xclang -ast-dump. - // Only available for certain types of nodes. - Utils::optional arcana() const { return optionalValue(arcanaKey); } - - // The part of the code that produced this node. Missing for implicit nodes, nodes produced - // by macro expansion, etc. - Range range() const { return typedValue(rangeKey); } - - // Descendants describing the internal structure. The tree of nodes is similar to that printed - // by clang -Xclang -ast-dump, or that traversed by clang::RecursiveASTVisitor. - Utils::optional> children() const { return optionalArray(childrenKey); } - - bool hasRange() const { return contains(rangeKey); } - - bool arcanaContains(const QString &s) const - { - const Utils::optional arcanaString = arcana(); - return arcanaString && arcanaString->contains(s); - } - - bool detailIs(const QString &s) const - { - return detail() && *detail() == s; - } - - bool isMemberFunctionCall() const - { - return role() == "expression" && (kind() == "CXXMemberCall" - || (kind() == "Member" && arcanaContains("member function"))); - } - - bool isPureVirtualDeclaration() const - { - return role() == "declaration" && kind() == "CXXMethod" && arcanaContains("virtual pure"); - } - - bool isPureVirtualDefinition() const - { - return role() == "declaration" && kind() == "CXXMethod" && arcanaContains("' pure"); - } - - bool mightBeAmbiguousVirtualCall() const - { - if (!isMemberFunctionCall()) - return false; - bool hasBaseCast = false; - bool hasRecordType = false; - const QList childList = children().value_or(QList()); - for (const AstNode &c : childList) { - if (!hasBaseCast && c.detailIs("UncheckedDerivedToBase")) - hasBaseCast = true; - if (!hasRecordType && c.role() == "specifier" && c.kind() == "TypeSpec") - hasRecordType = true; - if (hasBaseCast && hasRecordType) - return false; - } - return true; - } - - bool isNamespace() const { return role() == "declaration" && kind() == "Namespace"; } - - bool isTemplateParameterDeclaration() const - { - return role() == "declaration" && (kind() == "TemplateTypeParm" - || kind() == "NonTypeTemplateParm"); - }; - - QString type() const - { - const Utils::optional arcanaString = arcana(); - if (!arcanaString) - return {}; - return typeFromPos(*arcanaString, 0); - } - - QString typeFromPos(const QString &s, int pos) const - { - const int quote1Offset = s.indexOf('\'', pos); - if (quote1Offset == -1) - return {}; - const int quote2Offset = s.indexOf('\'', quote1Offset + 1); - if (quote2Offset == -1) - return {}; - if (s.mid(quote2Offset + 1, 2) == ":'") - return typeFromPos(s, quote2Offset + 2); - return s.mid(quote1Offset + 1, quote2Offset - quote1Offset - 1); - } - - HelpItem::Category qdocCategoryForDeclaration(HelpItem::Category fallback) - { - const auto childList = children(); - if (!childList || childList->size() < 2) - return fallback; - const AstNode c1 = childList->first(); - if (c1.role() != "type" || c1.kind() != "Auto") - return fallback; - QList typeCandidates = {childList->at(1)}; - while (!typeCandidates.isEmpty()) { - const AstNode n = typeCandidates.takeFirst(); - if (n.role() == "type") { - if (n.kind() == "Enum") - return HelpItem::Enum; - if (n.kind() == "Record") - return HelpItem::ClassOrNamespace; - return fallback; - } - typeCandidates << n.children().value_or(QList()); - } - return fallback; - } - - // Returns true <=> the type is "recursively const". - // E.g. returns true for "const int &", "const int *" and "const int * const *", - // and false for "int &" and "const int **". - // For non-pointer types such as "int", we check whether they are uses as lvalues - // or rvalues. - bool hasConstType() const - { - QString theType = type(); - if (theType.endsWith("const")) - theType.chop(5); - - // We don't care about the "inner" type of templates. - const int openAngleBracketPos = theType.indexOf('<'); - if (openAngleBracketPos != -1) { - const int closingAngleBracketPos = theType.lastIndexOf('>'); - if (closingAngleBracketPos > openAngleBracketPos) { - theType = theType.left(openAngleBracketPos) - + theType.mid(closingAngleBracketPos + 1); - } - } - const int xrefCount = theType.count("&&"); - const int refCount = theType.count('&') - 2 * xrefCount; - const int ptrRefCount = theType.count('*') + refCount; - const int constCount = theType.count("const"); - if (ptrRefCount == 0) - return constCount > 0 || detailIs("LValueToRValue") || arcanaContains("xvalue"); - return ptrRefCount <= constCount; - } - - bool childContainsRange(int index, const Range &range) const - { - const Utils::optional> childList = children(); - return childList && childList->size() > index - && childList->at(index).range().contains(range); - } - - bool hasChildWithRole(const QString &role) const - { - return Utils::contains(children().value_or(QList()), [&role](const AstNode &c) { - return c.role() == role; - }); - } - - QString operatorString() const - { - if (kind() == "BinaryOperator") - return detail().value_or(QString()); - QTC_ASSERT(kind() == "CXXOperatorCall", return {}); - const Utils::optional arcanaString = arcana(); - if (!arcanaString) - return {}; - const int closingQuoteOffset = arcanaString->lastIndexOf('\''); - if (closingQuoteOffset <= 0) - return {}; - const int openingQuoteOffset = arcanaString->lastIndexOf('\'', closingQuoteOffset - 1); - if (openingQuoteOffset == -1) - return {}; - return arcanaString->mid(openingQuoteOffset + 1, closingQuoteOffset - - openingQuoteOffset - 1); - } - - enum class FileStatus { Ours, Foreign, Mixed, Unknown }; - FileStatus fileStatus(const Utils::FilePath &thisFile) const - { - const Utils::optional arcanaString = arcana(); - if (!arcanaString) - return FileStatus::Unknown; - - // Example arcanas: - // "FunctionDecl 0x7fffb5d0dbd0 line:1:6 func 'void ()'" - // "VarDecl 0x7fffb5d0dcf0 /tmp/test.cpp:2:10 b 'bool' cinit" - // The second one is for a particularly silly construction where the RHS of an - // initialization comes from an included header. - const int openPos = arcanaString->indexOf('<'); - if (openPos == -1) - return FileStatus::Unknown; - const int closePos = arcanaString->indexOf('>', openPos + 1); - if (closePos == -1) - return FileStatus::Unknown; - bool hasOurs = false; - bool hasOther = false; - for (int startPos = openPos + 1; startPos < closePos;) { - int colon1Pos = arcanaString->indexOf(':', startPos); - if (colon1Pos == -1 || colon1Pos > closePos) - break; - if (Utils::HostOsInfo::isWindowsHost()) - colon1Pos = arcanaString->indexOf(':', colon1Pos + 1); - if (colon1Pos == -1 || colon1Pos > closePos) - break; - const int colon2Pos = arcanaString->indexOf(':', colon1Pos + 2); - if (colon2Pos == -1 || colon2Pos > closePos) - break; - const int line = subViewEnd(*arcanaString, colon1Pos + 1, colon2Pos).toString().toInt(); // TODO: Drop toString() once we require >= Qt 5.15 - if (line == 0) - break; - const QStringView fileOrLineString = subViewEnd(*arcanaString, startPos, colon1Pos); - if (fileOrLineString != QLatin1String("line")) { - if (Utils::FilePath::fromUserInput(fileOrLineString.toString()) == thisFile) - hasOurs = true; - else - hasOther = true; - } - const int commaPos = arcanaString->indexOf(',', colon2Pos + 2); - if (commaPos != -1) - startPos = commaPos + 2; - else - break; - } - if (hasOurs) - return hasOther ? FileStatus::Mixed : FileStatus::Ours; - return hasOther ? FileStatus::Foreign : FileStatus::Unknown; - } - - // For debugging. - void print(int indent = 0) const - { - (qDebug().noquote() << QByteArray(indent, ' ')).quote() << role() << kind() - << detail().value_or(QString()) << arcana().value_or(QString()) - << range(); - for (const AstNode &c : children().value_or(QList())) - c.print(indent + 2); - } - - bool isValid() const override - { - return contains(roleKey) && contains(kindKey); - } -}; - -class AstPathCollector -{ -public: - AstPathCollector(const AstNode &root, const Range &range) : m_root(root), m_range(range) {} - - QList collectPath() - { - if (!m_root.isValid()) - return {}; - visitNode(m_root, true); - return m_done ? m_path : m_longestSubPath; - } - -private: - void visitNode(const AstNode &node, bool isRoot = false) - { - if (!isRoot && (!node.hasRange() || !node.range().contains(m_range))) - return; - m_path << node; - - class PathDropper { - public: - PathDropper(AstPathCollector &collector) : m_collector(collector) {}; - ~PathDropper() { - if (m_collector.m_done) - return; - if (m_collector.m_path.size() > m_collector.m_longestSubPath.size()) - m_collector.m_longestSubPath = m_collector.m_path; - m_collector.m_path.removeLast(); - } - private: - AstPathCollector &m_collector; - } pathDropper(*this); - - // Still traverse the children, because they could have the same range. - if (node.range() == m_range) - m_done = true; - - const auto children = node.children(); - if (!children) - return; - - QList childrenToCheck; - if (node.kind() == "Function" || node.role() == "expression") { - // Functions and expressions can contain implicit nodes that make the list unsorted. - // They cannot be ignored, as we need to consider them in certain contexts. - // Therefore, the binary search cannot be used here. - childrenToCheck = *children; - } else { - for (auto it = std::lower_bound(children->cbegin(), children->cend(), m_range, - leftOfRange); - it != children->cend() && !m_range.isLeftOf(it->range()); ++it) { - childrenToCheck << *it; - } - } - - const bool wasDone = m_done; - for (const AstNode &child : qAsConst(childrenToCheck)) { - visitNode(child); - if (m_done && !wasDone) - break; - } - } - - static bool leftOfRange(const AstNode &node, const Range &range) - { - // Class and struct nodes can contain implicit constructors, destructors and - // operators, which appear at the end of the list, but whose range is the same - // as the class name. Therefore, we must force them not to compare less to - // anything else. - return node.range().isLeftOf(range) && !node.arcanaContains(" implicit "); - }; - - const AstNode &m_root; - const Range &m_range; - QList m_path; - QList m_longestSubPath; - bool m_done = false; -}; - -static QList getAstPath(const AstNode &root, const Range &range) -{ - return AstPathCollector(root, range).collectPath(); -} - -static QList getAstPath(const AstNode &root, const Position &pos) -{ - return getAstPath(root, Range(pos, pos)); -} - -static Usage::Type getUsageType(const QList &path) +static Usage::Type getUsageType(const ClangdAstPath &path) { bool potentialWrite = false; bool isFunction = false; @@ -645,7 +285,7 @@ class ReferencesFileData { public: QList> rangesAndLineText; QString fileContent; - AstNode ast; + ClangdAstNode ast; }; class ReplacementData { public: @@ -761,8 +401,8 @@ public: Utils::Link defLink; QList allLinks; QHash declDefMap; - Utils::optional cursorNode; - AstNode defLinkNode; + Utils::optional cursorNode; + ClangdAstNode defLinkNode; SymbolDataList symbolsToDisplay; std::set openedFiles; VirtualFunctionAssistProcessor *virtualFuncAssistProcessor = nullptr; @@ -777,11 +417,11 @@ public: : id(id), document(doc), uri(DocumentUri::fromFilePath(doc->filePath())), cursor(cursor), editorWidget(editorWidget), callback(std::move(callback)) {} - Utils::optional getFunctionNode() const + Utils::optional getFunctionNode() const { QTC_ASSERT(ast, return {}); - const QList path = getAstPath(*ast, Range(cursor)); + const ClangdAstPath path = getAstPath(*ast, Range(cursor)); for (auto it = path.rbegin(); it != path.rend(); ++it) { if (it->role() == "declaration" && (it->kind() == "CXXMethod" || it->kind() == "CXXConversion" @@ -792,7 +432,7 @@ public: return {}; } - QTextCursor cursorForFunctionName(const AstNode &functionNode) const + QTextCursor cursorForFunctionName(const ClangdAstNode &functionNode) const { QTC_ASSERT(docSymbols, return {}); @@ -818,7 +458,7 @@ public: const QPointer editorWidget; Utils::ProcessLinkCallback callback; Utils::optional docSymbols; - Utils::optional ast; + Utils::optional ast; }; class LocalRefsData { @@ -1271,7 +911,7 @@ public: enum class AstCallbackMode { SyncIfPossible, AlwaysAsync }; using TextDocOrFile = const Utils::variant; - using AstHandler = const std::function; + using AstHandler = const std::function; MessageId getAndHandleAst(TextDocOrFile &doc, AstHandler &astHandler, AstCallbackMode callbackMode, const Range &range = {}); @@ -1287,8 +927,8 @@ public: QHash parserConfigs; QHash issuePaneEntries; - VersionedDataCache astCache; - VersionedDataCache externalAstCache; + VersionedDataCache astCache; + VersionedDataCache externalAstCache; TaskTimer highlightingTimer{"highlighting"}; quint64 nextJobId = 0; bool isFullyIndexed = false; @@ -2053,7 +1693,7 @@ void ClangdClient::Private::handleFindUsagesResult(quint64 key, const QListopenExtraFile(it.key().toFilePath(), it->fileContent); it->fileContent.clear(); const auto docVariant = doc ? TextDocOrFile(doc) : TextDocOrFile(it.key().toFilePath()); - const auto astHandler = [this, key, loc = it.key()](const AstNode &ast, + const auto astHandler = [this, key, loc = it.key()](const ClangdAstNode &ast, const MessageId &reqId) { qCDebug(clangdLog) << "AST for" << loc.toFilePath(); const auto refData = runningFindUsages.find(key); @@ -2212,7 +1852,7 @@ void ClangdClient::followSymbol(TextDocument *document, symbolSupport().findLinkAt(document, adjustedCursor, std::move(gotoDefCallback), true); const auto astHandler = [this, id = d->followSymbolData->id] - (const AstNode &ast, const MessageId &) { + (const ClangdAstNode &ast, const MessageId &) { qCDebug(clangdLog) << "received ast response for cursor"; if (!d->followSymbolData || d->followSymbolData->id != id) return; @@ -2236,7 +1876,7 @@ void ClangdClient::switchDeclDef(TextDocument *document, const QTextCursor &curs std::move(callback)); // Retrieve AST and document symbols. - const auto astHandler = [this, id = d->switchDeclDefData->id](const AstNode &ast, + const auto astHandler = [this, id = d->switchDeclDefData->id](const ClangdAstNode &ast, const MessageId &) { qCDebug(clangdLog) << "received ast for decl/def switch"; if (!d->switchDeclDefData || d->switchDeclDefData->id != id @@ -2304,7 +1944,7 @@ void ClangdClient::findLocalUsages(TextDocument *document, const QTextCursor &cu } // Step 2: Get AST and check whether it's a local variable. - const auto astHandler = [this, link, id](const AstNode &ast, const MessageId &) { + const auto astHandler = [this, link, id](const ClangdAstNode &ast, const MessageId &) { qCDebug(clangdLog) << "received ast response"; if (!d->localRefsData || id != d->localRefsData->id) return; @@ -2314,7 +1954,7 @@ void ClangdClient::findLocalUsages(TextDocument *document, const QTextCursor &cu } const Position linkPos(link.targetLine - 1, link.targetColumn); - const QList astPath = getAstPath(ast, linkPos); + const ClangdAstPath astPath = getAstPath(ast, linkPos); bool isVar = false; for (auto it = astPath.rbegin(); it != astPath.rend(); ++it) { if (it->role() == "declaration" @@ -2404,26 +2044,26 @@ void ClangdClient::gatherHelpItemForTooltip(const HoverRequest::Response &hoverR const TextDocument * const doc = documentForFilePath(uri.toFilePath()); QTC_ASSERT(doc, return); - const auto astHandler = [this, uri, hoverResponse](const AstNode &ast, const MessageId &) { + const auto astHandler = [this, uri, hoverResponse](const ClangdAstNode &ast, const MessageId &) { const MessageId id = hoverResponse.id(); Range range; if (const Utils::optional result = hoverResponse.result()) { if (auto hover = Utils::get_if(&(*result))) range = hover->range().value_or(Range()); } - const QList path = getAstPath(ast, range); + const ClangdAstPath path = getAstPath(ast, range); if (path.isEmpty()) { d->setHelpItemForTooltip(id); return; } - AstNode node = path.last(); + ClangdAstNode node = path.last(); if (node.role() == "expression" && node.kind() == "ImplicitCast") { - const Utils::optional> children = node.children(); + const Utils::optional> children = node.children(); if (children && !children->isEmpty()) node = children->first(); } while (node.kind() == "Qualified") { - const Utils::optional> children = node.children(); + const Utils::optional> children = node.children(); if (children && !children->isEmpty()) node = children->first(); } @@ -2707,7 +2347,7 @@ void ClangdClient::Private::handleGotoImplementationResult( const Position defLinkPos(followSymbolData->defLink.targetLine - 1, followSymbolData->defLink.targetColumn); const auto astHandler = [this, id = followSymbolData->id] - (const AstNode &ast, const MessageId &) { + (const ClangdAstNode &ast, const MessageId &) { qCDebug(clangdLog) << "received ast response for def link"; if (!followSymbolData || followSymbolData->id != id) return; @@ -2754,7 +2394,7 @@ void ClangdClient::Private::handleDeclDefSwitchReplies() // on a function return type, or ... if (clangdLogAst().isDebugEnabled()) switchDeclDefData->ast->print(0); - const Utils::optional functionNode = switchDeclDefData->getFunctionNode(); + const Utils::optional functionNode = switchDeclDefData->getFunctionNode(); if (!functionNode) { switchDeclDefData.reset(); return; @@ -2815,10 +2455,10 @@ QTextCursor ClangdClient::Private::adjustedCursor(const QTextCursor &cursor, case T_DOT: break; case T_ARROW: { - const Utils::optional clangdAst = astCache.get(doc); + const Utils::optional clangdAst = astCache.get(doc); if (!clangdAst) return cursor; - const QList clangdAstPath = getAstPath(*clangdAst, Range(cursor)); + const ClangdAstPath clangdAstPath = getAstPath(*clangdAst, Range(cursor)); for (auto it = clangdAstPath.rbegin(); it != clangdAstPath.rend(); ++it) { if (it->detailIs("operator->") && it->arcanaContains("CXXMethod")) return cursor; @@ -2903,29 +2543,29 @@ class ExtraHighlightingResultsCollector public: ExtraHighlightingResultsCollector(QFutureInterface &future, HighlightingResults &results, - const Utils::FilePath &filePath, const AstNode &ast, + const Utils::FilePath &filePath, const ClangdAstNode &ast, const QTextDocument *doc, const QString &docContent); void collect(); private: static bool lessThan(const HighlightingResult &r1, const HighlightingResult &r2); static int onlyIndexOf(const QStringView &text, const QStringView &subString, int from = 0); - int posForNodeStart(const AstNode &node) const; - int posForNodeEnd(const AstNode &node) const; + int posForNodeStart(const ClangdAstNode &node) const; + int posForNodeEnd(const ClangdAstNode &node) const; void insertResult(const HighlightingResult &result); - void insertResult(const AstNode &node, TextStyle style); + void insertResult(const ClangdAstNode &node, TextStyle style); void insertAngleBracketInfo(int searchStart1, int searchEnd1, int searchStart2, int searchEnd2); void setResultPosFromRange(HighlightingResult &result, const Range &range); - void collectFromNode(const AstNode &node); - void visitNode(const AstNode&node); + void collectFromNode(const ClangdAstNode &node); + void visitNode(const ClangdAstNode&node); QFutureInterface &m_future; HighlightingResults &m_results; const Utils::FilePath m_filePath; - const AstNode &m_ast; + const ClangdAstNode &m_ast; const QTextDocument * const m_doc; const QString &m_docContent; - AstNode::FileStatus m_currentFileStatus = AstNode::FileStatus::Unknown; + ClangdAstNode::FileStatus m_currentFileStatus = ClangdAstNode::FileStatus::Unknown; }; // clangd reports also the #ifs, #elses and #endifs around the disabled code as disabled, @@ -3012,7 +2652,7 @@ static QList cleanupDisabledCode(HighlightingResults &results, const static void semanticHighlighter(QFutureInterface &future, const Utils::FilePath &filePath, const QList &tokens, - const QString &docContents, const AstNode &ast, + const QString &docContents, const ClangdAstNode &ast, const QPointer &textDocument, int docRevision, const QVersionNumber &clangdVersion, const TaskTimer &taskTimer) @@ -3035,7 +2675,7 @@ static void semanticHighlighter(QFutureInterface &future, if (token.type != "variable" && token.type != "property" && token.type != "parameter") return false; const Range range = tokenRange(token); - const QList path = getAstPath(ast, range); + const ClangdAstPath path = getAstPath(ast, range); if (path.size() < 2) return false; if (token.type == "property" @@ -3056,7 +2696,7 @@ static void semanticHighlighter(QFutureInterface &future, // know whether the argument is passed as const or not. if (it->arcanaContains("dependent type")) return false; - const QList children = it->children().value_or(QList()); + const QList children = it->children().value_or(QList()); return children.isEmpty() || (children.first().range() != (it - 1)->range() && children.first().kind() != "UnresolvedLookup"); @@ -3065,7 +2705,7 @@ static void semanticHighlighter(QFutureInterface &future, // The token should get marked for e.g. lambdas, but not for assignment operators, // where the user sees that it's being written. if (it->kind() == "CXXOperatorCall") { - const QList children = it->children().value_or(QList()); + const QList children = it->children().value_or(QList()); // Child 1 is the call itself, Child 2 is the named entity on which the call happens // (a lambda or a class instance), after that follow the actual call arguments. @@ -3082,9 +2722,9 @@ static void semanticHighlighter(QFutureInterface &future, if (children.at(1).range().contains(range)) return false; - QList firstChildTree{children.first()}; + QList firstChildTree{children.first()}; while (!firstChildTree.isEmpty()) { - const AstNode n = firstChildTree.takeFirst(); + const ClangdAstNode n = firstChildTree.takeFirst(); const QString detail = n.detail().value_or(QString()); if (detail.startsWith("operator")) { return !detail.contains('=') @@ -3092,7 +2732,7 @@ static void semanticHighlighter(QFutureInterface &future, && !detail.contains("<<") && !detail.contains(">>") && !detail.contains("*"); } - firstChildTree << n.children().value_or(QList()); + firstChildTree << n.children().value_or(QList()); } return true; } @@ -3107,7 +2747,7 @@ static void semanticHighlighter(QFutureInterface &future, if (it->kind() == "CXXMemberCall") { if (it == path.rbegin()) return false; - const QList children = it->children().value_or(QList()); + const QList children = it->children().value_or(QList()); QTC_ASSERT(!children.isEmpty(), return false); // The called object is never displayed as an output parameter. @@ -3141,9 +2781,9 @@ static void semanticHighlighter(QFutureInterface &future, styles.mainStyle = token.modifiers.contains(QLatin1String("virtual")) ? C_VIRTUAL_METHOD : C_FUNCTION; if (ast.isValid()) { - const QList path = getAstPath(ast, tokenRange(token)); + const ClangdAstPath path = getAstPath(ast, tokenRange(token)); if (path.length() > 1) { - const AstNode declNode = path.at(path.length() - 2); + const ClangdAstNode declNode = path.at(path.length() - 2); if (declNode.kind() == "Function" || declNode.kind() == "CXXMethod") { if (clangdVersion < QVersionNumber(14) && declNode.arcanaContains("' virtual")) { @@ -3160,13 +2800,13 @@ static void semanticHighlighter(QFutureInterface &future, // clang hardly ever differentiates between constructors and the associated class, // whereas we highlight constructors as functions. if (ast.isValid()) { - const QList path = getAstPath(ast, tokenRange(token)); + const ClangdAstPath path = getAstPath(ast, tokenRange(token)); if (!path.isEmpty()) { if (path.last().kind() == "CXXConstructor") { if (!path.last().arcanaContains("implicit")) styles.mainStyle = C_FUNCTION; } else if (path.last().kind() == "Record" && path.length() > 1) { - const AstNode node = path.at(path.length() - 2); + const ClangdAstNode node = path.at(path.length() - 2); if (node.kind() == "CXXDestructor" && !node.arcanaContains("implicit")) { styles.mainStyle = C_FUNCTION; @@ -3284,7 +2924,7 @@ void ClangdClient::Private::handleSemanticTokens(TextDocument *doc, qCDebug(clangdLogHighlight()) << '\t' << t.line << t.column << t.length << t.type << t.modifiers; - const auto astHandler = [this, tokens, doc, version](const AstNode &ast, const MessageId &) { + const auto astHandler = [this, tokens, doc, version](const ClangdAstNode &ast, const MessageId &) { FinalizingSubtaskTimer t(highlightingTimer); if (!q->documentOpen(doc)) return; @@ -3759,48 +3399,10 @@ MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc, } // Otherwise retrieve the AST from clangd. - - class AstParams : public JsonObject - { - public: - AstParams(const TextDocumentIdentifier &document, const Range &range = {}) - { - setTextDocument(document); - if (range.isValid()) - setRange(range); - } - - using JsonObject::JsonObject; - - // The open file to inspect. - TextDocumentIdentifier textDocument() const - { return typedValue(textDocumentKey); } - void setTextDocument(const TextDocumentIdentifier &id) { insert(textDocumentKey, id); } - - // The region of the source code whose AST is fetched. The highest-level node that entirely - // contains the range is returned. - Utils::optional range() const { return optionalValue(rangeKey); } - void setRange(const Range &range) { insert(rangeKey, range); } - - bool isValid() const override { return contains(textDocumentKey); } - }; - - class AstRequest : public Request - { - public: - using Request::Request; - explicit AstRequest(const AstParams ¶ms) : Request("textDocument/ast", params) {} - }; - - AstRequest request(AstParams(TextDocumentIdentifier(DocumentUri::fromFilePath(filePath)), - range)); - request.setResponseCallback([this, filePath, guardedTextDoc = QPointer(textDoc), astHandler, - fullAstRequested, docRev = textDoc ? getRevision(textDoc) : -1, - fileRev = getRevision(filePath), reqId = request.id()] - (AstRequest::Response response) { + const auto wrapperHandler = [this, filePath, guardedTextDoc = QPointer(textDoc), astHandler, + fullAstRequested, docRev = textDoc ? getRevision(textDoc) : -1, + fileRev = getRevision(filePath)](const ClangdAstNode &ast, const MessageId &reqId) { qCDebug(clangdLog) << "retrieved AST from clangd"; - const auto result = response.result(); - const AstNode ast = result ? *result : AstNode(); if (fullAstRequested) { if (guardedTextDoc) { if (docRev == getRevision(guardedTextDoc)) @@ -3810,15 +3412,14 @@ MessageId ClangdClient::Private::getAndHandleAst(const TextDocOrFile &doc, } } astHandler(ast, reqId); - }); + }; qCDebug(clangdLog) << "requesting AST for" << filePath; - q->sendContent(request, SendDocUpdates::Ignore); - return request.id(); + return requestAst(q, filePath, range, wrapperHandler); } ExtraHighlightingResultsCollector::ExtraHighlightingResultsCollector( QFutureInterface &future, HighlightingResults &results, - const Utils::FilePath &filePath, const AstNode &ast, const QTextDocument *doc, + const Utils::FilePath &filePath, const ClangdAstNode &ast, const QTextDocument *doc, const QString &docContent) : m_future(future), m_results(results), m_filePath(filePath), m_ast(ast), m_doc(doc), m_docContent(docContent) @@ -3875,13 +3476,13 @@ int ExtraHighlightingResultsCollector::onlyIndexOf(const QStringView &text, // Unfortunately, the exact position of a specific token is usually not // recorded in the AST, so if we need that, we have to search for it textually. // In corner cases, this might get sabotaged by e.g. comments, in which case we give up. -int ExtraHighlightingResultsCollector::posForNodeStart(const AstNode &node) const +int ExtraHighlightingResultsCollector::posForNodeStart(const ClangdAstNode &node) const { return Utils::Text::positionInText(m_doc, node.range().start().line() + 1, node.range().start().character() + 1); } -int ExtraHighlightingResultsCollector::posForNodeEnd(const AstNode &node) const +int ExtraHighlightingResultsCollector::posForNodeEnd(const ClangdAstNode &node) const { return Utils::Text::positionInText(m_doc, node.range().end().line() + 1, node.range().end().character() + 1); @@ -3920,7 +3521,7 @@ void ExtraHighlightingResultsCollector::insertResult(const HighlightingResult &r } } -void ExtraHighlightingResultsCollector::insertResult(const AstNode &node, TextStyle style) +void ExtraHighlightingResultsCollector::insertResult(const ClangdAstNode &node, TextStyle style) { HighlightingResult result; result.useTextSyles = true; @@ -3979,7 +3580,7 @@ void ExtraHighlightingResultsCollector::setResultPosFromRange(HighlightingResult result.length = endPos.toPositionInDocument(m_doc) - startPos.toPositionInDocument(m_doc); } -void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node) +void ExtraHighlightingResultsCollector::collectFromNode(const ClangdAstNode &node) { if (node.kind() == "UserDefinedLiteral") return; @@ -4010,7 +3611,7 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node) const bool isDeclaration = node.role() == "declaration"; const int nodeStartPos = posForNodeStart(node); const int nodeEndPos = posForNodeEnd(node); - const QList children = node.children().value_or(QList()); + const QList children = node.children().value_or(QList()); // Match question mark and colon in ternary operators. if (isExpression && node.kind() == "ConditionalOperator") { @@ -4060,7 +3661,7 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node) const QString classOrFunctionKind = QLatin1String(node.kind() == "FunctionTemplate" ? "Function" : "CXXRecord"); const auto functionOrClassIt = std::find_if(children.begin(), children.end(), - [&classOrFunctionKind](const AstNode &n) { + [&classOrFunctionKind](const ClangdAstNode &n) { return n.role() == "declaration" && n.kind() == classOrFunctionKind; }); if (functionOrClassIt == children.end() || functionOrClassIt == children.begin()) @@ -4073,7 +3674,7 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node) return; } - const auto isTemplateParamDecl = [](const AstNode &node) { + const auto isTemplateParamDecl = [](const ClangdAstNode &node) { return node.isTemplateParameterDeclaration(); }; if (isDeclaration && node.kind() == "TypeAliasTemplate") { @@ -4092,7 +3693,7 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node) isTemplateParamDecl); QTC_ASSERT(lastTemplateParam != children.rend(), return); const auto typeAlias = std::find_if(children.begin(), children.end(), - [](const AstNode &n) { return n.kind() == "TypeAlias"; }); + [](const ClangdAstNode &n) { return n.kind() == "TypeAlias"; }); if (typeAlias == children.end()) return; @@ -4126,7 +3727,7 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node) isTemplateParamDecl); QTC_ASSERT(lastTemplateParam != children.rend(), return); const auto templateArg = std::find_if(children.begin(), children.end(), - [](const AstNode &n) { return n.role() == "template argument"; }); + [](const ClangdAstNode &n) { return n.role() == "template argument"; }); const int firstTemplateParamStartPos = posForNodeStart(*firstTemplateParam); const int lastTemplateParamEndPos = posForNodeEnd(*lastTemplateParam); @@ -4308,27 +3909,27 @@ void ExtraHighlightingResultsCollector::collectFromNode(const AstNode &node) insertResult(result); } -void ExtraHighlightingResultsCollector::visitNode(const AstNode &node) +void ExtraHighlightingResultsCollector::visitNode(const ClangdAstNode &node) { if (m_future.isCanceled()) return; - const AstNode::FileStatus prevFileStatus = m_currentFileStatus; + const ClangdAstNode::FileStatus prevFileStatus = m_currentFileStatus; m_currentFileStatus = node.fileStatus(m_filePath); - if (m_currentFileStatus == AstNode::FileStatus::Unknown - && prevFileStatus != AstNode::FileStatus::Ours) { + if (m_currentFileStatus == ClangdAstNode::FileStatus::Unknown + && prevFileStatus != ClangdAstNode::FileStatus::Ours) { m_currentFileStatus = prevFileStatus; } switch (m_currentFileStatus) { - case AstNode::FileStatus::Ours: - case AstNode::FileStatus::Unknown: + case ClangdAstNode::FileStatus::Ours: + case ClangdAstNode::FileStatus::Unknown: collectFromNode(node); [[fallthrough]]; - case AstNode::FileStatus::Foreign: - case ClangCodeModel::Internal::AstNode::FileStatus::Mixed: { + case ClangdAstNode::FileStatus::Foreign: + case ClangCodeModel::Internal::ClangdAstNode::FileStatus::Mixed: { const auto children = node.children(); if (!children) return; - for (const AstNode &childNode : *children) + for (const ClangdAstNode &childNode : *children) visitNode(childNode); break; }