C++: Fix code completion for nested classes

Fix code completion for nested classes when enclosing is
template class.
Unit tests

Task-number: QTCREATORBUG-8245 (only standalone)
Change-Id: Ib31ad4b799db927b56debd4dc3e7403404c1839d
Reviewed-by: hjk <qthjk@ovi.com>
This commit is contained in:
Przemyslaw Gorszkowski
2012-12-07 14:31:32 +01:00
committed by hjk
parent 17748280e8
commit b1199ef0cc
8 changed files with 271 additions and 22 deletions

View File

@@ -105,7 +105,7 @@ Symbol::Symbol(TranslationUnit *translationUnit, unsigned sourceLocation, const
Symbol::Symbol(Clone *clone, Subst *subst, Symbol *original) Symbol::Symbol(Clone *clone, Subst *subst, Symbol *original)
: _name(clone->name(original->_name, subst)), : _name(clone->name(original->_name, subst)),
_scope(0), _scope(original->_scope),
_next(0), _next(0),
_fileId(clone->control()->stringLiteral(original->fileName(), original->fileNameLength())), _fileId(clone->control()->stringLiteral(original->fileName(), original->fileNameLength())),
_sourceLocation(original->_sourceLocation), _sourceLocation(original->_sourceLocation),

View File

@@ -54,6 +54,8 @@ public:
FullySpecifiedType &operator[](const Name *name) { return _map[name]; } FullySpecifiedType &operator[](const Name *name) { return _map[name]; }
bool contains(const Name *name) const { return _map.find(name) != _map.end(); }
private: private:
Control *_control; Control *_control;
Subst *_previous; Subst *_previous;

View File

@@ -260,7 +260,8 @@ ClassOrNamespace *LookupContext::globalNamespace() const
return bindings()->globalNamespace(); return bindings()->globalNamespace();
} }
ClassOrNamespace *LookupContext::lookupType(const Name *name, Scope *scope) const ClassOrNamespace *LookupContext::lookupType(const Name *name, Scope *scope,
ClassOrNamespace* enclosingTemplateInstantiation) const
{ {
if (! scope) { if (! scope) {
return 0; return 0;
@@ -286,16 +287,17 @@ ClassOrNamespace *LookupContext::lookupType(const Name *name, Scope *scope) cons
} }
} }
return lookupType(name, scope->enclosingScope()); return lookupType(name, scope->enclosingScope());
} else if (ClassOrNamespace *b = bindings()->lookupType(scope)) { } else if (ClassOrNamespace *b = bindings()->lookupType(scope, enclosingTemplateInstantiation)) {
return b->lookupType(name); return b->lookupType(name);
} }
return 0; return 0;
} }
ClassOrNamespace *LookupContext::lookupType(Symbol *symbol) const ClassOrNamespace *LookupContext::lookupType(Symbol *symbol,
ClassOrNamespace* enclosingTemplateInstantiation) const
{ {
return bindings()->lookupType(symbol); return bindings()->lookupType(symbol, enclosingTemplateInstantiation);
} }
QList<LookupItem> LookupContext::lookup(const Name *name, Scope *scope) const QList<LookupItem> LookupContext::lookup(const Name *name, Scope *scope) const
@@ -587,7 +589,8 @@ void CreateBindings::lookupInScope(const Name *name, Scope *scope,
#ifdef DEBUG_LOOKUP #ifdef DEBUG_LOOKUP
Overview oo; Overview oo;
qDebug() << "Found" << id->chars() << "in" << (binding ? oo(binding->_name) : "<null>"); qDebug() << "Found" << id->chars() << "in"
<< (binding ? oo(binding->_name) : QString::fromAscii("<null>"));
#endif // DEBUG_LOOKUP #endif // DEBUG_LOOKUP
LookupItem item; LookupItem item;
@@ -670,16 +673,23 @@ ClassOrNamespace *ClassOrNamespace::lookupType_helper(const Name *name,
if (_usings.size() == 1) { if (_usings.size() == 1) {
ClassOrNamespace *delegate = _usings.first(); ClassOrNamespace *delegate = _usings.first();
if (ClassOrNamespace *r = delegate->lookupType_helper(name, processed, /*searchInEnclosingScope = */ true, origin)) if (ClassOrNamespace *r = delegate->lookupType_helper(name,
processed,
/*searchInEnclosingScope = */ true,
origin))
return r; return r;
} else { } else {
if (debug) if (debug)
qWarning() << "expected one using declaration. Number of using declarations is:" << _usings.size(); qWarning() << "expected one using declaration. Number of using declarations is:"
<< _usings.size();
} }
} }
foreach (ClassOrNamespace *u, usings()) { foreach (ClassOrNamespace *u, usings()) {
if (ClassOrNamespace *r = u->lookupType_helper(name, processed, /*searchInEnclosingScope =*/ false, origin)) if (ClassOrNamespace *r = u->lookupType_helper(name,
processed,
/*searchInEnclosingScope =*/ false,
origin))
return r; return r;
} }
} }
@@ -740,6 +750,9 @@ ClassOrNamespace *ClassOrNamespace::nestedType(const Name *name, ClassOrNamespac
// If we are dealling with a template type, more work is required, since we need to // If we are dealling with a template type, more work is required, since we need to
// construct all instantiation data. // construct all instantiation data.
if (templId) { if (templId) {
if (_instantiations.contains(templId))
return _instantiations[templId];
_alreadyConsideredTemplates.insert(templId); _alreadyConsideredTemplates.insert(templId);
ClassOrNamespace *instantiation = _factory->allocClassOrNamespace(reference); ClassOrNamespace *instantiation = _factory->allocClassOrNamespace(reference);
#ifdef DEBUG_LOOKUP #ifdef DEBUG_LOOKUP
@@ -747,7 +760,6 @@ ClassOrNamespace *ClassOrNamespace::nestedType(const Name *name, ClassOrNamespac
#endif // DEBUG_LOOKUP #endif // DEBUG_LOOKUP
instantiation->_templateId = templId; instantiation->_templateId = templId;
instantiation->_instantiationOrigin = origin; instantiation->_instantiationOrigin = origin;
instantiation->_classOrNamespaces = reference->_classOrNamespaces;
// The instantiation should have all symbols, enums, and usings from the reference. // The instantiation should have all symbols, enums, and usings from the reference.
instantiation->_enums.append(reference->unscopedEnums()); instantiation->_enums.append(reference->unscopedEnums());
@@ -780,6 +792,7 @@ ClassOrNamespace *ClassOrNamespace::nestedType(const Name *name, ClassOrNamespac
qDebug()<<"cloned"<<oo(clone->type()); qDebug()<<"cloned"<<oo(clone->type());
#endif // DEBUG_LOOKUP #endif // DEBUG_LOOKUP
} }
instantiateNestedClasses(reference, cloner, subst, instantiation);
} else { } else {
instantiation->_symbols.append(reference->symbols()); instantiation->_symbols.append(reference->symbols());
} }
@@ -843,10 +856,12 @@ ClassOrNamespace *ClassOrNamespace::nestedType(const Name *name, ClassOrNamespac
instantiation->addUsing(baseBinding); instantiation->addUsing(baseBinding);
} }
} else { } else {
instantiation->_classOrNamespaces = reference->_classOrNamespaces;
instantiation->_symbols.append(reference->symbols()); instantiation->_symbols.append(reference->symbols());
} }
_alreadyConsideredTemplates.clear(templId); _alreadyConsideredTemplates.clear(templId);
_instantiations[templId] = instantiation;
return instantiation; return instantiation;
} }
@@ -881,6 +896,104 @@ ClassOrNamespace *ClassOrNamespace::nestedType(const Name *name, ClassOrNamespac
return reference; return reference;
} }
void ClassOrNamespace::instantiateNestedClasses(ClassOrNamespace *enclosingTemplateClass,
Clone &cloner,
Subst &subst,
ClassOrNamespace *enclosingTemplateClassInstantiation)
{
NestedClassInstantiator nestedClassInstantiator(_factory, cloner, subst);
nestedClassInstantiator.instantiate(enclosingTemplateClass, enclosingTemplateClassInstantiation);
}
void ClassOrNamespace::NestedClassInstantiator::instantiate(ClassOrNamespace *enclosingTemplateClass,
ClassOrNamespace *enclosingTemplateClassInstantiation)
{
if (_alreadyConsideredNestedClassInstantiations.contains(enclosingTemplateClass))
return;
_alreadyConsideredNestedClassInstantiations.insert(enclosingTemplateClass);
ClassOrNamespace::Table::const_iterator cit = enclosingTemplateClass->_classOrNamespaces.begin();
for (; cit != enclosingTemplateClass->_classOrNamespaces.end(); ++cit) {
const Name *nestedName = cit->first;
ClassOrNamespace *nestedClassOrNamespace = cit->second;
ClassOrNamespace *nestedClassOrNamespaceInstantiation = nestedClassOrNamespace;
if (isInstantiateNestedClassNeeded(nestedClassOrNamespace->_symbols)) {
nestedClassOrNamespaceInstantiation = _factory->allocClassOrNamespace(nestedClassOrNamespace);
nestedClassOrNamespaceInstantiation->_enums.append(nestedClassOrNamespace->unscopedEnums());
nestedClassOrNamespaceInstantiation->_usings.append(nestedClassOrNamespace->usings());
nestedClassOrNamespaceInstantiation->_instantiationOrigin = nestedClassOrNamespace;
foreach (Symbol *s, nestedClassOrNamespace->_symbols) {
Symbol *clone = _cloner.symbol(s, &_subst);
nestedClassOrNamespaceInstantiation->_symbols.append(clone);
}
}
instantiate(nestedClassOrNamespace, nestedClassOrNamespaceInstantiation);
enclosingTemplateClassInstantiation->_classOrNamespaces[nestedName] =
nestedClassOrNamespaceInstantiation;
}
_alreadyConsideredNestedClassInstantiations.remove(enclosingTemplateClass);
}
bool ClassOrNamespace::NestedClassInstantiator::isInstantiateNestedClassNeeded(const QList<Symbol *> &symbols) const
{
foreach (Symbol *s, symbols) {
if (Class *klass = s->asClass()) {
int memberCount = klass->memberCount();
for (int i = 0; i < memberCount; ++i) {
Symbol *memberAsSymbol = klass->memberAt(i);
if (Declaration *declaration = memberAsSymbol->asDeclaration()) {
if (containsTemplateType(declaration))
return true;
}
else if (Function *function = memberAsSymbol->asFunction()) {
if (containsTemplateType(function))
return true;
}
}
}
}
return false;
}
bool ClassOrNamespace::NestedClassInstantiator::containsTemplateType(Declaration *declaration) const
{
Type *memberType = declaration->type().type();
NamedType *memberNamedType = findMemberNamedType(memberType);
if (memberNamedType) {
const Name *name = memberNamedType->name();
if (_subst.contains(name)) {
return true;
}
}
return false;
}
bool ClassOrNamespace::NestedClassInstantiator::containsTemplateType(Function * /*function*/) const
{
//TODO: make implementation
return false;
}
NamedType *ClassOrNamespace::NestedClassInstantiator::findMemberNamedType(Type *memberType) const
{
if (NamedType *namedType = memberType->asNamedType()) {
return namedType;
}
else if (PointerType *pointerType = memberType->asPointerType()) {
return findMemberNamedType(pointerType->elementType().type());
}
else if (ReferenceType *referenceType = memberType->asReferenceType()) {
return findMemberNamedType(referenceType->elementType().type());
}
return 0;
}
void ClassOrNamespace::flush() void ClassOrNamespace::flush()
{ {
if (! _todo.isEmpty()) { if (! _todo.isEmpty()) {
@@ -973,17 +1086,22 @@ ClassOrNamespace *CreateBindings::globalNamespace() const
return _globalNamespace; return _globalNamespace;
} }
ClassOrNamespace *CreateBindings::lookupType(Symbol *symbol) ClassOrNamespace *CreateBindings::lookupType(Symbol *symbol, ClassOrNamespace* enclosingTemplateInstantiation)
{ {
const QList<const Name *> path = LookupContext::path(symbol); const QList<const Name *> path = LookupContext::path(symbol);
return lookupType(path); return lookupType(path, enclosingTemplateInstantiation);
} }
ClassOrNamespace *CreateBindings::lookupType(const QList<const Name *> &path) ClassOrNamespace *CreateBindings::lookupType(const QList<const Name *> &path, ClassOrNamespace* enclosingTemplateInstantiation)
{ {
if (path.isEmpty()) if (path.isEmpty())
return _globalNamespace; return _globalNamespace;
if (enclosingTemplateInstantiation) {
if (ClassOrNamespace *b = enclosingTemplateInstantiation->lookupType(path.last()))
return b;
}
ClassOrNamespace *b = _globalNamespace->lookupType(path.at(0)); ClassOrNamespace *b = _globalNamespace->lookupType(path.at(0));
for (int i = 1; b && i < path.size(); ++i) for (int i = 1; b && i < path.size(); ++i)

View File

@@ -41,6 +41,7 @@
#include <QSet> #include <QSet>
#include <map> #include <map>
#include <functional> #include <functional>
#include <QMap>
namespace CPlusPlus { namespace CPlusPlus {
@@ -92,8 +93,15 @@ private:
ClassOrNamespace *nestedType(const Name *name, ClassOrNamespace *origin); ClassOrNamespace *nestedType(const Name *name, ClassOrNamespace *origin);
void instantiateNestedClasses(ClassOrNamespace *enclosingTemplateClass,
Clone &cloner,
Subst &subst,
ClassOrNamespace *enclosingTemplateClassInstantiation);
bool isInstantiateNestedClassNeeded(const QList<Symbol *>& symbols, const Subst &subst) const;
private: private:
typedef std::map<const Name *, ClassOrNamespace *, Name::Compare> Table; typedef std::map<const Name *, ClassOrNamespace *, Name::Compare> Table;
CreateBindings *_factory; CreateBindings *_factory;
ClassOrNamespace *_parent; ClassOrNamespace *_parent;
QList<Symbol *> _symbols; QList<Symbol *> _symbols;
@@ -102,6 +110,7 @@ private:
QList<Enum *> _enums; QList<Enum *> _enums;
QList<Symbol *> _todo; QList<Symbol *> _todo;
QSharedPointer<Control> _control; QSharedPointer<Control> _control;
QMap<const Name *, ClassOrNamespace *> _instantiations;
// it's an instantiation. // it's an instantiation.
const TemplateNameId *_templateId; const TemplateNameId *_templateId;
@@ -110,6 +119,28 @@ private:
AlreadyConsideredClassContainer<Class> _alreadyConsideredClasses; AlreadyConsideredClassContainer<Class> _alreadyConsideredClasses;
AlreadyConsideredClassContainer<TemplateNameId> _alreadyConsideredTemplates; AlreadyConsideredClassContainer<TemplateNameId> _alreadyConsideredTemplates;
class NestedClassInstantiator
{
public:
NestedClassInstantiator(CreateBindings *factory, Clone &cloner, Subst &subst)
: _factory(factory)
, _cloner(cloner)
, _subst(subst)
{}
void instantiate(ClassOrNamespace *enclosingTemplateClass,
ClassOrNamespace *enclosingTemplateClassInstantiation);
private:
bool isInstantiateNestedClassNeeded(const QList<Symbol *> &symbols) const;
bool containsTemplateType(Declaration *declaration) const;
bool containsTemplateType(Function *function) const;
NamedType *findMemberNamedType(Type *memberType) const;
QSet<ClassOrNamespace *> _alreadyConsideredNestedClassInstantiations;
CreateBindings *_factory;
Clone &_cloner;
Subst &_subst;
};
#ifdef DEBUG_LOOKUP #ifdef DEBUG_LOOKUP
public: public:
const Name *_name; const Name *_name;
@@ -130,8 +161,10 @@ public:
ClassOrNamespace *globalNamespace() const; ClassOrNamespace *globalNamespace() const;
/// Finds the binding associated to the given symbol. /// Finds the binding associated to the given symbol.
ClassOrNamespace *lookupType(Symbol *symbol); ClassOrNamespace *lookupType(Symbol *symbol,
ClassOrNamespace *lookupType(const QList<const Name *> &path); ClassOrNamespace* enclosingTemplateInstantiation = 0);
ClassOrNamespace *lookupType(const QList<const Name *> &path,
ClassOrNamespace* enclosingTemplateInstantiation = 0);
/// Returns the Control that must be used to create temporary symbols. /// Returns the Control that must be used to create temporary symbols.
/// \internal /// \internal
@@ -228,8 +261,10 @@ public:
ClassOrNamespace *globalNamespace() const; ClassOrNamespace *globalNamespace() const;
QList<LookupItem> lookup(const Name *name, Scope *scope) const; QList<LookupItem> lookup(const Name *name, Scope *scope) const;
ClassOrNamespace *lookupType(const Name *name, Scope *scope) const; ClassOrNamespace *lookupType(const Name *name, Scope *scope,
ClassOrNamespace *lookupType(Symbol *symbol) const; ClassOrNamespace* enclosingTemplateInstantiation = 0) const;
ClassOrNamespace *lookupType(Symbol *symbol,
ClassOrNamespace* enclosingTemplateInstantiation = 0) const;
ClassOrNamespace *lookupParent(Symbol *symbol) const; ClassOrNamespace *lookupParent(Symbol *symbol) const;
/// \internal /// \internal

View File

@@ -811,16 +811,17 @@ bool ResolveExpression::visit(MemberAccessAST *ast)
return false; return false;
} }
ClassOrNamespace *ResolveExpression::findClass(const FullySpecifiedType &originalTy, Scope *scope) const ClassOrNamespace *ResolveExpression::findClass(const FullySpecifiedType &originalTy, Scope *scope,
ClassOrNamespace* enclosingTemplateInstantiation) const
{ {
FullySpecifiedType ty = originalTy.simplified(); FullySpecifiedType ty = originalTy.simplified();
ClassOrNamespace *binding = 0; ClassOrNamespace *binding = 0;
if (Class *klass = ty->asClassType()) if (Class *klass = ty->asClassType())
binding = _context.lookupType(klass); binding = _context.lookupType(klass, enclosingTemplateInstantiation);
else if (NamedType *namedTy = ty->asNamedType()) else if (NamedType *namedTy = ty->asNamedType())
binding = _context.lookupType(namedTy->name(), scope); binding = _context.lookupType(namedTy->name(), scope, enclosingTemplateInstantiation);
else if (Function *funTy = ty->asFunctionType()) else if (Function *funTy = ty->asFunctionType())
return findClass(funTy->returnType(), scope); return findClass(funTy->returnType(), scope);
@@ -979,7 +980,13 @@ ClassOrNamespace *ResolveExpression::baseExpression(const QList<LookupItem> &bas
} }
} }
if (ClassOrNamespace *binding = findClass(ty, scope)) ClassOrNamespace *enclosingTemplateInstantiation = 0;
if (ClassOrNamespace *binding = r.binding()) {
if (binding->instantiationOrigin())
enclosingTemplateInstantiation = binding;
}
if (ClassOrNamespace *binding = findClass(ty, scope, enclosingTemplateInstantiation))
return binding; return binding;
} }
} }

View File

@@ -56,7 +56,8 @@ public:
const LookupContext &context() const; const LookupContext &context() const;
protected: protected:
ClassOrNamespace *findClass(const FullySpecifiedType &ty, Scope *scope) const; ClassOrNamespace *findClass(const FullySpecifiedType &ty, Scope *scope,
ClassOrNamespace* enclosingTemplateInstantiation = 0) const;
QList<LookupItem> expression(ExpressionAST *ast); QList<LookupItem> expression(ExpressionAST *ast);

View File

@@ -1176,3 +1176,87 @@ void CppToolsPlugin::test_completion_enclosing_template_class_data()
QTest::newRow("case: nested template class with enclosing template class") QTest::newRow("case: nested template class with enclosing template class")
<< code << completions; << code << completions;
} }
void CppToolsPlugin::test_completion_instantiate_nested_class_when_enclosing_is_template()
{
TestData data;
data.srcText = "\n"
"struct Foo \n"
"{\n"
" int foo_i;\n"
"};\n"
"\n"
"template <typename T>\n"
"struct Enclosing\n"
"{\n"
" struct Nested\n"
" {\n"
" T nested_t;\n"
" } nested;\n"
"\n"
" T enclosing_t;\n"
"};\n"
"\n"
"Enclosing<Foo> enclosing;\n"
"@\n"
;
setup(&data);
Utils::ChangeSet change;
QString txt = QLatin1String("enclosing.nested.nested_t.");
change.insert(data.pos, txt);
QTextCursor cursor(data.doc);
change.apply(&cursor);
data.pos += txt.length();
QStringList completions = getCompletions(data);
QCOMPARE(completions.size(), 2);
QVERIFY(completions.contains(QLatin1String("Foo")));
QVERIFY(completions.contains(QLatin1String("foo_i")));
}
void CppToolsPlugin::test_completion_instantiate_nested_of_nested_class_when_enclosing_is_template()
{
TestData data;
data.srcText = "\n"
"struct Foo \n"
"{\n"
" int foo_i;\n"
"};\n"
"\n"
"template <typename T>\n"
"struct Enclosing\n"
"{\n"
" struct Nested\n"
" {\n"
" T nested_t;\n"
" struct NestedNested\n"
" {\n"
" T nestedNested_t;\n"
" } nestedNested;\n"
" } nested;\n"
"\n"
" T enclosing_t;\n"
"};\n"
"\n"
"Enclosing<Foo> enclosing;\n"
"@\n"
;
setup(&data);
Utils::ChangeSet change;
QString txt = QLatin1String("enclosing.nested.nestedNested.nestedNested_t.");
change.insert(data.pos, txt);
QTextCursor cursor(data.doc);
change.apply(&cursor);
data.pos += txt.length();
QStringList completions = getCompletions(data);
QCOMPARE(completions.size(), 2);
QVERIFY(completions.contains(QLatin1String("Foo")));
QVERIFY(completions.contains(QLatin1String("foo_i")));
}

View File

@@ -110,6 +110,8 @@ private slots:
void test_completion_cyclic_inheritance_data(); void test_completion_cyclic_inheritance_data();
void test_completion_enclosing_template_class(); void test_completion_enclosing_template_class();
void test_completion_enclosing_template_class_data(); void test_completion_enclosing_template_class_data();
void test_completion_instantiate_nested_class_when_enclosing_is_template();
void test_completion_instantiate_nested_of_nested_class_when_enclosing_is_template();
private: private:
void test_completion(); void test_completion();