CppEditor: Handle classes that do not override

...when searching the overrides for virtual functions.

In case there is no override for the static type of a function call
expression, make sure to:

1) include the last provided override (look up bases)
2) and all overrides whose classes are derived from that static type

Task-number: QTCREATORBUG-10470

Change-Id: I2c01bfdc6cb35c5a01a000ebd81a2b322ce2b795
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
Reviewed-by: Erik Verbruggen <erik.verbruggen@digia.com>
This commit is contained in:
Nikolai Kosjar
2013-10-28 15:12:18 +01:00
parent 0c16757085
commit 4a06dab30a
5 changed files with 276 additions and 39 deletions

View File

@@ -141,6 +141,11 @@ private slots:
void test_FollowSymbolUnderCursor_virtualFunctionCall_fallbackToDeclaration(); void test_FollowSymbolUnderCursor_virtualFunctionCall_fallbackToDeclaration();
void test_FollowSymbolUnderCursor_virtualFunctionCall_itemOrder(); void test_FollowSymbolUnderCursor_virtualFunctionCall_itemOrder();
void test_FollowSymbolUnderCursor_virtualFunctionCall_instantiatedSymbols(); void test_FollowSymbolUnderCursor_virtualFunctionCall_instantiatedSymbols();
void test_FollowSymbolUnderCursor_virtualFunctionCall_QSharedPointer();
void test_FollowSymbolUnderCursor_virtualFunctionCall_multipeDocuments();
void test_FollowSymbolUnderCursor_virtualFunctionCall_noSiblings_references();
void test_FollowSymbolUnderCursor_virtualFunctionCall_noSiblings_pointers();
void test_FollowSymbolUnderCursor_virtualFunctionCall_noSiblings_noBaseExpression();
void test_FollowSymbolUnderCursor_virtualFunctionCall_onDotMemberAccessOfReferenceTypes(); void test_FollowSymbolUnderCursor_virtualFunctionCall_onDotMemberAccessOfReferenceTypes();
void test_FollowSymbolUnderCursor_virtualFunctionCall_notOnDotMemberAccessOfNonReferenceType(); void test_FollowSymbolUnderCursor_virtualFunctionCall_notOnDotMemberAccessOfNonReferenceType();
void test_FollowSymbolUnderCursor_virtualFunctionCall_notOnQualified(); void test_FollowSymbolUnderCursor_virtualFunctionCall_notOnQualified();

View File

@@ -35,6 +35,7 @@
#include <cplusplus/ASTPath.h> #include <cplusplus/ASTPath.h>
#include <cplusplus/BackwardsScanner.h> #include <cplusplus/BackwardsScanner.h>
#include <cplusplus/ExpressionUnderCursor.h> #include <cplusplus/ExpressionUnderCursor.h>
#include <cplusplus/ResolveExpression.h>
#include <cplusplus/SimpleLexer.h> #include <cplusplus/SimpleLexer.h>
#include <cplusplus/TypeOfExpression.h> #include <cplusplus/TypeOfExpression.h>
#include <cpptools/cppmodelmanagerinterface.h> #include <cpptools/cppmodelmanagerinterface.h>
@@ -60,75 +61,85 @@ public:
VirtualFunctionHelper(const TypeOfExpression &typeOfExpression, VirtualFunctionHelper(const TypeOfExpression &typeOfExpression,
Scope *scope, Scope *scope,
const Document::Ptr &document, const Document::Ptr &document,
const Snapshot &snapshot); const Snapshot &snapshot,
SymbolFinder *symbolFinder);
bool canLookupVirtualFunctionOverrides(const Function *function) const; bool canLookupVirtualFunctionOverrides(Function *function);
/// Returns != 0 if canLookupVirtualFunctionOverrides() succeeded.
Class *staticClassOfFunctionCallExpression() const
{ return m_staticClassOfFunctionCallExpression; }
private: private:
VirtualFunctionHelper(); VirtualFunctionHelper();
Q_DISABLE_COPY(VirtualFunctionHelper) Q_DISABLE_COPY(VirtualFunctionHelper)
ExpressionAST *getBaseExpressionAST() const Class *staticClassOfFunctionCallExpression_internal() const;
{
if (!m_expressionAST)
return 0;
CallAST *callAST = m_expressionAST->asCall();
if (!callAST)
return 0;
if (ExpressionAST *baseExpressionAST = callAST->base_expression)
return baseExpressionAST;
return 0;
}
private: private:
// Provided
const QSharedPointer<TypeOfExpression> m_typeOfExpression; const QSharedPointer<TypeOfExpression> m_typeOfExpression;
ExpressionAST *m_expressionAST;
const Document::Ptr m_expressionDocument; const Document::Ptr m_expressionDocument;
Scope *m_scope; Scope *m_scope;
const Document::Ptr &m_document; const Document::Ptr &m_document;
const Snapshot &m_snapshot; const Snapshot &m_snapshot;
SymbolFinder *m_finder;
// Determined
ExpressionAST *m_baseExpressionAST;
Function *m_function;
int m_accessTokenKind;
Class *m_staticClassOfFunctionCallExpression; // Output
}; };
VirtualFunctionHelper::VirtualFunctionHelper(const TypeOfExpression &typeOfExpression, VirtualFunctionHelper::VirtualFunctionHelper(const TypeOfExpression &typeOfExpression,
Scope *scope, Scope *scope,
const Document::Ptr &document, const Document::Ptr &document,
const Snapshot &snapshot) const Snapshot &snapshot,
: m_expressionAST(typeOfExpression.expressionAST()) SymbolFinder *finder)
, m_expressionDocument(typeOfExpression.context().expressionDocument()) : m_expressionDocument(typeOfExpression.context().expressionDocument())
, m_scope(scope) , m_scope(scope)
, m_document(document) , m_document(document)
, m_snapshot(snapshot) , m_snapshot(snapshot)
, m_finder(finder)
, m_baseExpressionAST(0)
, m_function(0)
, m_accessTokenKind(0)
, m_staticClassOfFunctionCallExpression(0)
{ {
if (ExpressionAST *expressionAST = typeOfExpression.expressionAST()) {
if (CallAST *callAST = expressionAST->asCall()) {
if (ExpressionAST *baseExpressionAST = callAST->base_expression)
m_baseExpressionAST = baseExpressionAST;
}
}
} }
bool VirtualFunctionHelper::canLookupVirtualFunctionOverrides(const Function *function) const bool VirtualFunctionHelper::canLookupVirtualFunctionOverrides(Function *function)
{ {
if (!m_expressionDocument || !m_document || !function || !m_scope || m_scope->isClass() m_function = function;
|| m_snapshot.isEmpty()) { if (!m_function || !m_baseExpressionAST || !m_expressionDocument || !m_document || !m_scope
|| m_scope->isClass() || m_snapshot.isEmpty()) {
return false; return false;
} }
ExpressionAST *baseExpressionAST = getBaseExpressionAST();
if (!baseExpressionAST)
return false;
bool result = false; bool result = false;
if (IdExpressionAST *idExpressionAST = baseExpressionAST->asIdExpression()) { if (IdExpressionAST *idExpressionAST = m_baseExpressionAST->asIdExpression()) {
NameAST *name = idExpressionAST->name; NameAST *name = idExpressionAST->name;
const bool nameIsQualified = name && name->asQualifiedName(); const bool nameIsQualified = name && name->asQualifiedName();
result = !nameIsQualified && FunctionHelper::isVirtualFunction(function, m_snapshot); result = !nameIsQualified && FunctionHelper::isVirtualFunction(function, m_snapshot);
} else if (MemberAccessAST *memberAccessAST = baseExpressionAST->asMemberAccess()) { } else if (MemberAccessAST *memberAccessAST = m_baseExpressionAST->asMemberAccess()) {
NameAST *name = memberAccessAST->member_name; NameAST *name = memberAccessAST->member_name;
const bool nameIsQualified = name && name->asQualifiedName(); const bool nameIsQualified = name && name->asQualifiedName();
if (!nameIsQualified && FunctionHelper::isVirtualFunction(function, m_snapshot)) { if (!nameIsQualified && FunctionHelper::isVirtualFunction(function, m_snapshot)) {
TranslationUnit *unit = m_expressionDocument->translationUnit(); TranslationUnit *unit = m_expressionDocument->translationUnit();
QTC_ASSERT(unit, return false); QTC_ASSERT(unit, return false);
const int accessTokenKind = unit->tokenKind(memberAccessAST->access_token); m_accessTokenKind = unit->tokenKind(memberAccessAST->access_token);
if (accessTokenKind == T_ARROW) { if (m_accessTokenKind == T_ARROW) {
result = true; result = true;
} else if (accessTokenKind == T_DOT) { } else if (m_accessTokenKind == T_DOT) {
TypeOfExpression typeOfExpression; TypeOfExpression typeOfExpression;
typeOfExpression.init(m_document, m_snapshot); typeOfExpression.init(m_document, m_snapshot);
typeOfExpression.setExpandTemplates(true); typeOfExpression.setExpandTemplates(true);
@@ -143,6 +154,50 @@ bool VirtualFunctionHelper::canLookupVirtualFunctionOverrides(const Function *fu
} }
} }
if (!result)
return false;
return (m_staticClassOfFunctionCallExpression = staticClassOfFunctionCallExpression_internal());
}
/// For "f()" in "class C { void g() { f(); };" return class C.
/// For "c->f()" in "{ C *c; c->f(); }" return class C.
Class *VirtualFunctionHelper::staticClassOfFunctionCallExpression_internal() const
{
if (!m_finder)
return 0;
Class *result = 0;
if (m_baseExpressionAST->asIdExpression()) {
for (Scope *s = m_scope; s ; s = s->enclosingScope()) {
if (Function *function = s->asFunction()) {
result = m_finder->findMatchingClassDeclaration(function, m_snapshot);
break;
}
}
} else if (MemberAccessAST *memberAccessAST = m_baseExpressionAST->asMemberAccess()) {
QTC_ASSERT(m_accessTokenKind == T_ARROW || m_accessTokenKind == T_DOT, return result);
TypeOfExpression typeOfExpression;
typeOfExpression.init(m_document, m_snapshot);
typeOfExpression.setExpandTemplates(true);
const QList<LookupItem> items = typeOfExpression(memberAccessAST->base_expression,
m_document, m_scope);
ResolveExpression resolveExpression(typeOfExpression.context());
ClassOrNamespace *binding = resolveExpression.baseExpression(items, m_accessTokenKind);
if (binding) {
if (Class *klass = binding->rootClass()) {
result = klass;
} else {
const QList<Symbol *> symbols = binding->symbols();
if (!symbols.isEmpty()) {
Symbol * const first = symbols.first();
if (first->isForwardClassDeclaration())
result = m_finder->findMatchingClassDeclaration(first, m_snapshot);
}
}
}
}
return result; return result;
} }
@@ -590,11 +645,12 @@ BaseTextEditorWidget::Link FollowSymbolUnderCursor::findLink(const QTextCursor &
// Consider to show a pop-up displaying overrides for the function // Consider to show a pop-up displaying overrides for the function
Function *function = symbol->type()->asFunctionType(); Function *function = symbol->type()->asFunctionType();
VirtualFunctionHelper helper(*typeOfExpression, scope, doc, snapshot); VirtualFunctionHelper helper(*typeOfExpression, scope, doc, snapshot, symbolFinder);
if (helper.canLookupVirtualFunctionOverrides(function)) { if (helper.canLookupVirtualFunctionOverrides(function)) {
VirtualFunctionAssistProvider::Parameters params; VirtualFunctionAssistProvider::Parameters params;
params.function = function; params.function = function;
params.staticClass = helper.staticClassOfFunctionCallExpression();
params.typeOfExpression = typeOfExpression; params.typeOfExpression = typeOfExpression;
params.snapshot = snapshot; params.snapshot = snapshot;
params.cursorPosition = cursor.position(); params.cursorPosition = cursor.position();

View File

@@ -133,6 +133,7 @@ public:
IAssistProposal *perform(const IAssistInterface *) IAssistProposal *perform(const IAssistInterface *)
{ {
QTC_ASSERT(m_params.function, return 0); QTC_ASSERT(m_params.function, return 0);
QTC_ASSERT(m_params.staticClass, return 0);
QTC_ASSERT(!m_params.snapshot.isEmpty(), return 0); QTC_ASSERT(!m_params.snapshot.isEmpty(), return 0);
Class *functionsClass = m_finder.findMatchingClassDeclaration(m_params.function, Class *functionsClass = m_finder.findMatchingClassDeclaration(m_params.function,
@@ -140,8 +141,8 @@ public:
if (!functionsClass) if (!functionsClass)
return 0; return 0;
const QList<Symbol *> overrides const QList<Symbol *> overrides = FunctionHelper::overrides(
= FunctionHelper::overrides(m_params.function, functionsClass, m_params.snapshot); m_params.function, functionsClass, m_params.staticClass, m_params.snapshot);
if (overrides.isEmpty()) if (overrides.isEmpty())
return 0; return 0;
@@ -254,11 +255,40 @@ bool FunctionHelper::isPureVirtualFunction(const Function *function, const Snaps
return isVirtualFunction_helper(function, snapshot, PureVirtual); return isVirtualFunction_helper(function, snapshot, PureVirtual);
} }
QList<Symbol *> FunctionHelper::overrides(Function *function, Class *functionsClass, static bool isDerivedOf(Class *derivedClassCandidate, Class *baseClass,
const Snapshot &snapshot) const Snapshot &snapshot)
{
QTC_ASSERT(derivedClassCandidate && baseClass, return false);
QList<CppClass> l = QList<CppClass>() << CppClass(derivedClassCandidate);
while (!l.isEmpty()) {
CppClass clazz = l.takeFirst();
QTC_ASSERT(clazz.declaration, continue);
const QString fileName = QString::fromUtf8(clazz.declaration->fileName());
const Document::Ptr document = snapshot.document(fileName);
if (!document)
continue;
const LookupContext context(document, snapshot);
clazz.lookupBases(clazz.declaration, context);
foreach (const CppClass &base, clazz.bases) {
if (base.declaration == baseClass)
return true;
if (!l.contains(base))
l << base;
}
}
return false;
}
QList<Symbol *> FunctionHelper::overrides(Function *function, Class *functionsClass,
Class *staticClass, const Snapshot &snapshot)
{ {
QList<Symbol *> result; QList<Symbol *> result;
QTC_ASSERT(function && functionsClass, return result); QTC_ASSERT(function && functionsClass && staticClass, return result);
FullySpecifiedType referenceType = function->type(); FullySpecifiedType referenceType = function->type();
const Name *referenceName = function->name(); const Name *referenceName = function->name();
@@ -274,15 +304,22 @@ QList<Symbol *> FunctionHelper::overrides(Function *function, Class *functionsCl
while (!l.isEmpty()) { while (!l.isEmpty()) {
// Add derived // Add derived
CppClass clazz = l.takeFirst(); CppClass clazz = l.takeFirst();
QTC_ASSERT(clazz.declaration, continue);
Class *c = clazz.declaration->asClass();
QTC_ASSERT(c, continue);
if (c != functionsClass && c != staticClass) {
if (!isDerivedOf(c, staticClass, snapshot))
continue;
}
foreach (const CppClass &d, clazz.derived) { foreach (const CppClass &d, clazz.derived) {
if (!l.contains(d)) if (!l.contains(d))
l << d; l << d;
} }
// Check member functions // Check member functions
QTC_ASSERT(clazz.declaration, continue);
Class *c = clazz.declaration->asClass();
QTC_ASSERT(c, continue);
for (int i = 0, total = c->memberCount(); i < total; ++i) { for (int i = 0, total = c->memberCount(); i < total; ++i) {
Symbol *candidate = c->memberAt(i); Symbol *candidate = c->memberAt(i);
const Name *candidateName = candidate->name(); const Name *candidateName = candidate->name();

View File

@@ -48,9 +48,10 @@ public:
VirtualFunctionAssistProvider(); VirtualFunctionAssistProvider();
struct Parameters { struct Parameters {
Parameters() : function(0), cursorPosition(-1), openInNextSplit(false) {} Parameters() : function(0), staticClass(0), cursorPosition(-1), openInNextSplit(false) {}
CPlusPlus::Function *function; CPlusPlus::Function *function;
CPlusPlus::Class *staticClass;
QSharedPointer<CPlusPlus::TypeOfExpression> typeOfExpression; // Keeps instantiated symbols. QSharedPointer<CPlusPlus::TypeOfExpression> typeOfExpression; // Keeps instantiated symbols.
CPlusPlus::Snapshot snapshot; CPlusPlus::Snapshot snapshot;
int cursorPosition; int cursorPosition;
@@ -80,6 +81,7 @@ public:
static QList<CPlusPlus::Symbol *> overrides(CPlusPlus::Function *function, static QList<CPlusPlus::Symbol *> overrides(CPlusPlus::Function *function,
CPlusPlus::Class *functionsClass, CPlusPlus::Class *functionsClass,
CPlusPlus::Class *staticClass,
const CPlusPlus::Snapshot &snapshot); const CPlusPlus::Snapshot &snapshot);
}; };

View File

@@ -176,7 +176,9 @@ public:
, editor(0) , editor(0)
, editorWidget(0) , editorWidget(0)
{ {
if (initialCursorPosition != -1 || targetCursorPosition != -1)
QVERIFY(initialCursorPosition != targetCursorPosition); QVERIFY(initialCursorPosition != targetCursorPosition);
if (initialCursorPosition > targetCursorPosition) { if (initialCursorPosition > targetCursorPosition) {
source.remove(initialCursorPosition, 1); source.remove(initialCursorPosition, 1);
if (targetCursorPosition != -1) { if (targetCursorPosition != -1) {
@@ -1420,6 +1422,141 @@ void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_instantia
test.run(); test.run();
} }
/// Check: Static type is nicely resolved, especially for QSharedPointers.
void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_QSharedPointer()
{
const QByteArray source =
"template <class T>\n"
"class Basic\n"
"{\n"
"public:\n"
" inline T &operator*() const;\n"
" inline T *operator->() const;\n"
"};\n"
"\n"
"template <class T> class ExternalRefCount: public Basic<T> {};\n"
"template <class T> class QSharedPointer: public ExternalRefCount<T> {};\n"
"\n"
"struct A { virtual void virt() {} };\n"
"struct B : public A { void virt() {} };\n"
"\n"
"int f()\n"
"{\n"
" QSharedPointer<A> p(new A);\n"
" p->$@virt();\n"
"}\n"
;
const OverrideItemList immediateResults = OverrideItemList()
<< OverrideItem(QLatin1String("A::virt"), 12)
<< OverrideItem(QLatin1String("...searching overrides"));
const OverrideItemList finalResults = OverrideItemList()
<< OverrideItem(QLatin1String("A::virt"), 12)
<< OverrideItem(QLatin1String("B::virt"), 13);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source, immediateResults, finalResults);
test.run();
}
/// Check: Base classes can be found although these might be defined in distinct documents.
void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_multipeDocuments()
{
QList<TestDocumentPtr> testFiles = QList<TestDocumentPtr>()
<< TestDocument::create("struct A { virtual void virt(int) = 0; };\n",
QLatin1String("a.h"))
<< TestDocument::create("#include \"a.h\"\n"
"struct B : A { void virt(int) {} };\n",
QLatin1String("b.h"))
<< TestDocument::create("#include \"a.h\"\n"
"void f(A *o) { o->$@virt(42); }\n",
QLatin1String("u.cpp"))
;
const OverrideItemList immediateResults = OverrideItemList()
<< OverrideItem(QLatin1String("A::virt"), 1)
<< OverrideItem(QLatin1String("...searching overrides"));
const OverrideItemList finalResults = OverrideItemList()
<< OverrideItem(QLatin1String("A::virt"), 1)
<< OverrideItem(QLatin1String("B::virt"), 2);
TestCase test(TestCase::FollowSymbolUnderCursorAction, testFiles, immediateResults,
finalResults);
test.run();
}
/// Check: In case there is no override for the static type of a function call expression,
/// make sure to:
/// 1) include the last provided override (look up bases)
/// 2) and all overrides whose classes are derived from that static type
void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_noSiblings_references()
{
const QByteArray source =
"struct A { virtual void virt(); };\n"
"struct B : A { void virt() {} };\n"
"struct C1 : B { void virt() {} };\n"
"struct C2 : B { };\n"
"struct D : C2 { void virt() {} };\n"
"\n"
"void f(C2 &o) { o.$@virt(); }\n"
;
const OverrideItemList immediateResults = OverrideItemList()
<< OverrideItem(QLatin1String("B::virt"), 2)
<< OverrideItem(QLatin1String("...searching overrides"));
const OverrideItemList finalResults = OverrideItemList()
<< OverrideItem(QLatin1String("B::virt"), 2)
<< OverrideItem(QLatin1String("D::virt"), 5);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source, immediateResults, finalResults);
test.run();
}
/// Variation of test_FollowSymbolUnderCursor_virtualFunctionCall_noSiblings_references
void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_noSiblings_pointers()
{
const QByteArray source =
"struct A { virtual void virt(); };\n"
"struct B : A { void virt() {} };\n"
"struct C1 : B { void virt() {} };\n"
"struct C2 : B { };\n"
"struct D : C2 { void virt() {} };\n"
"\n"
"void f(C2 *o) { o->$@virt(); }\n"
;
const OverrideItemList immediateResults = OverrideItemList()
<< OverrideItem(QLatin1String("B::virt"), 2)
<< OverrideItem(QLatin1String("...searching overrides"));
const OverrideItemList finalResults = OverrideItemList()
<< OverrideItem(QLatin1String("B::virt"), 2)
<< OverrideItem(QLatin1String("D::virt"), 5);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source, immediateResults, finalResults);
test.run();
}
/// Variation of test_FollowSymbolUnderCursor_virtualFunctionCall_noSiblings_references
void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_noSiblings_noBaseExpression()
{
const QByteArray source =
"struct A { virtual void virt() {} };\n"
"struct B : A { void virt() {} };\n"
"struct C1 : B { void virt() {} };\n"
"struct C2 : B { void g() { $@virt(); } };\n"
"struct D : C2 { void virt() {} };\n"
;
const OverrideItemList immediateResults = OverrideItemList()
<< OverrideItem(QLatin1String("B::virt"), 2)
<< OverrideItem(QLatin1String("...searching overrides"));
const OverrideItemList finalResults = OverrideItemList()
<< OverrideItem(QLatin1String("B::virt"), 2)
<< OverrideItem(QLatin1String("D::virt"), 5);
TestCase test(TestCase::FollowSymbolUnderCursorAction, source, immediateResults, finalResults);
test.run();
}
/// Check: Trigger on a.virt() if a is of type &A. /// Check: Trigger on a.virt() if a is of type &A.
void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_onDotMemberAccessOfReferenceTypes() void CppEditorPlugin::test_FollowSymbolUnderCursor_virtualFunctionCall_onDotMemberAccessOfReferenceTypes()
{ {