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

View File

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

View File

@@ -83,6 +83,7 @@ public:
docRevision(editorWidget ? editorWidget->textDocument()->document()->revision() : -1), docRevision(editorWidget ? editorWidget->textDocument()->document()->revision() : -1),
openInSplit(openInSplit) {} openInSplit(openInSplit) {}
void goToTypeDefinition();
void handleGotoDefinitionResult(); void handleGotoDefinitionResult();
void sendGotoImplementationRequest(const Utils::Link &link); void sendGotoImplementationRequest(const Utils::Link &link);
void handleGotoImplementationResult(const GotoImplementationRequest::Response &response); void handleGotoImplementationResult(const GotoImplementationRequest::Response &response);
@@ -118,7 +119,7 @@ public:
ClangdFollowSymbol::ClangdFollowSymbol(ClangdClient *client, const QTextCursor &cursor, ClangdFollowSymbol::ClangdFollowSymbol(ClangdClient *client, const QTextCursor &cursor,
CppEditorWidget *editorWidget, TextDocument *document, const LinkHandler &callback, CppEditorWidget *editorWidget, TextDocument *document, const LinkHandler &callback,
bool openInSplit) FollowTo followTo, bool openInSplit)
: QObject(client), : QObject(client),
d(new Private(this, client, cursor, editorWidget, document->filePath(), callback, d(new Private(this, client, cursor, editorWidget, document->filePath(), callback,
openInSplit)) openInSplit))
@@ -133,6 +134,11 @@ ClangdFollowSymbol::ClangdFollowSymbol(ClangdClient *client, const QTextCursor &
d->focusChangedConnection = connect(qApp, &QApplication::focusChanged, d->focusChangedConnection = connect(qApp, &QApplication::focusChanged,
this, [this] { emitDone(); }, Qt::QueuedConnection); 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 // 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 // AST node corresponding to the cursor position, so we can find out whether
// we have to look for overrides. // we have to look for overrides.
@@ -353,6 +359,30 @@ ClangdFollowSymbol::VirtualFunctionAssistProvider::createProcessor(const AssistI
= new VirtualFunctionAssistProcessor(m_followSymbol); = 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() void ClangdFollowSymbol::Private::handleGotoDefinitionResult()
{ {
QTC_ASSERT(defLink.hasValidTarget(), return); QTC_ASSERT(defLink.hasValidTarget(), return);

View File

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

View File

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

View File

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

View File

@@ -313,38 +313,50 @@ void ClangdTestFollowSymbol::test_data()
QTest::addColumn<QString>("targetFile"); QTest::addColumn<QString>("targetFile");
QTest::addColumn<int>("targetLine"); QTest::addColumn<int>("targetLine");
QTest::addColumn<int>("targetColumn"); QTest::addColumn<int>("targetColumn");
QTest::addColumn<bool>("goToType");
QTest::newRow("on namespace") << "main.cpp" << 27 << 1 << "header.h" << 28 << 11; 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; 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; 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; << false;
QTest::newRow("class definition (same file)") << "header.h" << 34 << 7 << "header.h" << 32 << 7; 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 QTest::newRow("class definition (different file)") << "main.cpp" << 54 << 7
<< "header.h" << 48 << 7; << "header.h" << 48 << 7 << false;
QTest::newRow("constructor decl") << "header.h" << 36 << 5 << "main.cpp" << 27 << 14; 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; QTest::newRow("constructor definition") << "main.cpp" << 27 << 14 << "header.h" << 36 << 5
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; QTest::newRow("member ref") << "main.cpp" << 39 << 10 << "header.h" << 38 << 18 << false;
QTest::newRow("member decl") << "header.h" << 38 << 18 << "header.h" << 38 << 18; QTest::newRow("union member ref") << "main.cpp" << 91 << 20 << "main.cpp" << 86 << 13 << false;
QTest::newRow("function ref") << "main.cpp" << 66 << 12 << "main.cpp" << 35 << 5; QTest::newRow("member decl") << "header.h" << 38 << 18 << "header.h" << 38 << 18 << false;
QTest::newRow("member function ref") << "main.cpp" << 42 << 12 << "main.cpp" << 49 << 21; QTest::newRow("function ref") << "main.cpp" << 66 << 12 << "main.cpp" << 35 << 5 << false;
QTest::newRow("function with no def ref") << "main.cpp" << 43 << 5 << "header.h" << 59 << 5; QTest::newRow("member function ref") << "main.cpp" << 42 << 12 << "main.cpp" << 49 << 21
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; QTest::newRow("function with no def ref") << "main.cpp" << 43 << 5 << "header.h" << 59 << 5
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; QTest::newRow("function def") << "main.cpp" << 35 << 5 << "header.h" << 52 << 5 << false;
QTest::newRow("local var") << "main.cpp" << 39 << 6 << "main.cpp" << 36 << 9; QTest::newRow("member function def") << "main.cpp" << 49 << 21 << "header.h" << 43 << 9
QTest::newRow("alias") << "main.cpp" << 36 << 5 << "main.cpp" << 33 << 7; << false;
QTest::newRow("static var") << "main.cpp" << 40 << 27 << "header.h" << 30 << 7; 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 QTest::newRow("member function ref (other class)") << "main.cpp" << 62 << 39
<< "cursor.cpp" << 104 << 22; << "cursor.cpp" << 104 << 22 << false;
QTest::newRow("macro ref") << "main.cpp" << 66 << 43 << "header.h" << 27 << 9; 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; 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; 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; 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; 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; 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; 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() void ClangdTestFollowSymbol::test()
@@ -355,6 +367,7 @@ void ClangdTestFollowSymbol::test()
QFETCH(QString, targetFile); QFETCH(QString, targetFile);
QFETCH(int, targetLine); QFETCH(int, targetLine);
QFETCH(int, targetColumn); QFETCH(int, targetColumn);
QFETCH(bool, goToType);
TextEditor::TextDocument * const doc = document(sourceFile); TextEditor::TextDocument * const doc = document(sourceFile);
QVERIFY(doc); QVERIFY(doc);
@@ -371,7 +384,8 @@ void ClangdTestFollowSymbol::test()
QTextCursor cursor(doc->document()); QTextCursor cursor(doc->document());
const int pos = Utils::Text::positionInText(doc->document(), sourceLine, sourceColumn); const int pos = Utils::Text::positionInText(doc->document(), sourceLine, sourceColumn);
cursor.setPosition(pos); 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); timer.start(10000);
loop.exec(); loop.exec();
QVERIFY(timer.isActive()); QVERIFY(timer.isActive());

View File

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