forked from qt-creator/qt-creator
We've been requiring C++17 since Qt 6.0, and our qAsConst use finally starts to bother us (QTBUG-99313), so time to port away from it now. Since qAsConst has exactly the same semantics as std::as_const (down to rvalue treatment, constexpr'ness and noexcept'ness), there's really nothing more to it than a global search-and-replace. Task-number: QTBUG-99313 Change-Id: I88edd91395849574436299b8badda21bb93bea39 Reviewed-by: hjk <hjk@qt.io>
401 lines
14 KiB
C++
401 lines
14 KiB
C++
// Copyright (C) 2022 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
|
|
|
#include "clangdast.h"
|
|
|
|
#include <languageclient/client.h>
|
|
#include <languageserverprotocol/jsonkeys.h>
|
|
#include <languageserverprotocol/lsptypes.h>
|
|
#include <utils/hostosinfo.h>
|
|
#include <utils/filepath.h>
|
|
|
|
#include <QStringView>
|
|
|
|
using namespace Core;
|
|
using namespace LanguageClient;
|
|
using namespace LanguageServerProtocol;
|
|
using namespace Utils;
|
|
|
|
namespace ClangCodeModel::Internal {
|
|
|
|
static constexpr char16_t roleKey[] = u"role";
|
|
static constexpr char16_t arcanaKey[] = u"arcana";
|
|
|
|
QString ClangdAstNode::role() const { return typedValue<QString>(roleKey); }
|
|
QString ClangdAstNode::kind() const { return typedValue<QString>(kindKey); }
|
|
std::optional<QString> ClangdAstNode::detail() const
|
|
{
|
|
return optionalValue<QString>(detailKey);
|
|
}
|
|
std::optional<QString> ClangdAstNode::arcana() const
|
|
{
|
|
return optionalValue<QString>(arcanaKey);
|
|
}
|
|
Range ClangdAstNode::range() const { return typedValue<Range>(rangeKey); }
|
|
bool ClangdAstNode::hasRange() const { return contains(rangeKey); }
|
|
bool ClangdAstNode::isValid() const { return contains(roleKey) && contains(kindKey); }
|
|
|
|
std::optional<QList<ClangdAstNode>> ClangdAstNode::children() const
|
|
{
|
|
return optionalArray<ClangdAstNode>(childrenKey);
|
|
}
|
|
|
|
bool ClangdAstNode::arcanaContains(const QString &s) const
|
|
{
|
|
const std::optional<QString> arcanaString = arcana();
|
|
return arcanaString && arcanaString->contains(s);
|
|
}
|
|
|
|
bool ClangdAstNode::isFunction() const
|
|
{
|
|
return role() == "declaration"
|
|
&& (kind() == "Function" || kind() == "FunctionProto" || kind() == "CXXMethod");
|
|
}
|
|
|
|
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<ClangdAstNode> childList = children().value_or(QList<ClangdAstNode>());
|
|
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 std::optional<QString> 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<ClangdAstNode> 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<ClangdAstNode>());
|
|
}
|
|
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 std::optional<QList<ClangdAstNode>> 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<ClangdAstNode>()),
|
|
[&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 std::optional<QString> 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 std::optional<QString> arcanaString = arcana();
|
|
if (!arcanaString)
|
|
return FileStatus::Unknown;
|
|
|
|
// Example arcanas:
|
|
// "FunctionDecl 0x7fffb5d0dbd0 </tmp/test.cpp:1:1, line:5:1> line:1:6 func 'void ()'"
|
|
// "VarDecl 0x7fffb5d0dcf0 </tmp/test.cpp:2:5, /tmp/test.h:1:1> /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<ClangdAstNode>()))
|
|
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<ClangdAstNode> 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 : std::as_const(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<TextDocumentIdentifier>(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.
|
|
std::optional<Range> range() const { return optionalValue<Range>(rangeKey); }
|
|
void setRange(const Range &range) { insert(rangeKey, range); }
|
|
|
|
bool isValid() const override { return contains(textDocumentKey); }
|
|
};
|
|
|
|
class AstRequest : public Request<ClangdAstNode, std::nullptr_t, AstParams>
|
|
{
|
|
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->sendMessage(request, Client::SendDocUpdates::Ignore);
|
|
return request.id();
|
|
}
|
|
|
|
} // namespace ClangCodeModel::Internal
|