Commit 68d6a762 authored by Przemyslaw Gorszkowski's avatar Przemyslaw Gorszkowski Committed by Erik Verbruggen

C++: add support for local types

This change addes support for class, enum definition inside blocks({}) or
functions, e.g.:
void f()
{
	struct S
	{
		int bar;
	};
	S s;
	s.bar;
}

It fixes:
* code completion
* highlighting
* follow symbol
* marking
* find usages

It fixes also problem with namespace aliases inside blocks or functions.

This change can have also impact on performance(there are additional processing)

Task-number: QTCREATORBUG-166 (namespace aliases inside function/block)
Task-number: QTCREATORBUG-3620
Task-number: QTCREATORBUG-6013
Task-number: QTCREATORBUG-8020
Change-Id: Iaea6c6dfe276f1d7b2279b50bdd2e68e375d31eb
Reviewed-by: default avatarErik Verbruggen <erik.verbruggen@digia.com>
parent 2bc24b7a
......@@ -348,7 +348,17 @@ ClassOrNamespace *LookupContext::lookupType(const Name *name, Scope *scope,
}
}
}
return lookupType(name, scope->enclosingScope());
// try to find it in block (rare case but has priority before enclosing scope)
// e.g.: void foo() { struct S {}; S s; }
if (ClassOrNamespace *b = bindings()->lookupType(scope, enclosingTemplateInstantiation)) {
if (ClassOrNamespace *classOrNamespaceNestedInNestedBlock = b->lookupType(name, block))
return classOrNamespaceNestedInNestedBlock;
}
// try to find type in enclosing scope(typical case)
if (ClassOrNamespace *found = lookupType(name, scope->enclosingScope()))
return found;
} else if (ClassOrNamespace *b = bindings()->lookupType(scope, enclosingTemplateInstantiation)) {
return b->lookupType(name);
}
......@@ -397,6 +407,15 @@ QList<LookupItem> LookupContext::lookup(const Name *name, Scope *scope) const
if (! candidates.isEmpty())
return candidates;
if (ClassOrNamespace *binding = bindings()->lookupType(scope)) {
if (ClassOrNamespace *block = binding->findBlock(scope->asBlock())) {
candidates = block->find(name);
if (! candidates.isEmpty())
return candidates;
}
}
} else if (Function *fun = scope->asFunction()) {
bindings()->lookupInScope(name, fun, &candidates, /*templateId = */ 0, /*binding=*/ 0);
......@@ -455,6 +474,17 @@ QList<LookupItem> LookupContext::lookup(const Name *name, Scope *scope) const
if (! candidates.isEmpty())
return candidates;
// the scope can be defined inside a block, try to find it
if (Block *block = scope->enclosingBlock()) {
if (ClassOrNamespace *b = bindings()->lookupType(block)) {
if (ClassOrNamespace *classOrNamespaceNestedInNestedBlock = b->lookupType(scope->name(), block))
candidates = classOrNamespaceNestedInNestedBlock->find(name);
}
}
if (! candidates.isEmpty())
return candidates;
} else if (scope->isObjCClass() || scope->isObjCProtocol()) {
if (ClassOrNamespace *binding = bindings()->lookupType(scope))
candidates = binding->find(name);
......@@ -723,12 +753,54 @@ ClassOrNamespace *ClassOrNamespace::lookupType(const Name *name)
return lookupType_helper(name, &processed, /*searchInEnclosingScope =*/ true, this);
}
ClassOrNamespace *ClassOrNamespace::lookupType(const Name *name, Block *block)
{
flush();
QHash<Block *, ClassOrNamespace *>::const_iterator citBlock = _blocks.find(block);
if (citBlock != _blocks.end()) {
ClassOrNamespace *nestedBlock = citBlock.value();
QSet<ClassOrNamespace *> processed;
if (ClassOrNamespace *foundInNestedBlock
= nestedBlock->lookupType_helper(name,
&processed,
/*searchInEnclosingScope = */ true,
this)) {
return foundInNestedBlock;
}
}
for (citBlock = _blocks.begin(); citBlock != _blocks.end(); ++citBlock) {
if (ClassOrNamespace *foundNestedBlock = citBlock.value()->lookupType(name, block))
return foundNestedBlock;
}
return 0;
}
ClassOrNamespace *ClassOrNamespace::findType(const Name *name)
{
QSet<ClassOrNamespace *> processed;
return lookupType_helper(name, &processed, /*searchInEnclosingScope =*/ false, this);
}
ClassOrNamespace *ClassOrNamespace::findBlock(Block *block)
{
flush();
QHash<Block *, ClassOrNamespace *>::const_iterator citBlock = _blocks.find(block);
if (citBlock != _blocks.end()) {
return citBlock.value();
}
for (citBlock = _blocks.begin(); citBlock != _blocks.end(); ++citBlock) {
if (ClassOrNamespace *foundNestedBlock = citBlock.value()->findBlock(block))
return foundNestedBlock;
}
return 0;
}
Symbol *ClassOrNamespace::lookupInScope(const QList<const Name *> &fullName)
{
if (!_scopeLookupCache) {
......@@ -1490,8 +1562,46 @@ bool CreateBindings::visit(Declaration *decl)
return false;
}
bool CreateBindings::visit(Function *)
bool CreateBindings::visit(Function *function)
{
for (unsigned i = 0, count = function->memberCount(); i < count; ++i) {
Symbol *s = function->memberAt(i);
if (Block *b = s->asBlock())
visit(b);
}
return false;
}
bool CreateBindings::visit(Block *block)
{
ClassOrNamespace *previous = _currentClassOrNamespace;
ClassOrNamespace *binding = new ClassOrNamespace(this, previous);
binding->_control = control();
_currentClassOrNamespace = binding;
_currentClassOrNamespace->addSymbol(block);
for (unsigned i = 0; i < block->memberCount(); ++i)
// we cannot use lazy processing here, because we have to know
// does this block contain any other blocks or classOrNamespaces
process(block->memberAt(i), _currentClassOrNamespace);
// we add this block to parent ClassOrNamespace only if it contains
// any nested ClassOrNamespaces or other blocks(which have to contain
// nested ClassOrNamespaces)
if (! _currentClassOrNamespace->_blocks.empty()
|| ! _currentClassOrNamespace->_classOrNamespaces.empty()
|| ! _currentClassOrNamespace->_enums.empty()) {
previous->_blocks[block] = binding;
_entities.append(binding);
} else {
delete binding;
binding = 0;
}
_currentClassOrNamespace = previous;
return false;
}
......
......@@ -81,7 +81,9 @@ public:
QList<LookupItem> find(const Name *name);
ClassOrNamespace *lookupType(const Name *name);
ClassOrNamespace *lookupType(const Name *name, Block *block);
ClassOrNamespace *findType(const Name *name);
ClassOrNamespace *findBlock(Block *block);
Symbol *lookupInScope(const QList<const Name *> &fullName);
......@@ -126,6 +128,7 @@ private:
QList<Symbol *> _symbols;
QList<ClassOrNamespace *> _usings;
Table _classOrNamespaces;
QHash<Block *, ClassOrNamespace *> _blocks;
QList<Enum *> _enums;
QList<Symbol *> _todo;
QSharedPointer<Control> _control;
......@@ -238,7 +241,9 @@ protected:
virtual bool visit(ForwardClassDeclaration *klass);
virtual bool visit(Enum *e);
virtual bool visit(Declaration *decl);
virtual bool visit(Function *);
virtual bool visit(Function *function);
virtual bool visit(Block *block);
virtual bool visit(BaseClass *b);
virtual bool visit(UsingNamespaceDirective *u);
virtual bool visit(UsingDeclaration *u);
......
......@@ -2463,3 +2463,230 @@ void CppToolsPlugin::test_completion_recursive_using_typedef_declarations()
QCOMPARE(completions.size(), 0);
}
void CppToolsPlugin::test_completion_class_declaration_inside_function_or_block_QTCREATORBUG3620()
{
test_completion();
}
void CppToolsPlugin::test_completion_class_declaration_inside_function_or_block_QTCREATORBUG3620_data()
{
QTest::addColumn<QByteArray>("code");
QTest::addColumn<QStringList>("expectedCompletions");
QByteArray code;
QStringList completions;
code = "\n"
"void foo()\n"
"{\n"
" struct C { int m; };\n"
" C c;\n"
" @\n"
" // padding so we get the scope right\n"
"}\n";
completions.append(QLatin1String("C"));
completions.append(QLatin1String("m"));
QTest::newRow("case: class definition inside function")
<< code << completions;
completions.clear();
code = "\n"
"void foo()\n"
"{\n"
" {\n"
" struct C { int m; };\n"
" C c;\n"
" @\n"
" // padding so we get the scope right\n"
" }\n"
"}\n"
;
completions.append(QLatin1String("C"));
completions.append(QLatin1String("m"));
QTest::newRow("case: class definition inside block inside function")
<< code << completions;
completions.clear();
code = "\n"
"void foo()\n"
"{\n"
" {\n"
" struct C { int m1; };\n"
" }\n"
" {\n"
" struct C { int m2; };\n"
" C c;\n"
" @\n"
" // padding so we get the scope right\n"
" }\n"
"}\n"
;
completions.append(QLatin1String("C"));
completions.append(QLatin1String("m2"));
QTest::newRow("case: class definition with the same name inside different block inside function")
<< code << completions;
completions.clear();
}
void CppToolsPlugin::test_completion_namespace_alias_inside_function_or_block_QTCREATORBUG166()
{
test_completion();
}
void CppToolsPlugin::test_completion_namespace_alias_inside_function_or_block_QTCREATORBUG166_data()
{
QTest::addColumn<QByteArray>("code");
QTest::addColumn<QStringList>("expectedCompletions");
QByteArray code;
QStringList completions;
code = "\n"
"namespace NS1\n"
"{\n"
"namespace NS2\n"
"{\n"
" struct C\n"
" {\n"
" int m;\n"
" };\n"
"}\n"
"void foo()\n"
"{\n"
" namespace NS = NS1::NS2;\n"
" NS::C c;\n"
" @\n"
" // padding so we get the scope right\n"
"}\n"
;
completions.append(QLatin1String("C"));
completions.append(QLatin1String("m"));
QTest::newRow("case: namespace alias inside function")
<< code << completions;
completions.clear();
code = "\n"
"namespace NS1\n"
"{\n"
"namespace NS2\n"
"{\n"
" struct C\n"
" {\n"
" int m;\n"
" };\n"
"}\n"
"void foo()\n"
"{\n"
" {\n"
" namespace NS = NS1::NS2;\n"
" NS::C c;\n"
" @\n"
" // padding so we get the scope right\n"
" }\n"
"}\n"
;
completions.append(QLatin1String("C"));
completions.append(QLatin1String("m"));
QTest::newRow("case: namespace alias inside block inside function")
<< code << completions;
completions.clear();
}
void CppToolsPlugin::test_completion_class_declaration_inside_function_or_block_QTCREATORBUG3620_static_member()
{
TestData data;
data.srcText =
"void foo()\n"
"{\n"
" {\n"
" struct C { static void staticFun1(); int m1; };\n"
" }\n"
" {\n"
" struct C { static void staticFun2(); int m2; };\n"
" @\n"
" // padding so we get the scope right\n"
" }\n"
"}\n"
;
setup(&data);
Utils::ChangeSet change;
QString txt = QLatin1String("C::");
change.insert(data.pos, txt);
QTextCursor cursor(data.doc);
change.apply(&cursor);
data.pos += txt.length();
QStringList completions = getCompletions(data);
QCOMPARE(completions.size(), 3);
QVERIFY(completions.contains(QLatin1String("C")));
QVERIFY(completions.contains(QLatin1String("staticFun2")));
QVERIFY(completions.contains(QLatin1String("m2")));
}
void CppToolsPlugin::test_completion_enum_inside_block_inside_function_QTCREATORBUG5456()
{
TestData data;
data.srcText =
"void foo()\n"
"{\n"
" {\n"
" enum E { e1, e2, e3 };\n"
" @\n"
" // padding so we get the scope right\n"
" }\n"
"}\n"
;
setup(&data);
Utils::ChangeSet change;
QString txt = QLatin1String("E::");
change.insert(data.pos, txt);
QTextCursor cursor(data.doc);
change.apply(&cursor);
data.pos += txt.length();
QStringList completions = getCompletions(data);
QCOMPARE(completions.size(), 4);
QVERIFY(completions.contains(QLatin1String("E")));
QVERIFY(completions.contains(QLatin1String("e1")));
QVERIFY(completions.contains(QLatin1String("e2")));
QVERIFY(completions.contains(QLatin1String("e3")));
}
void CppToolsPlugin::test_completion_enum_inside_function_QTCREATORBUG5456()
{
TestData data;
data.srcText =
"void foo()\n"
"{\n"
" enum E { e1, e2, e3 };\n"
" @\n"
" // padding so we get the scope right\n"
"}\n"
;
setup(&data);
Utils::ChangeSet change;
QString txt = QLatin1String("E::");
change.insert(data.pos, txt);
QTextCursor cursor(data.doc);
change.apply(&cursor);
data.pos += txt.length();
QStringList completions = getCompletions(data);
QCOMPARE(completions.size(), 4);
QVERIFY(completions.contains(QLatin1String("E")));
QVERIFY(completions.contains(QLatin1String("e1")));
QVERIFY(completions.contains(QLatin1String("e2")));
QVERIFY(completions.contains(QLatin1String("e3")));
}
......@@ -1430,6 +1430,14 @@ bool CppCompletionAssistProcessor::completeScope(const QList<CPlusPlus::LookupIt
break;
}
// it can be class defined inside a block
if (classTy->enclosingScope()->isBlock()) {
if (ClassOrNamespace *b = context.lookupType(classTy->name(), classTy->enclosingScope())) {
completeClass(b);
break;
}
}
} else if (Namespace *nsTy = ty->asNamespaceType()) {
if (ClassOrNamespace *b = context.lookupType(nsTy)) {
completeNamespace(b);
......@@ -1445,10 +1453,22 @@ bool CppCompletionAssistProcessor::completeScope(const QList<CPlusPlus::LookupIt
}
} else if (Enum *e = ty->asEnumType()) {
// it can be class defined inside a block
if (e->enclosingScope()->isBlock()) {
if (ClassOrNamespace *b = context.lookupType(e)) {
Block *block = e->enclosingScope()->asBlock();
if (ClassOrNamespace *bb = b->findBlock(block)) {
completeNamespace(bb);
break;
}
}
}
if (ClassOrNamespace *b = context.lookupType(e)) {
completeNamespace(b);
break;
}
}
}
......
......@@ -148,6 +148,14 @@ private slots:
void test_completion_recursive_using_declarations2();
void test_completion_recursive_using_typedef_declarations();
void test_completion_class_declaration_inside_function_or_block_QTCREATORBUG3620();
void test_completion_class_declaration_inside_function_or_block_QTCREATORBUG3620_data();
void test_completion_namespace_alias_inside_function_or_block_QTCREATORBUG166();
void test_completion_namespace_alias_inside_function_or_block_QTCREATORBUG166_data();
void test_completion_class_declaration_inside_function_or_block_QTCREATORBUG3620_static_member();
void test_completion_enum_inside_block_inside_function_QTCREATORBUG5456();
void test_completion_enum_inside_function_QTCREATORBUG5456();
void test_format_pointerdeclaration_in_simpledeclarations();
void test_format_pointerdeclaration_in_simpledeclarations_data();
void test_format_pointerdeclaration_in_controlflowstatements();
......
......@@ -199,6 +199,8 @@ private slots:
void test_checksymbols_crashWhenUsingNamespaceClass_QTCREATORBUG9323_namespace();
void test_checksymbols_crashWhenUsingNamespaceClass_QTCREATORBUG9323_insideFunction();
void test_alias_decl_QTCREATORBUG9386();
void test_completion_enum_inside_block_inside_function_QTCREATORBUG5456();
void test_completion_enum_inside_function_QTCREATORBUG5456();
};
void tst_CheckSymbols::test_checksymbols_TypeUse()
......@@ -1768,5 +1770,56 @@ void tst_CheckSymbols::test_checksymbols_highlightingUsedTemplateFunctionParamet
TestData::check(source, expectedUses);
}
void tst_CheckSymbols::test_completion_enum_inside_block_inside_function_QTCREATORBUG5456()
{
const QByteArray source =
"void foo()\n"
"{\n"
" {\n"
" enum E { e1, e2, e3 };\n"
" E e = e1;\n"
" }\n"
"}\n"
;
const QList<Use> expectedUses = QList<Use>()
<< Use(1, 6, 3, CppHighlightingSupport::FunctionUse)
<< Use(4, 13, 1, CppHighlightingSupport::TypeUse)
<< Use(4, 17, 2, CppHighlightingSupport::EnumerationUse)
<< Use(4, 21, 2, CppHighlightingSupport::EnumerationUse)
<< Use(4, 25, 2, CppHighlightingSupport::EnumerationUse)
<< Use(5, 8, 1, CppHighlightingSupport::TypeUse)
<< Use(5, 10, 1, CppHighlightingSupport::LocalUse)
<< Use(5, 14, 2, CppHighlightingSupport::EnumerationUse)
;
TestData::check(source, expectedUses);
}
void tst_CheckSymbols::test_completion_enum_inside_function_QTCREATORBUG5456()
{
const QByteArray source =
"void foo()\n"
"{\n"
" enum E { e1, e2, e3 };\n"
" E e = e1;\n"
"}\n"
;
const QList<Use> expectedUses = QList<Use>()
<< Use(1, 6, 3, CppHighlightingSupport::FunctionUse)
<< Use(3, 9, 1, CppHighlightingSupport::TypeUse)
<< Use(3, 13, 2, CppHighlightingSupport::EnumerationUse)
<< Use(3, 17, 2, CppHighlightingSupport::EnumerationUse)
<< Use(3, 21, 2, CppHighlightingSupport::EnumerationUse)
<< Use(4, 4, 1, CppHighlightingSupport::TypeUse)
<< Use(4, 6, 1, CppHighlightingSupport::LocalUse)
<< Use(4, 10, 2, CppHighlightingSupport::EnumerationUse)
;
TestData::check(source, expectedUses);
}
QTEST_APPLESS_MAIN(tst_CheckSymbols)
#include "tst_checksymbols.moc"
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