// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "testtemplates.h" #include "testutil.h" #include #include #include #include #include #include #include #include #include using namespace Qt::StringLiterals; void TestTemplates::testTemplateWithNamespace() { const char cppCode[] = R"CPP( template struct QList {}; struct Url { void name(); }; namespace Internet { struct Url{}; struct Bookmarks { QList list(); }; }; )CPP"; const char xmlCode0[] = R"XML( )XML"; QTemporaryFile file; QVERIFY(file.open()); file.write(xmlCode0); file.close(); QString xmlCode1 = QString::fromLatin1(R"XML( )XML").arg(file.fileName()); QScopedPointer builder(TestUtil::parse(cppCode, qPrintable(xmlCode1), false)); QVERIFY(!builder.isNull()); AbstractMetaClassList classes = builder->classes(); AbstractMetaClass* classB = AbstractMetaClass::findClass(classes, u"Bookmarks"); QVERIFY(classB); const auto func = classB->findFunction(u"list"); QVERIFY(!func.isNull()); AbstractMetaType funcType = func->type(); QVERIFY(!funcType.isVoid()); QCOMPARE(funcType.cppSignature(), u"QList"); } void TestTemplates::testTemplateOnContainers() { const char cppCode[] = R"CPP( struct Base {}; template struct QList {}; namespace Namespace { enum SomeEnum { E1, E2 }; template struct A { A foo(const QList >& a); }; typedef A B; } )CPP"; const char xmlCode[] = R"XML( )XML"; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(!builder.isNull()); AbstractMetaClassList classes = builder->classes(); AbstractMetaClass* classB = AbstractMetaClass::findClass(classes, u"B"); QVERIFY(classB); QVERIFY(!classB->baseClass()); QVERIFY(classB->baseClassName().isEmpty()); const auto func = classB->findFunction(u"foo"); QVERIFY(!func.isNull()); AbstractMetaType argType = func->arguments().constFirst().type(); QCOMPARE(argType.instantiations().size(), 1); QCOMPARE(argType.typeEntry()->qualifiedCppName(), u"QList"); const AbstractMetaType &instance1 = argType.instantiations().constFirst(); QCOMPARE(instance1.instantiations().size(), 1); QCOMPARE(instance1.typeEntry()->qualifiedCppName(), u"Namespace::A"); const AbstractMetaType &instance2 = instance1.instantiations().constFirst(); QCOMPARE(instance2.instantiations().size(), 0); QCOMPARE(instance2.typeEntry()->qualifiedCppName(), u"Namespace::E1"); } void TestTemplates::testTemplateValueAsArgument() { const char cppCode[] = R"CPP( template struct List {}; void func(List arg) {} )CPP"; const char xmlCode[] = R"XML( )XML"; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(!builder.isNull()); const auto globalFuncs = builder->globalFunctions(); QCOMPARE(globalFuncs.size(), 1); const auto func = globalFuncs.constFirst(); QCOMPARE(func->minimalSignature(), u"func(List)"); QCOMPARE(func->arguments().constFirst().type().cppSignature(), u"List"); } void TestTemplates::testTemplatePointerAsArgument() { const char cppCode[] = R"CPP( template struct List {}; void func(List* arg) {} )CPP"; const char xmlCode[] = R"XML( )XML"; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(!builder.isNull()); AbstractMetaFunctionCList globalFuncs = builder->globalFunctions(); QCOMPARE(globalFuncs.size(), 1); const auto func = globalFuncs.constFirst(); QCOMPARE(func->minimalSignature(), u"func(List*)"); QCOMPARE(func->arguments().constFirst().type().cppSignature(), u"List *"); } void TestTemplates::testTemplateReferenceAsArgument() { const char cppCode[] = R"CPP( template struct List {}; void func(List& arg) {} )CPP"; const char xmlCode[] = R"XML( )XML"; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(!builder.isNull()); const auto globalFuncs = builder->globalFunctions(); QCOMPARE(globalFuncs.size(), 1); const auto func = globalFuncs.constFirst(); QCOMPARE(func->minimalSignature(), u"func(List&)"); QCOMPARE(func->arguments().constFirst().type().cppSignature(), u"List &"); } void TestTemplates::testTemplateParameterFixup() { const char cppCode[] = R"CPP( template struct List { struct Iterator {}; void append(List l); void erase(List::Iterator it); }; )CPP"; const char xmlCode[] = R"XML( )XML"; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(!builder.isNull()); const AbstractMetaClassList templates = builder->templates(); QCOMPARE(templates.size(), 1); const AbstractMetaClass *list = templates.constFirst(); // Verify that the parameter of "void append(List l)" gets fixed to "List" const auto append = list->findFunction(QStringLiteral("append")); QVERIFY(!append.isNull()); QCOMPARE(append->arguments().size(), 1); QCOMPARE(append->arguments().at(0).type().cppSignature(), u"List"); // Verify that the parameter of "void erase(Iterator)" is not modified const auto erase = list->findFunction(QStringLiteral("erase")); QVERIFY(!erase.isNull()); QCOMPARE(erase->arguments().size(), 1); QEXPECT_FAIL("", "Clang: Some other code changes the parameter type", Abort); QCOMPARE(erase->arguments().at(0).type().cppSignature(), u"List::Iterator"); } void TestTemplates::testInheritanceFromContainterTemplate() { const char cppCode[] = R"CPP( template struct ListContainer { inline void push_front(const T& t); inline T& front(); }; struct FooBar {}; struct FooBars : public ListContainer {}; )CPP"; const char xmlCode[] = R"XML( )XML"; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(!builder.isNull()); AbstractMetaClassList classes = builder->classes(); AbstractMetaClassList templates = builder->templates(); QCOMPARE(classes.size(), 2); QCOMPARE(templates.size(), 1); const AbstractMetaClass* foobars = AbstractMetaClass::findClass(classes, u"FooBars"); QCOMPARE(foobars->functions().size(), 4); const AbstractMetaClass *lc = templates.constFirst(); QCOMPARE(lc->functions().size(), 2); } void TestTemplates::testTemplateInheritanceMixedWithForwardDeclaration() { const char cppCode[] = R"CPP( enum SomeEnum { E1, E2 }; template struct Future; template struct A { A(); void method(); friend struct Future; }; typedef A B; template struct Future {}; )CPP"; const char xmlCode[] = R"XML( )XML"; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(!builder.isNull()); AbstractMetaClassList classes = builder->classes(); AbstractMetaClass* classB = AbstractMetaClass::findClass(classes, u"B"); QVERIFY(classB); QVERIFY(!classB->baseClass()); QVERIFY(classB->baseClassName().isEmpty()); // 3 functions: simple constructor, copy constructor and "method()". QCOMPARE(classB->functions().size(), 3); } void TestTemplates::testTemplateInheritanceMixedWithNamespaceAndForwardDeclaration() { const char cppCode[] = R"CPP( namespace Namespace { enum SomeEnum { E1, E2 }; template struct Future; template struct A { A(); void method(); friend struct Future; }; typedef A B; template struct Future {}; }; )CPP"; const char xmlCode[] = R"XML( )XML"; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(!builder.isNull()); AbstractMetaClassList classes = builder->classes(); AbstractMetaClass* classB = AbstractMetaClass::findClass(classes, u"Namespace::B"); QVERIFY(classB); QVERIFY(!classB->baseClass()); QVERIFY(classB->baseClassName().isEmpty()); // 3 functions: simple constructor, copy constructor and "method()". QCOMPARE(classB->functions().size(), 3); } void TestTemplates::testTypedefOfInstantiationOfTemplateClass() { const char cppCode[] = R"CPP( namespace NSpace { enum ClassType { TypeOne }; template struct BaseTemplateClass { inline ClassType getClassType() const { return CLASS_TYPE; } }; typedef BaseTemplateClass TypeOneClass; } )CPP"; const char xmlCode[] = R"XML( )XML"; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, false)); QVERIFY(!builder.isNull()); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 3); const AbstractMetaClass* base = AbstractMetaClass::findClass(classes, u"BaseTemplateClass"); QVERIFY(base); const AbstractMetaClass* one = AbstractMetaClass::findClass(classes, u"TypeOneClass"); QVERIFY(one); QCOMPARE(one->templateBaseClass(), base); QCOMPARE(one->functions().size(), base->functions().size()); QVERIFY(one->isTypeDef()); const ComplexTypeEntry* oneType = one->typeEntry(); const ComplexTypeEntry* baseType = base->typeEntry(); QCOMPARE(oneType->baseContainerType(), baseType); QCOMPARE(one->baseClassNames(), QStringList(u"BaseTemplateClass"_s)); QVERIFY(one->hasTemplateBaseClassInstantiations()); AbstractMetaTypeList instantiations = one->templateBaseClassInstantiations(); QCOMPARE(instantiations.size(), 1); const AbstractMetaType &inst = instantiations.constFirst(); QVERIFY(!inst.isEnum()); QVERIFY(!inst.typeEntry()->isEnum()); QVERIFY(inst.typeEntry()->isEnumValue()); QCOMPARE(inst.cppSignature(), u"NSpace::TypeOne"); } void TestTemplates::testContainerTypeIncompleteArgument() { const char cppCode[] = R"CPP( template class Vector { void method(const Vector& vector); Vector otherMethod(); }; template void Vector::method(const Vector& vector) {} template Vector Vector::otherMethod() { return Vector(); } typedef Vector IntVector; )CPP"; const char xmlCode[] = R"XML( )XML"; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, true)); QVERIFY(!builder.isNull()); AbstractMetaClassList classes = builder->classes(); QCOMPARE(classes.size(), 1); AbstractMetaClass* vector = AbstractMetaClass::findClass(classes, u"IntVector"); QVERIFY(vector); auto baseContainer = vector->typeEntry()->baseContainerType(); QVERIFY(baseContainer); QCOMPARE(reinterpret_cast(baseContainer)->containerKind(), ContainerTypeEntry::ListContainer); QCOMPARE(vector->functions().size(), 4); const auto method = vector->findFunction(u"method"); QVERIFY(!method.isNull()); QCOMPARE(method->signature(), u"method(const Vector & vector)"); const auto otherMethod = vector->findFunction(u"otherMethod"); QVERIFY(!otherMethod.isNull()); QCOMPARE(otherMethod->signature(), u"otherMethod()"); QVERIFY(!otherMethod->type().isVoid()); QCOMPARE(otherMethod->type().cppSignature(), u"Vector"); } void TestTemplates::testNonTypeTemplates() { // PYSIDe-1296, functions with non type templates parameters. const char cppCode[] = R"CPP( template class Array { T array[Size]; }; Array foo(); )CPP"; const char xmlCode[] = R"XML( )XML"; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, true)); QVERIFY(!builder.isNull()); auto functions = builder->globalFunctions(); QCOMPARE(functions.size(), 1); auto foo = functions.constFirst(); QCOMPARE(foo->name(), u"foo"); QCOMPARE(foo->type().name(), u"Array"); } // Perform checks on template inheritance; a typedef of a template class // should result in rewritten types. void TestTemplates::testTemplateTypeDefs_data() { QTest::addColumn("cpp"); QTest::addColumn("xml"); const char optionalClassDef[] = R"CPP( template // Some value type similar to std::optional class Optional { public: T value() const { return m_value; } operator bool() const { return m_success; } T m_value; bool m_success = false; }; )CPP"; const char xmlPrefix[] = R"XML( )XML"; const char xmlOptionalDecl[] = "\n"; const char xmlOptionalIntDecl[] = "\n"; const char xmlPostFix[] = "\n"; // Flat, global namespace QString cpp; QTextStream(&cpp) << optionalClassDef << "typedef Optional IntOptional;\n"; QString xml; QTextStream(&xml) << xmlPrefix << xmlOptionalDecl << xmlOptionalIntDecl << "" << xmlPostFix; QTest::newRow("global-namespace") << cpp << xml; // Typedef from namespace Std cpp.clear(); QTextStream(&cpp) << "namespace Std {\n" << optionalClassDef << "}\n" << "typedef Std::Optional IntOptional;\n"; xml.clear(); QTextStream(&xml) << xmlPrefix << "\n" << xmlOptionalDecl << "\n" << xmlOptionalIntDecl << "" << xmlPostFix; QTest::newRow("namespace-Std") << cpp << xml; // Typedef from nested class cpp.clear(); QTextStream(&cpp) << "class Outer {\npublic:\n" << optionalClassDef << "\n};\n" << "typedef Outer::Optional IntOptional;\n"; xml.clear(); QTextStream(&xml) << xmlPrefix << "\n" << xmlOptionalDecl << "\n" << xmlOptionalIntDecl << "" << xmlPostFix; QTest::newRow("nested-class") << cpp << xml; } void TestTemplates::testTemplateTypeDefs() { QFETCH(QString, cpp); QFETCH(QString, xml); const QByteArray cppBa = cpp.toLocal8Bit(); const QByteArray xmlBa = xml.toLocal8Bit(); QScopedPointer builder(TestUtil::parse(cppBa.constData(), xmlBa.constData(), true)); QVERIFY(!builder.isNull()); AbstractMetaClassList classes = builder->classes(); const AbstractMetaClass *optional = AbstractMetaClass::findClass(classes, u"Optional"); QVERIFY(optional); // Find the typedef'ed class const AbstractMetaClass *optionalInt = AbstractMetaClass::findClass(classes, u"IntOptional"); QVERIFY(optionalInt); QCOMPARE(optionalInt->templateBaseClass(), optional); // Find the class typedef'ed in the typesystem XML const AbstractMetaClass *xmlOptionalInt = AbstractMetaClass::findClass(classes, u"XmlIntOptional"); QVERIFY(xmlOptionalInt); QCOMPARE(xmlOptionalInt->templateBaseClass(), optional); // Check whether the value() method now has an 'int' return const auto valueMethod = optionalInt->findFunction(u"value"); QVERIFY(!valueMethod.isNull()); QCOMPARE(valueMethod->type().cppSignature(), u"int"); // ditto for typesystem XML const auto xmlValueMethod = xmlOptionalInt->findFunction(u"value"); QVERIFY(!xmlValueMethod.isNull()); QCOMPARE(xmlValueMethod->type().cppSignature(), u"int"); // Check whether the m_value field is of type 'int' const auto valueField = optionalInt->findField(u"m_value"); QVERIFY(valueField.has_value()); QCOMPARE(valueField->type().cppSignature(), u"int"); // ditto for typesystem XML const auto xmlValueField = xmlOptionalInt->findField(u"m_value"); QVERIFY(xmlValueField.has_value()); QCOMPARE(xmlValueField->type().cppSignature(), u"int"); } void TestTemplates::testTemplateTypeAliases() { // Model Qt 6's "template using QList = QVector" const char cppCode[] = R"CPP( template class Container1 { }; template using Container2 = Container1; class Test { public: Container2 m_intContainer; }; class Derived : public Container2 { public: }; )CPP"; const char xmlCode[] = R"XML( )XML"; QScopedPointer builder(TestUtil::parse(cppCode, xmlCode, true)); QVERIFY(!builder.isNull()); AbstractMetaClassList classes = builder->classes(); auto testClass = AbstractMetaClass::findClass(classes, u"Test"); QVERIFY(testClass); auto fields = testClass->fields(); QCOMPARE(fields.size(), 1); auto fieldType = testClass->fields().at(0).type(); QCOMPARE(fieldType.name(), u"Container1"); QCOMPARE(fieldType.instantiations().size(), 1); auto derived = AbstractMetaClass::findClass(classes, u"Derived"); QVERIFY(derived); auto base = derived->templateBaseClass(); QCOMPARE(base->name(), u"Container1"); } QTEST_APPLESS_MAIN(TestTemplates)