forked from qt-creator/qt-creator
C++: Completion for templates as base classes
This fixes a variety of issues regarding class completion when templates are used as base classes. The test cases show examples. Task-number: QTCREATORBUG-4357 Change-Id: I764d5ce817a78e1b19336e5beab758ca9e10f34b Reviewed-by: Roberto Raggi <roberto.raggi@nokia.com>
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
#include "ResolveExpression.h"
|
||||
#include "Overview.h"
|
||||
#include "DeprecatedGenTemplateInstance.h"
|
||||
#include "CppRewriter.h"
|
||||
|
||||
#include <CoreTypes.h>
|
||||
#include <Symbols.h>
|
||||
@@ -382,9 +383,6 @@ QList<Enum *> ClassOrNamespace::enums() const
|
||||
|
||||
QList<Symbol *> ClassOrNamespace::symbols() const
|
||||
{
|
||||
if (_templateId && ! _usings.isEmpty())
|
||||
return _usings.first()->symbols(); // ask to the base implementation
|
||||
|
||||
const_cast<ClassOrNamespace *>(this)->flush();
|
||||
return _symbols;
|
||||
}
|
||||
@@ -608,7 +606,7 @@ ClassOrNamespace *ClassOrNamespace::lookupType_helper(const Name *name,
|
||||
return 0;
|
||||
}
|
||||
|
||||
ClassOrNamespace *ClassOrNamespace::nestedType(const Name *name, ClassOrNamespace *origin) const
|
||||
ClassOrNamespace *ClassOrNamespace::nestedType(const Name *name, ClassOrNamespace *origin)
|
||||
{
|
||||
Q_ASSERT(name != 0);
|
||||
Q_ASSERT(name->isNameId() || name->isTemplateNameId());
|
||||
@@ -616,21 +614,131 @@ ClassOrNamespace *ClassOrNamespace::nestedType(const Name *name, ClassOrNamespac
|
||||
const_cast<ClassOrNamespace *>(this)->flush();
|
||||
|
||||
Table::const_iterator it = _classOrNamespaces.find(name);
|
||||
|
||||
if (it == _classOrNamespaces.end())
|
||||
return 0;
|
||||
|
||||
ClassOrNamespace *c = it->second;
|
||||
ClassOrNamespace *reference = it->second;
|
||||
|
||||
if (const TemplateNameId *templId = name->asTemplateNameId()) {
|
||||
ClassOrNamespace *i = _factory->allocClassOrNamespace(c);
|
||||
i->_templateId = templId;
|
||||
i->_instantiationOrigin = origin;
|
||||
i->_usings.append(c);
|
||||
return i;
|
||||
// The reference binding might still be missing some of its base classes in the case they
|
||||
// are templates. We need to collect them now. First, we track the bases which are already
|
||||
// part of the binding so we can identify the missings ones later.
|
||||
|
||||
QSet<const Name *> knownBases;
|
||||
foreach (ClassOrNamespace *con, reference->usings()) {
|
||||
foreach (Symbol *s, con->symbols()) {
|
||||
if (Class *c = s->asClass()) {
|
||||
knownBases.insert(c->name());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
Class *referenceClass = 0;
|
||||
QList<const Name *> missingBases;
|
||||
foreach (Symbol *s, reference->symbols()) {
|
||||
if (Class *clazz = s->asClass()) {
|
||||
for (unsigned i = 0; i < clazz->baseClassCount(); ++i) {
|
||||
BaseClass *baseClass = clazz->baseClassAt(i);
|
||||
if (baseClass->name() && !knownBases.contains(baseClass->name()))
|
||||
missingBases.append(baseClass->name());
|
||||
}
|
||||
referenceClass = clazz;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!referenceClass)
|
||||
return reference;
|
||||
|
||||
// If we are dealling with a template type, more work is required, since we need to
|
||||
// construct all instantiation data.
|
||||
if (const TemplateNameId *templId = name->asTemplateNameId()) {
|
||||
ClassOrNamespace *instantiation = _factory->allocClassOrNamespace(reference);
|
||||
instantiation->_templateId = templId;
|
||||
instantiation->_instantiationOrigin = origin;
|
||||
|
||||
// The instantiation should have all symbols, enums, and usings from the reference.
|
||||
instantiation->_symbols.append(reference->symbols());
|
||||
instantiation->_enums.append(reference->enums());
|
||||
instantiation->_usings.append(reference->usings());
|
||||
|
||||
// It gets a bit complicated if the reference is actually a class template because we
|
||||
// now must worry about dependent names in base classes.
|
||||
if (Template *templ = referenceClass->enclosingTemplate()) {
|
||||
QHash<const Name*, unsigned> templParams;
|
||||
for (unsigned i = 0; i < templ->templateParameterCount(); ++i)
|
||||
templParams.insert(templ->templateParameterAt(i)->name(), i);
|
||||
|
||||
foreach (const Name *baseName, missingBases) {
|
||||
ClassOrNamespace *baseBinding = 0;
|
||||
|
||||
if (const Identifier *nameId = baseName->asNameId()) {
|
||||
// This is the simple case in which a template parameter is itself a base.
|
||||
// Ex.: template <class T> class A : public T {};
|
||||
if (templParams.contains(nameId)) {
|
||||
const FullySpecifiedType &fullType =
|
||||
templId->templateArgumentAt(templParams.value(nameId));
|
||||
if (NamedType *namedType = fullType.type()->asNamedType())
|
||||
baseBinding = lookupType(namedType->name());
|
||||
}
|
||||
} else {
|
||||
SubstitutionMap map;
|
||||
for (unsigned i = 0;
|
||||
i < templ->templateParameterCount() && i < templId->templateArgumentCount();
|
||||
++i) {
|
||||
map.bind(templ->templateParameterAt(i)->name(),
|
||||
templId->templateArgumentAt(i));
|
||||
}
|
||||
SubstitutionEnvironment env;
|
||||
env.enter(&map);
|
||||
|
||||
baseName = rewriteName(baseName, &env, _control.data());
|
||||
|
||||
if (const TemplateNameId *baseTemplId = baseName->asTemplateNameId()) {
|
||||
// Another template that uses the dependent name.
|
||||
// Ex.: template <class T> class A : public B<T> {};
|
||||
if (baseTemplId->identifier() != templId->identifier())
|
||||
baseBinding = nestedType(baseName, origin);
|
||||
} else if (const QualifiedNameId *qBaseName = baseName->asQualifiedNameId()) {
|
||||
// Qualified names in general.
|
||||
// Ex.: template <class T> class A : public B<T>::Type {};
|
||||
ClassOrNamespace *binding = this;
|
||||
if (const Name *qualification = qBaseName->base())
|
||||
binding = lookupType(qualification);
|
||||
baseName = qBaseName->name();
|
||||
|
||||
if (binding)
|
||||
baseBinding = binding->lookupType(baseName);
|
||||
}
|
||||
}
|
||||
|
||||
if (baseBinding)
|
||||
instantiation->addUsing(baseBinding);
|
||||
}
|
||||
}
|
||||
|
||||
return instantiation;
|
||||
}
|
||||
|
||||
// Find the missing bases for regular (non-template) types.
|
||||
// Ex.: class A : public B<Some>::Type {};
|
||||
foreach (const Name *baseName, missingBases) {
|
||||
ClassOrNamespace *binding = this;
|
||||
if (const QualifiedNameId *qBaseName = baseName->asQualifiedNameId()) {
|
||||
if (const Name *qualification = qBaseName->base())
|
||||
binding = lookupType(qualification);
|
||||
baseName = qBaseName->name();
|
||||
}
|
||||
|
||||
if (binding) {
|
||||
ClassOrNamespace * baseBinding = binding->lookupType(baseName);
|
||||
if (baseBinding)
|
||||
reference->addUsing(baseBinding);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return reference;
|
||||
}
|
||||
|
||||
void ClassOrNamespace::flush()
|
||||
@@ -761,6 +869,7 @@ QSharedPointer<Control> CreateBindings::control() const
|
||||
ClassOrNamespace *CreateBindings::allocClassOrNamespace(ClassOrNamespace *parent)
|
||||
{
|
||||
ClassOrNamespace *e = new ClassOrNamespace(this, parent);
|
||||
e->_control = control();
|
||||
_entities.append(e);
|
||||
return e;
|
||||
}
|
||||
|
@@ -90,7 +90,7 @@ private:
|
||||
ClassOrNamespace *lookupType_helper(const Name *name, QSet<ClassOrNamespace *> *processed,
|
||||
bool searchInEnclosingScope, ClassOrNamespace *origin);
|
||||
|
||||
ClassOrNamespace *nestedType(const Name *name, ClassOrNamespace *origin) const;
|
||||
ClassOrNamespace *nestedType(const Name *name, ClassOrNamespace *origin);
|
||||
|
||||
private:
|
||||
struct CompareName: std::binary_function<const Name *, const Name *, bool> {
|
||||
@@ -106,6 +106,7 @@ private:
|
||||
Table _classOrNamespaces;
|
||||
QList<Enum *> _enums;
|
||||
QList<Symbol *> _todo;
|
||||
QSharedPointer<Control> _control;
|
||||
|
||||
// it's an instantiation.
|
||||
const TemplateNameId *_templateId;
|
||||
|
@@ -196,3 +196,172 @@ void CppToolsPlugin::test_completion_template_1()
|
||||
QVERIFY(!completions.contains("f"));
|
||||
QVERIFY(!completions.contains("func"));
|
||||
}
|
||||
|
||||
void CppToolsPlugin::test_completion_template_as_base()
|
||||
{
|
||||
QFETCH(QByteArray, code);
|
||||
QFETCH(QStringList, expectedCompletions);
|
||||
|
||||
TestData data;
|
||||
data.srcText = code;
|
||||
setup(&data);
|
||||
|
||||
Utils::ChangeSet change;
|
||||
change.insert(data.pos, "c.");
|
||||
QTextCursor cursor(data.doc);
|
||||
change.apply(&cursor);
|
||||
data.pos += 2;
|
||||
|
||||
QStringList actualCompletions = getCompletions(data);
|
||||
actualCompletions.sort();
|
||||
expectedCompletions.sort();
|
||||
|
||||
QCOMPARE(actualCompletions, expectedCompletions);
|
||||
}
|
||||
|
||||
void CppToolsPlugin::test_completion_template_as_base_data()
|
||||
{
|
||||
QTest::addColumn<QByteArray>("code");
|
||||
QTest::addColumn<QStringList>("expectedCompletions");
|
||||
|
||||
QByteArray code;
|
||||
QStringList completions;
|
||||
|
||||
code = "\n"
|
||||
"class Data { int dataMember; };\n"
|
||||
"template <class T> class Other : public T { int otherMember; };\n"
|
||||
"\n"
|
||||
"void func() {\n"
|
||||
" Other<Data> c;\n"
|
||||
" @\n"
|
||||
" // padding so we get the scope right\n"
|
||||
"}";
|
||||
completions.append("Data");
|
||||
completions.append("dataMember");
|
||||
completions.append("Other");
|
||||
completions.append("otherMember");
|
||||
QTest::newRow("case: base as template directly") << code << completions;
|
||||
|
||||
|
||||
completions.clear();
|
||||
code = "\n"
|
||||
"class Data { int dataMember; };\n"
|
||||
"template <class T> class Other : public T { int otherMember; };\n"
|
||||
"template <class T> class More : public Other<T> { int moreMember; };\n"
|
||||
"\n"
|
||||
"void func() {\n"
|
||||
" More<Data> c;\n"
|
||||
" @\n"
|
||||
" // padding so we get the scope right\n"
|
||||
"}";
|
||||
completions.append("Data");
|
||||
completions.append("dataMember");
|
||||
completions.append("Other");
|
||||
completions.append("otherMember");
|
||||
completions.append("More");
|
||||
completions.append("moreMember");
|
||||
QTest::newRow("case: base as class template") << code << completions;
|
||||
|
||||
|
||||
completions.clear();
|
||||
code = "\n"
|
||||
"class Data { int dataMember; };\n"
|
||||
"template <class T> class Other : public T { int otherMember; };\n"
|
||||
"template <class T> class More : public ::Other<T> { int moreMember; };\n"
|
||||
"\n"
|
||||
"void func() {\n"
|
||||
" More<Data> c;\n"
|
||||
" @\n"
|
||||
" // padding so we get the scope right\n"
|
||||
"}";
|
||||
completions.append("Data");
|
||||
completions.append("dataMember");
|
||||
completions.append("Other");
|
||||
completions.append("otherMember");
|
||||
completions.append("More");
|
||||
completions.append("moreMember");
|
||||
QTest::newRow("case: base as globally qualified class template") << code << completions;
|
||||
|
||||
|
||||
completions.clear();
|
||||
code = "\n"
|
||||
"class Data { int dataMember; };\n"
|
||||
"namespace NS {\n"
|
||||
"template <class T> class Other : public T { int otherMember; };\n"
|
||||
"}\n"
|
||||
"template <class T> class More : public NS::Other<T> { int moreMember; };\n"
|
||||
"\n"
|
||||
"void func() {\n"
|
||||
" More<Data> c;\n"
|
||||
" @\n"
|
||||
" // padding so we get the scope right\n"
|
||||
"}";
|
||||
completions.append("Data");
|
||||
completions.append("dataMember");
|
||||
completions.append("Other");
|
||||
completions.append("otherMember");
|
||||
completions.append("More");
|
||||
completions.append("moreMember");
|
||||
QTest::newRow("case: base as namespace qualified class template") << code << completions;
|
||||
|
||||
|
||||
completions.clear();
|
||||
code = "\n"
|
||||
"class Data { int dataMember; };\n"
|
||||
"namespace NS {\n"
|
||||
"template <class T> class Delegate { typedef Data<T> Type; };\n"
|
||||
"}\n"
|
||||
"template <class T> class Final : public NS::Delegate<T>::Type { int finalMember; };\n"
|
||||
"\n"
|
||||
"void func() {\n"
|
||||
" Final<Data> c;\n"
|
||||
" @\n"
|
||||
" // padding so we get the scope right\n"
|
||||
"}";
|
||||
completions.append("Data");
|
||||
completions.append("dataMember");
|
||||
completions.append("Final");
|
||||
completions.append("finalMember");
|
||||
QTest::newRow("case: base as nested template name") << code << completions;
|
||||
|
||||
|
||||
completions.clear();
|
||||
code = "\n"
|
||||
"class Data { int dataMember; };\n"
|
||||
"namespace NS {\n"
|
||||
"template <class T> class Delegate { typedef Data<T> Type; };\n"
|
||||
"}\n"
|
||||
"class Final : public NS::Delegate<Data>::Type { int finalMember; };\n"
|
||||
"\n"
|
||||
"void func() {\n"
|
||||
" Final c;\n"
|
||||
" @\n"
|
||||
" // padding so we get the scope right\n"
|
||||
"}";
|
||||
completions.append("Data");
|
||||
completions.append("dataMember");
|
||||
completions.append("Final");
|
||||
completions.append("finalMember");
|
||||
QTest::newRow("case: base as nested template name in non-template") << code << completions;
|
||||
|
||||
completions.clear();
|
||||
code = "\n"
|
||||
"class Data { int dataMember; };\n"
|
||||
"namespace NS {\n"
|
||||
"template <class T> class Other : public T { int otherMember; };\n"
|
||||
"}\n"
|
||||
"class Final : public NS::Other<Data> { int finalMember; };\n"
|
||||
"\n"
|
||||
"void func() {\n"
|
||||
" Final c;\n"
|
||||
" @\n"
|
||||
" // padding so we get the scope right\n"
|
||||
"}";
|
||||
completions.append("Data");
|
||||
completions.append("dataMember");
|
||||
completions.append("Final");
|
||||
completions.append("finalMember");
|
||||
completions.append("Other");
|
||||
completions.append("otherMember");
|
||||
QTest::newRow("case: base as template name in non-template") << code << completions;
|
||||
}
|
||||
|
@@ -92,6 +92,8 @@ private slots:
|
||||
|
||||
void test_completion_basic_1();
|
||||
void test_completion_template_1();
|
||||
void test_completion_template_as_base();
|
||||
void test_completion_template_as_base_data();
|
||||
#endif
|
||||
|
||||
private:
|
||||
|
Reference in New Issue
Block a user