Commit 50041d95 authored by David Nolden's avatar David Nolden
Browse files

Search declarations by ID in addition to their range

The declaration search based on "findContextAt" completely fails when
there are multiple different versions of a header which were parsed
differently depending on a macro. Also, findContextAt does a linear
search, and may be slow when there is a large list of contexts.
Whenever possible, try finding the declaration by its qualified
identifier based on the duchain symbol table, which should be more
efficient and scalable.

Extract the qualified identifier from clang by
following up the chain of semantic parents. After declarations
were found based on the symbol table, they are filtered
the same way as those found by findContextAt -- so there should
be no regressions. When the search fails, then use the previous
findContextAt method instead.

Added a test which tests several difficult cases that failed
without this change, and that succeed with it.

This also fixes the testDeclarationsInsideMacroExpansion test,
which was EXPECT_FAIL, but which succeeds now.
parent a36ae328
......@@ -299,7 +299,7 @@ CXChildVisitResult declVisitor(CXCursor cursor, CXCursor parent, CXClientData d)
DUChainReadLocker lock;
top = DUChain::self()->chainForDocument(ClangString(clang_getFileName(file)).toIndexed());
}
DeclarationPointer declaration = ClangHelpers::findDeclaration(clang_getCursorLocation(cursor), top);
DeclarationPointer declaration = ClangHelpers::findDeclaration(clang_getCursorLocation(cursor), QualifiedIdentifier(), top);
data->prototypes->append(FuncImplementInfo{kind == CXCursor_Constructor, kind == CXCursor_Destructor,
data->templatePrefix, returnType, rest, declaration});
......
......@@ -193,7 +193,7 @@ ReferencedTopDUContext ClangHelpers::buildDUChain(CXFile file, const Imports& im
return context;
}
DeclarationPointer ClangHelpers::findDeclaration(CXSourceLocation location, const ReferencedTopDUContext& top)
DeclarationPointer ClangHelpers::findDeclaration(CXSourceLocation location, QualifiedIdentifier id, const ReferencedTopDUContext& top)
{
if (!top) {
// may happen for cyclic includes
......@@ -202,6 +202,24 @@ DeclarationPointer ClangHelpers::findDeclaration(CXSourceLocation location, cons
auto cursor = CursorInRevision(ClangLocation(location));
DUChainReadLocker lock;
QList<Declaration*> decls;
if (!id.isEmpty())
{
decls = top->findDeclarations(id);
foreach (Declaration* decl, decls)
{
if (decl->range().contains(cursor) ||
(decl->range().isEmpty() && decl->range().start == cursor))
{
return DeclarationPointer(decl);
}
}
}
// there was no match based on the IDs, try the classical
// range based search (very slow)
Q_ASSERT(top);
if (DUContext *local = top->findContextAt(cursor)) {
if (local->owner() && local->owner()->range().contains(cursor)) {
......@@ -221,7 +239,22 @@ DeclarationPointer ClangHelpers::findDeclaration(CXCursor cursor, const IncludeF
return {};
}
return findDeclaration(location, includes.value(file));
// build a qualified identifier by following the chain of semantic parents
QList<Identifier> ids;
CXCursor currentCursor = cursor;
while (currentCursor.kind != CXCursor_TranslationUnit &&
currentCursor.kind != CXCursor_InvalidFile)
{
ids << Identifier(ClangString(clang_getCursorSpelling(currentCursor)).toString());
currentCursor = clang_getCursorSemanticParent(currentCursor);
}
QualifiedIdentifier qid;
for (int i = ids.size()-1; i >= 0; --i)
{
qid.push(ids[i]);
}
return findDeclaration(location, qid, includes.value(file));
}
DeclarationPointer ClangHelpers::findDeclaration(CXType type, const IncludeFileContexts& includes)
......
......@@ -45,7 +45,7 @@ using IncludeFileContexts = QHash<CXFile, KDevelop::ReferencedTopDUContext>;
namespace ClangHelpers {
KDevelop::DeclarationPointer findDeclaration(CXSourceLocation cursor, const KDevelop::ReferencedTopDUContext& top);
KDevelop::DeclarationPointer findDeclaration(CXSourceLocation cursor, KDevelop::QualifiedIdentifier id, const KDevelop::ReferencedTopDUContext& top);
KDevelop::DeclarationPointer findDeclaration(CXCursor cursor, const IncludeFileContexts& includes);
KDevelop::DeclarationPointer findDeclaration(CXType type, const IncludeFileContexts& includes);
......
......@@ -1196,6 +1196,72 @@ void TestDUChain::testReparseChangeEnvironment()
}
}
void TestDUChain::testMacroDependentHeader()
{
TestFile header("struct MY_CLASS { class Q{Q(); int m;}; int m; };\n", "h");
TestFile impl("#define MY_CLASS A\n"
"#include \"" + header.url().byteArray() + "\"\n"
"#undef MY_CLASS\n"
"#define MY_CLASS B\n"
"#include \"" + header.url().byteArray() + "\"\n"
"#undef MY_CLASS\n"
"A a;\n"
"const A::Q aq;\n"
"B b;\n"
"const B::Q bq;\n"
"int am = a.m;\n"
"int aqm = aq.m;\n"
"int bm = b.m;\n"
"int bqm = bq.m;\n"
, "cpp", &header);
impl.parse(TopDUContext::Features(TopDUContext::AllDeclarationsContextsAndUses|TopDUContext::AST|TopDUContext::ForceUpdate));
QVERIFY(impl.waitForParsed(500000));
DUChainReadLocker lock;
TopDUContext* top = impl.topContext().data();
QVERIFY(top);
QCOMPARE(top->localDeclarations().size(), 10); // 2x macro, then a, aq, b, bq
QCOMPARE(top->importedParentContexts().size(), 1);
AbstractType::Ptr type = top->localDeclarations()[2]->abstractType();
StructureType* sType = dynamic_cast<StructureType*>(type.data());
QVERIFY(sType);
QCOMPARE(sType->toString(), QString("A"));
Declaration* decl = sType->declaration(top);
QVERIFY(decl);
AbstractType::Ptr type2 = top->localDeclarations()[4]->abstractType();
StructureType* sType2 = dynamic_cast<StructureType*>(type2.data());
QVERIFY(sType2);
QCOMPARE(sType2->toString(), QString("B"));
Declaration* decl2 = sType2->declaration(top);
QVERIFY(decl2);
TopDUContext* top2 = dynamic_cast<TopDUContext*>(top->importedParentContexts()[0].context(top));
QVERIFY(top2);
QCOMPARE(top2->localDeclarations().size(), 2);
QCOMPARE(top2->localDeclarations()[0], decl);
QCOMPARE(top2->localDeclarations()[1], decl2);
qDebug() << "DECL RANGE:" << top2->localDeclarations()[0]->range().castToSimpleRange().toString();
qDebug() << "CTX RANGE:" << top2->localDeclarations()[0]->internalContext()->range().castToSimpleRange().toString();
// validate uses:
QCOMPARE(top->usesCount(), 14);
QCOMPARE(top->uses()[0].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A"));
QCOMPARE(top->uses()[1].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A"));
QCOMPARE(top->uses()[2].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::Q"));
QCOMPARE(top->uses()[3].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B"));
QCOMPARE(top->uses()[4].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B"));
QCOMPARE(top->uses()[5].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::Q"));
QCOMPARE(top->uses()[6].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("a"));
QCOMPARE(top->uses()[7].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::m"));
QCOMPARE(top->uses()[8].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("aq"));
QCOMPARE(top->uses()[9].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("A::Q::m"));
QCOMPARE(top->uses()[10].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("b"));
QCOMPARE(top->uses()[11].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::m"));
QCOMPARE(top->uses()[12].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("bq"));
QCOMPARE(top->uses()[13].usedDeclaration(top)->qualifiedIdentifier(), QualifiedIdentifier("B::Q::m"));
}
void TestDUChain::testHeaderParsingOrder1()
{
TestFile header("typedef const A<int> B;\n", "h");
......@@ -1666,7 +1732,6 @@ void TestDUChain::testDeclarationsInsideMacroExpansion()
auto context = file.topContext()->childContexts().first()->childContexts().first();
QVERIFY(context);
QCOMPARE(context->localDeclarations().size(), 1);
QEXPECT_FAIL("", "Clang assigns the same range for all declarations inside a macro expansion. Therefore we can't find their uses", Abort);
QCOMPARE(context->usesCount(), 3);
QCOMPARE(context->uses()[0].m_range, RangeInRevision({2, 0}, {2, 1}));
......
......@@ -74,6 +74,7 @@ private slots:
void testMacrosRanges();
void testHeaderParsingOrder1();
void testHeaderParsingOrder2();
void testMacroDependentHeader();
void testNestedImports();
void testEnvironmentWithDifferentOrderOfElements();
void testReparseMacro();
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment