ClangCodeModel: Implement following a symbol to its type

Making use of LSP's "Go To Type Definition".
Just the backend for now, UI to follow.

Change-Id: Id73b2cf701eab03913477f6d4d3093e257e80dbd
Reviewed-by: David Schulz <david.schulz@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
This commit is contained in:
Christian Kandeler
2022-09-26 11:53:22 +02:00
parent e3b67799d4
commit 813c6fbd81
8 changed files with 90 additions and 35 deletions

View File

@@ -822,6 +822,7 @@ void ClangdClient::followSymbol(TextDocument *document,
CppEditor::CppEditorWidget *editorWidget,
const Utils::LinkHandler &callback,
bool resolveTarget,
FollowTo followTo,
bool openInSplit
)
{
@@ -839,7 +840,7 @@ void ClangdClient::followSymbol(TextDocument *document,
qCDebug(clangdLog) << "follow symbol requested" << document->filePath()
<< adjustedCursor.blockNumber() << adjustedCursor.positionInBlock();
d->followSymbol = new ClangdFollowSymbol(this, adjustedCursor, editorWidget, document, callback,
openInSplit);
followTo, openInSplit);
connect(d->followSymbol, &ClangdFollowSymbol::done, this, [this] {
d->followSymbol->deleteLater();
d->followSymbol = nullptr;

View File

@@ -34,6 +34,8 @@ Q_DECLARE_LOGGING_CATEGORY(clangdLogAst);
void setupClangdConfigFile();
enum class FollowTo { SymbolDef, SymbolType };
class ClangdClient : public LanguageClient::Client
{
Q_OBJECT
@@ -55,6 +57,7 @@ public:
CppEditor::CppEditorWidget *editorWidget,
const Utils::LinkHandler &callback,
bool resolveTarget,
FollowTo followTo,
bool openInSplit);
void switchDeclDef(TextEditor::TextDocument *document,

View File

@@ -83,6 +83,7 @@ public:
docRevision(editorWidget ? editorWidget->textDocument()->document()->revision() : -1),
openInSplit(openInSplit) {}
void goToTypeDefinition();
void handleGotoDefinitionResult();
void sendGotoImplementationRequest(const Utils::Link &link);
void handleGotoImplementationResult(const GotoImplementationRequest::Response &response);
@@ -118,7 +119,7 @@ public:
ClangdFollowSymbol::ClangdFollowSymbol(ClangdClient *client, const QTextCursor &cursor,
CppEditorWidget *editorWidget, TextDocument *document, const LinkHandler &callback,
bool openInSplit)
FollowTo followTo, bool openInSplit)
: QObject(client),
d(new Private(this, client, cursor, editorWidget, document->filePath(), callback,
openInSplit))
@@ -133,6 +134,11 @@ ClangdFollowSymbol::ClangdFollowSymbol(ClangdClient *client, const QTextCursor &
d->focusChangedConnection = connect(qApp, &QApplication::focusChanged,
this, [this] { emitDone(); }, Qt::QueuedConnection);
if (followTo == FollowTo::SymbolType) {
d->goToTypeDefinition();
return;
}
// Step 1: Follow the symbol via "Go to Definition". At the same time, request the
// AST node corresponding to the cursor position, so we can find out whether
// we have to look for overrides.
@@ -353,6 +359,30 @@ ClangdFollowSymbol::VirtualFunctionAssistProvider::createProcessor(const AssistI
= new VirtualFunctionAssistProcessor(m_followSymbol);
}
void ClangdFollowSymbol::Private::goToTypeDefinition()
{
GotoTypeDefinitionRequest req(TextDocumentPositionParams(TextDocumentIdentifier{uri},
Position(cursor)));
req.setResponseCallback([sentinel = QPointer(q), this, reqId = req.id()]
(const GotoTypeDefinitionRequest::Response &response) {
qCDebug(clangdLog) << "received go to type definition reply";
if (!sentinel)
return;
Link link;
if (const std::optional<GotoResult> &result = response.result()) {
if (const auto ploc = std::get_if<Location>(&*result)) {
link = {ploc->toLink()};
} else if (const auto plloc = std::get_if<QList<Location>>(&*result)) {
if (!plloc->empty())
link = plloc->first().toLink();
}
}
q->emitDone(link);
});
client->sendMessage(req, ClangdClient::SendDocUpdates::Ignore);
qCDebug(clangdLog) << "sending go to type definition request";
}
void ClangdFollowSymbol::Private::handleGotoDefinitionResult()
{
QTC_ASSERT(defLink.hasValidTarget(), return);

View File

@@ -17,6 +17,7 @@ QT_END_NAMESPACE
namespace ClangCodeModel::Internal {
class ClangdAstNode;
class ClangdClient;
enum class FollowTo;
class ClangdFollowSymbol : public QObject
{
@@ -25,7 +26,7 @@ public:
ClangdFollowSymbol(ClangdClient *client, const QTextCursor &cursor,
CppEditor::CppEditorWidget *editorWidget,
TextEditor::TextDocument *document, const Utils::LinkHandler &callback,
bool openInSplit);
FollowTo followTo, bool openInSplit);
~ClangdFollowSymbol();
void clear();

View File

@@ -169,7 +169,7 @@ void ClangdSwitchDeclDef::Private::handleDeclDefSwitchReplies()
const QTextCursor funcNameCursor = cursorForFunctionName(*functionNode);
if (!funcNameCursor.isNull()) {
client->followSymbol(document.data(), funcNameCursor, editorWidget, callback,
true, false);
true, FollowTo::SymbolDef, false);
}
q->emitDone();
}

View File

@@ -262,7 +262,7 @@ void ClangModelManagerSupport::followSymbol(const CppEditor::CursorInEditor &dat
if (ClangdClient * const client = clientForFile(data.filePath());
client && client->isFullyIndexed()) {
client->followSymbol(data.textDocument(), data.cursor(), data.editorWidget(),
processLinkCallback, resolveTarget, inNextSplit);
processLinkCallback, resolveTarget, FollowTo::SymbolDef, inNextSplit);
return;
}

View File

@@ -313,38 +313,50 @@ void ClangdTestFollowSymbol::test_data()
QTest::addColumn<QString>("targetFile");
QTest::addColumn<int>("targetLine");
QTest::addColumn<int>("targetColumn");
QTest::addColumn<bool>("goToType");
QTest::newRow("on namespace") << "main.cpp" << 27 << 1 << "header.h" << 28 << 11;
QTest::newRow("class ref") << "main.cpp" << 27 << 9 << "header.h" << 34 << 7;
QTest::newRow("forward decl (same file)") << "header.h" << 32 << 7 << "header.h" << 34 << 7;
QTest::newRow("forward decl (different file") << "header.h" << 48 << 9 << "main.cpp" << 54 << 7;
QTest::newRow("class definition (same file)") << "header.h" << 34 << 7 << "header.h" << 32 << 7;
QTest::newRow("on namespace") << "main.cpp" << 27 << 1 << "header.h" << 28 << 11 << false;
QTest::newRow("class ref") << "main.cpp" << 27 << 9 << "header.h" << 34 << 7 << false;
QTest::newRow("forward decl (same file)") << "header.h" << 32 << 7 << "header.h" << 34 << 7
<< false;
QTest::newRow("forward decl (different file") << "header.h" << 48 << 9 << "main.cpp" << 54 << 7
<< false;
QTest::newRow("class definition (same file)") << "header.h" << 34 << 7 << "header.h" << 32 << 7
<< false;
QTest::newRow("class definition (different file)") << "main.cpp" << 54 << 7
<< "header.h" << 48 << 7;
QTest::newRow("constructor decl") << "header.h" << 36 << 5 << "main.cpp" << 27 << 14;
QTest::newRow("constructor definition") << "main.cpp" << 27 << 14 << "header.h" << 36 << 5;
QTest::newRow("member ref") << "main.cpp" << 39 << 10 << "header.h" << 38 << 18;
QTest::newRow("union member ref") << "main.cpp" << 91 << 20 << "main.cpp" << 86 << 13;
QTest::newRow("member decl") << "header.h" << 38 << 18 << "header.h" << 38 << 18;
QTest::newRow("function ref") << "main.cpp" << 66 << 12 << "main.cpp" << 35 << 5;
QTest::newRow("member function ref") << "main.cpp" << 42 << 12 << "main.cpp" << 49 << 21;
QTest::newRow("function with no def ref") << "main.cpp" << 43 << 5 << "header.h" << 59 << 5;
QTest::newRow("function def") << "main.cpp" << 35 << 5 << "header.h" << 52 << 5;
QTest::newRow("member function def") << "main.cpp" << 49 << 21 << "header.h" << 43 << 9;
QTest::newRow("member function decl") << "header.h" << 43 << 9 << "main.cpp" << 49 << 21;
QTest::newRow("include") << "main.cpp" << 25 << 13 << "header.h" << 1 << 1;
QTest::newRow("local var") << "main.cpp" << 39 << 6 << "main.cpp" << 36 << 9;
QTest::newRow("alias") << "main.cpp" << 36 << 5 << "main.cpp" << 33 << 7;
QTest::newRow("static var") << "main.cpp" << 40 << 27 << "header.h" << 30 << 7;
<< "header.h" << 48 << 7 << false;
QTest::newRow("constructor decl") << "header.h" << 36 << 5 << "main.cpp" << 27 << 14 << false;
QTest::newRow("constructor definition") << "main.cpp" << 27 << 14 << "header.h" << 36 << 5
<< false;
QTest::newRow("member ref") << "main.cpp" << 39 << 10 << "header.h" << 38 << 18 << false;
QTest::newRow("union member ref") << "main.cpp" << 91 << 20 << "main.cpp" << 86 << 13 << false;
QTest::newRow("member decl") << "header.h" << 38 << 18 << "header.h" << 38 << 18 << false;
QTest::newRow("function ref") << "main.cpp" << 66 << 12 << "main.cpp" << 35 << 5 << false;
QTest::newRow("member function ref") << "main.cpp" << 42 << 12 << "main.cpp" << 49 << 21
<< false;
QTest::newRow("function with no def ref") << "main.cpp" << 43 << 5 << "header.h" << 59 << 5
<< false;
QTest::newRow("function def") << "main.cpp" << 35 << 5 << "header.h" << 52 << 5 << false;
QTest::newRow("member function def") << "main.cpp" << 49 << 21 << "header.h" << 43 << 9
<< false;
QTest::newRow("member function decl") << "header.h" << 43 << 9 << "main.cpp" << 49 << 21
<< false;
QTest::newRow("include") << "main.cpp" << 25 << 13 << "header.h" << 1 << 1 << false;
QTest::newRow("local var") << "main.cpp" << 39 << 6 << "main.cpp" << 36 << 9 << false;
QTest::newRow("alias") << "main.cpp" << 36 << 5 << "main.cpp" << 33 << 7 << false;
QTest::newRow("static var") << "main.cpp" << 40 << 27 << "header.h" << 30 << 7 << false;
QTest::newRow("member function ref (other class)") << "main.cpp" << 62 << 39
<< "cursor.cpp" << 104 << 22;
QTest::newRow("macro ref") << "main.cpp" << 66 << 43 << "header.h" << 27 << 9;
QTest::newRow("on namespace 2") << "main.cpp" << 27 << 3 << "header.h" << 28 << 11;
QTest::newRow("after namespace") << "main.cpp" << 27 << 7 << "header.h" << 28 << 11;
QTest::newRow("operator def") << "main.cpp" << 76 << 13 << "main.cpp" << 72 << 9;
QTest::newRow("operator def 2") << "main.cpp" << 80 << 15 << "main.cpp" << 73 << 10;
QTest::newRow("operator decl") << "main.cpp" << 72 << 12 << "main.cpp" << 76 << 10;
QTest::newRow("operator decl 2") << "main.cpp" << 73 << 12 << "main.cpp" << 80 << 11;
<< "cursor.cpp" << 104 << 22 << false;
QTest::newRow("macro ref") << "main.cpp" << 66 << 43 << "header.h" << 27 << 9 << false;
QTest::newRow("on namespace 2") << "main.cpp" << 27 << 3 << "header.h" << 28 << 11 << false;
QTest::newRow("after namespace") << "main.cpp" << 27 << 7 << "header.h" << 28 << 11 << false;
QTest::newRow("operator def") << "main.cpp" << 76 << 13 << "main.cpp" << 72 << 9 << false;
QTest::newRow("operator def 2") << "main.cpp" << 80 << 15 << "main.cpp" << 73 << 10 << false;
QTest::newRow("operator decl") << "main.cpp" << 72 << 12 << "main.cpp" << 76 << 10 << false;
QTest::newRow("operator decl 2") << "main.cpp" << 73 << 12 << "main.cpp" << 80 << 11 << false;
QTest::newRow("go to typedef") << "main.cpp" << 100 << 19 << "main.cpp" << 33 << 7 << true;
QTest::newRow("go to type") << "main.cpp" << 101 << 19 << "main.cpp" << 69 << 7 << true;
}
void ClangdTestFollowSymbol::test()
@@ -355,6 +367,7 @@ void ClangdTestFollowSymbol::test()
QFETCH(QString, targetFile);
QFETCH(int, targetLine);
QFETCH(int, targetColumn);
QFETCH(bool, goToType);
TextEditor::TextDocument * const doc = document(sourceFile);
QVERIFY(doc);
@@ -371,7 +384,8 @@ void ClangdTestFollowSymbol::test()
QTextCursor cursor(doc->document());
const int pos = Utils::Text::positionInText(doc->document(), sourceLine, sourceColumn);
cursor.setPosition(pos);
client()->followSymbol(doc, cursor, nullptr, handler, true, false);
client()->followSymbol(doc, cursor, nullptr, handler, true,
goToType ? FollowTo::SymbolType : FollowTo::SymbolDef, false);
timer.start(10000);
loop.exec();
QVERIFY(timer.isActive());

View File

@@ -92,4 +92,10 @@ struct S {
int i = 42;
return i;
}
YYY getYYY() const { return YYY(); }
Bar getBar() const { return Bar(); }
};
static const auto yyyVar = S().getYYY();
static const auto barVar = S().getBar();