moc: generate the uint data using the new constexpr functions

We needed to go through two levels of indirection to generate it for
classes. First, it needs to be inside the context of the class or
namespace in question, hence the need for a qt_create_metaobjectdata()
member function. This is necessary because moc does not know what
context some property types are, like "Error": they may be a nested type
in the class or local to this namespace. Doing this also gains us access
to everything the class has access to. (The metatype array avoided this
problem by declaring the types as template parameters in the context of
the staticMetaObject, so it transitively gained the same access we now
do)

Second, because the class itself may be private, we need a way to call
the function we've just defined without naming it, so we insert a
template friend in Q_OBJECT & Q_GADGET, which we can define in the moc
output. We multiplex it using the 1:1 association with the token type
that moc already defines.

The member function is a template so no compiler will attempt to export
it. Moreover, our specialization is keyed to a type in an unnamed
namespace.

The changes to tst_QVariant are temporary, because it is abusing the
Q_ENUM marker on a 64-bit enumeration.

Task-number: QTBUG-111926
Change-Id: I8a96935cf6c742259c9dfffd17e951563adde9a1
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Thiago Macieira 2024-08-10 15:31:43 -07:00
parent 3e6eb30a80
commit da4a6cf78f
8 changed files with 312 additions and 14 deletions

View File

@ -14,6 +14,10 @@
#include <QtCore/qtcoreexports.h>
#include <QtCore/qtmetamacros.h>
// this function is implemented by moc for the user classes and thus
// intentionally outside of our namespace
template <typename T> constexpr auto qt_call_create_metaobjectdata();
QT_BEGIN_NAMESPACE
class QByteArray;

View File

@ -130,6 +130,8 @@ private: \
Q_OBJECT_NO_ATTRIBUTES_WARNING \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
QT_WARNING_POP \
template <typename> static constexpr auto qt_create_metaobjectdata(); \
template <typename> friend constexpr auto ::qt_call_create_metaobjectdata(); \
QT_DEFINE_TAG_STRUCT(QPrivateSignal); \
QT_ANNOTATE_CLASS(qt_qobject, "")
@ -147,6 +149,8 @@ private: \
QT_WARNING_PUSH \
Q_OBJECT_NO_ATTRIBUTES_WARNING \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
template <typename> static constexpr auto qt_create_metaobjectdata(); \
template <typename> friend constexpr auto ::qt_call_create_metaobjectdata(); \
QT_WARNING_POP \
QT_ANNOTATE_CLASS(qt_qgadget, "") \
/*end*/
@ -157,6 +161,7 @@ private: \
/* qmake ignore Q_NAMESPACE_EXPORT */
#define Q_NAMESPACE_EXPORT(...) \
extern __VA_ARGS__ const QMetaObject staticMetaObject; \
template <typename> static constexpr auto qt_create_metaobjectdata(); \
QT_ANNOTATE_CLASS(qt_qnamespace, "") \
/*end*/

View File

@ -50,6 +50,7 @@ enum PropertyFlags : uint {
Required = 0x01000000,
Bindable = 0x02000000,
};
inline constexpr PropertyFlags DefaultPropertyFlags { Readable | Designable | Scriptable | Stored };
enum MethodFlags : uint {
AccessPrivate = 0x00,

View File

@ -286,6 +286,89 @@ void Generator::generateCode()
// build the data array
//
int initialMetaTypeOffset = 0;
// We define a method inside the context of the class or namespace we're
// creating the meta object for, so we get access to everything it has
// access to and with the same contexts (for example, member enums and
// types).
fprintf(out, "#ifdef QT_MOC_HAS_UINTDATA\n"
"template <> constexpr inline auto %s::qt_create_metaobjectdata<qt_meta_tag_%s_t>()\n"
"{\n"
" namespace QMC = QtMocConstants;\n"
" QtMocHelpers::UintData qt_properties {\n",
cdef->qualified.constData(), qualifiedClassNameIdentifier.constData());
addProperties(initialMetaTypeOffset);
fprintf(out, " };\n"
" QtMocHelpers::UintData qt_enums {\n");
addEnums(initialMetaTypeOffset);
fprintf(out, " };\n"
" QtMocHelpers::UintData qt_methods {\n");
// we insert the metatype for the class itself here
++initialMetaTypeOffset;
// Build signals array first, otherwise the signal indices would be wrong
addFunctions(cdef->signalList, "Signal", initialMetaTypeOffset);
addFunctions(cdef->slotList, "Slot", initialMetaTypeOffset);
addFunctions(cdef->methodList, "Method", initialMetaTypeOffset);
fprintf(out, " };\n");
const char *uintDataParams = "";
if (isConstructible || !cdef->classInfoList.isEmpty()) {
if (isConstructible) {
fprintf(out, " using Constructor = QtMocHelpers::NoType;\n"
" QtMocHelpers::UintData qt_constructors {\n");
addFunctions(cdef->constructorList, "Constructor", initialMetaTypeOffset);
fprintf(out, " };\n");
} else {
fputs(" QtMocHelpers::UintData qt_constructors {};\n", out);
}
uintDataParams = ", qt_constructors";
if (!cdef->classInfoList.isEmpty()) {
fprintf(out, " QtMocHelpers::ClassInfos qt_classinfo({\n");
addClassInfos();
fprintf(out, " });\n");
uintDataParams = ", qt_constructors, qt_classinfo";
}
}
const char *metaObjectFlags = "QMC::MetaObjectFlag{}";
if (cdef->hasQGadget || cdef->hasQNamespace) {
// Ideally, all the classes could have that flag. But this broke
// classes generated by qdbusxml2cpp which generate code that require
// that we call qt_metacall for properties.
metaObjectFlags = "QMC::PropertyAccessInStaticMetaCall";
}
fprintf(out, " return QtMocHelpers::metaObjectData(%s, qt_methods, qt_properties, qt_enums%s);\n"
"}\n", metaObjectFlags, uintDataParams);
if (cdef->hasQNamespace) {
// We can always access the function above if it's at namespace scope.
fprintf(out, "static constexpr auto qt_meta_data_%s_array =\n"
" %s::qt_create_metaobjectdata<qt_meta_tag_%s_t>().data;\n",
qualifiedClassNameIdentifier.constData(), cdef->qualified.constData(),
qualifiedClassNameIdentifier.constData());
} else {
// If this is a class, it might itself be private, so we need an extra
// level of indirection to access the function above.
fprintf(out, "template <> constexpr inline auto qt_call_create_metaobjectdata<qt_meta_tag_%s_t>()\n"
"{\n"
" return %s::qt_create_metaobjectdata<qt_meta_tag_%s_t>();\n"
"}\n"
"static constexpr auto qt_meta_data_%s_array =\n"
" qt_call_create_metaobjectdata<qt_meta_tag_%s_t>().data;\n",
qualifiedClassNameIdentifier.constData(), cdef->qualified.constData(),
qualifiedClassNameIdentifier.constData(), qualifiedClassNameIdentifier.constData(),
qualifiedClassNameIdentifier.constData());
}
fprintf(out, "static constexpr const uint *qt_meta_data_%s =\n"
" qt_meta_data_%s_array.data();\n"
"#else // !QT_MOC_HAS_UINTDATA\n",
qualifiedClassNameIdentifier.constData(), qualifiedClassNameIdentifier.constData());
int index = MetaObjectPrivateFieldCount;
fprintf(out, "Q_CONSTINIT static const uint qt_meta_data_%s[] = {\n", qualifiedClassNameIdentifier.constData());
fprintf(out, "\n // content:\n");
@ -347,7 +430,7 @@ void Generator::generateCode()
|| propEnumCount >= std::numeric_limits<int>::max()) {
parser->error("internal limit exceeded: number of property and enum metatypes is too big.");
}
int initialMetaTypeOffset = int(propEnumCount);
initialMetaTypeOffset = int(propEnumCount);
//
// Build signals array first, otherwise the signal indices would be wrong
@ -401,7 +484,9 @@ void Generator::generateCode()
//
// Terminate data array
//
fprintf(out, "\n 0 // eod\n};\n\n");
fprintf(out, "\n 0 // eod\n};\n");
fprintf(out, "#endif // !QT_MOC_HAS_UINTDATA\n\n");
//
// Build extra array
@ -672,6 +757,12 @@ void Generator::registerClassInfoStrings()
}
}
void Generator::addClassInfos()
{
for (const ClassInfoDef &c : std::as_const(cdef->classInfoList))
fprintf(out, " { %4d, %4d },\n", stridx(c.name), stridx(c.value));
}
void Generator::generateClassInfos()
{
if (cdef->classInfoList.isEmpty())
@ -705,6 +796,76 @@ void Generator::registerByteArrayVector(const QList<QByteArray> &list)
strreg(ba);
}
void Generator::addFunctions(const QList<FunctionDef> &list, const char *functype,
int &initialMetatypeOffset)
{
for (const FunctionDef &f : list) {
if (!f.isConstructor)
fprintf(out, " // %s '%s'\n", functype, f.name.constData());
fprintf(out, " QtMocHelpers::%s%sData<",
f.revision > 0 ? "Revisioned" : "", functype);
if (f.isConstructor)
fprintf(out, "Constructor(");
else
fprintf(out, "%s(", f.type.name.constData()); // return type
const char *comma = "";
for (const auto &argument : f.arguments) {
fprintf(out, "%s%s", comma, argument.type.name.constData());
comma = ", ";
}
fprintf(out, ")%s>(%d, %d, %d, ", f.isConst ? " const" : "",
stridx(f.name), stridx(f.tag), initialMetatypeOffset);
// flags
// access right is always present
if (f.access == FunctionDef::Private)
fprintf(out, "QMC::AccessPrivate");
else if (f.access == FunctionDef::Public)
fprintf(out, "QMC::AccessPublic");
else if (f.access == FunctionDef::Protected)
fprintf(out, "QMC::AccessProtected");
if (f.isCompat)
fprintf(out, " | QMC::MethodCompatibility");
if (f.wasCloned)
fprintf(out, " | QMC::MethodCloned");
if (f.isScriptable)
fprintf(out, " | QMC::MethodScriptable");
// QtMocConstants::MethodRevisioned is implied by the call we're making
if (f.revision > 0)
fprintf(out, ", %#x", f.revision);
// return type (if not a constructor)
const bool allowEmptyName = f.isConstructor;
fprintf(out, ", ");
generateTypeInfo(f.normalizedType, allowEmptyName);
if (f.arguments.isEmpty()) {
fprintf(out, "),\n");
} else {
// array of parameter types (or type names) and names
fprintf(out, ", {{");
for (qsizetype i = 0; i < f.arguments.size(); ++i) {
if ((i % 4) == 0)
fprintf(out, "\n ");
const ArgumentDef &arg = f.arguments.at(i);
fprintf(out, " { ");
generateTypeInfo(arg.normalizedType);
fprintf(out, ", %d },", stridx(arg.name));
}
fprintf(out, "\n }}),\n");
}
// constructors don't have a return type
if (!f.isConstructor)
++initialMetatypeOffset;
initialMetatypeOffset += int(f.arguments.size());
}
}
void Generator::generateFunctions(const QList<FunctionDef> &list, const char *functype, int type,
int &paramsIndex, int &initialMetatypeOffset)
{
@ -825,6 +986,79 @@ void Generator::registerPropertyStrings()
}
}
void Generator::addProperties(int &initialMetaTypeOffset)
{
for (const PropertyDef &p : std::as_const(cdef->propertyList)) {
fprintf(out, " // property '%s'\n"
" QtMocHelpers::PropertyData(%d, ",
p.name.constData(), stridx(p.name));
generateTypeInfo(p.type);
fputc(',', out);
const char *separator = "";
auto addFlag = [this, &separator](const char *text) {
fprintf(out, "%s QMC::%s", separator, text);
separator = " |";
};
bool readable = !p.read.isEmpty() || !p.member.isEmpty();
bool designable = p.designable != "false";
bool scriptable = p.scriptable != "false";
bool stored = p.stored != "false";
if (readable && designable && scriptable && stored) {
addFlag("DefaultPropertyFlags");
if ((!p.member.isEmpty() && !p.constant) || !p.write.isEmpty())
addFlag("Writable");
} else {
if (readable)
addFlag("Readable");
if ((!p.member.isEmpty() && !p.constant) || !p.write.isEmpty())
addFlag("Writable");
if (designable)
addFlag("Designable");
if (scriptable)
addFlag("Scriptable");
if (stored)
addFlag("Stored");
}
if (!p.reset.isEmpty())
addFlag("Resettable");
if (!isBuiltinType(p.type))
addFlag("EnumOrFlag");
if (p.stdCppSet())
addFlag("StdCppSet");
if (p.constant)
addFlag("Constant");
if (p.final)
addFlag("Final");
if (p.user != "false")
addFlag("User");
if (p.required)
addFlag("Required");
if (!p.bind.isEmpty())
addFlag("Bindable");
if (*separator == '\0')
addFlag("Invalid");
int notifyId = p.notifyId;
if (notifyId != -1 || p.revision > 0) {
fprintf(out, ", ");
if (p.notifyId < -1) {
// signal is in parent class
const int indexInStrings = int(strings.indexOf(p.notify));
notifyId = indexInStrings;
fprintf(out, "%#x | ", IsUnresolvedSignal);
}
fprintf(out, "%d", notifyId);
if (p.revision > 0)
fprintf(out, ", %#x", p.revision);
}
fprintf(out, "),\n");
}
initialMetaTypeOffset += int(cdef->propertyList.size());
}
void Generator::generateProperties()
{
//
@ -895,6 +1129,48 @@ void Generator::registerEnumStrings()
}
}
void Generator::addEnums(int &initialMetaTypeOffset)
{
for (const EnumDef &e : std::as_const(cdef->enumList)) {
const QByteArray &typeName = e.enumName.isNull() ? e.name : e.enumName;
fprintf(out, " // %s '%s'\n"
" QtMocHelpers::EnumData<%s>(%d, %d,",
e.flags & EnumIsFlag ? "flag" : "enum", e.name.constData(),
e.name.constData(), stridx(e.name), stridx(typeName));
if (e.flags) {
const char *separator = "";
auto addFlag = [this, &separator](const char *text) {
fprintf(out, "%s QMC::%s", separator, text);
separator = " |";
};
if (e.flags & EnumIsFlag)
addFlag("EnumIsFlag");
if (e.flags & EnumIsScoped)
addFlag("EnumIsScoped");
} else {
fprintf(out, " QMC::EnumFlags{}");
}
if (e.values.isEmpty()) {
fprintf(out, "),\n");
continue;
}
// add the enumerations
fprintf(out, ").add({\n");
QByteArray prefix = (e.enumName.isNull() ? e.name : e.enumName);
for (const QByteArray &val : e.values) {
fprintf(out, " { %4d, %s::%s },\n", stridx(val),
prefix.constData(), val.constData());
}
fprintf(out, " }),\n");
}
initialMetaTypeOffset += int(cdef->enumList.size());
}
void Generator::generateEnums(int index)
{
if (cdef->enumDeclarations.isEmpty())

View File

@ -29,6 +29,10 @@ private:
void generateClassInfos();
void registerFunctionStrings(const QList<FunctionDef> &list);
void registerByteArrayVector(const QList<QByteArray> &list);
void addProperties(int &initialMetaTypeOffset);
void addEnums(int &initialMetaTypeOffset);
void addFunctions(const QList<FunctionDef> &list, const char *functype, int &initialMetatypeOffset);
void addClassInfos();
void generateFunctions(const QList<FunctionDef> &list, const char *functype, int type,
int &paramsIndex, int &initialMetatypeOffset);
void generateFunctionRevisions(const QList<FunctionDef> &list, const char *functype);

View File

@ -152,12 +152,12 @@ public:
enum MetaEnumTest_Enum0 { MetaEnumTest_Enum0_dummy = 2, MetaEnumTest_Enum0_value = 42, MetaEnsureSignedEnum0 = -1 };
Q_ENUM(MetaEnumTest_Enum0)
enum MetaEnumTest_Enum1 : qint64 { MetaEnumTest_Enum1_value = 42, MetaEnumTest_Enum1_bigValue = (Q_INT64_C(1) << 33) + 50 };
Q_ENUM(MetaEnumTest_Enum1)
// Q_ENUM(MetaEnumTest_Enum1)
enum MetaEnumTest_Enum3 : qint64 { MetaEnumTest_Enum3_value = -47, MetaEnumTest_Enum3_bigValue = (Q_INT64_C(1) << 56) + 5, MetaEnumTest_Enum3_bigNegValue = -(Q_INT64_C(1) << 56) - 3 };
Q_ENUM(MetaEnumTest_Enum3)
// Q_ENUM(MetaEnumTest_Enum3)
enum MetaEnumTest_Enum4 : quint64 { MetaEnumTest_Enum4_value = 47, MetaEnumTest_Enum4_bigValue = (Q_INT64_C(1) << 52) + 45 };
Q_ENUM(MetaEnumTest_Enum4)
// Q_ENUM(MetaEnumTest_Enum4)
enum MetaEnumTest_Enum5 : uint { MetaEnumTest_Enum5_value = 47 };
Q_ENUM(MetaEnumTest_Enum5)
enum MetaEnumTest_Enum6 : uchar { MetaEnumTest_Enum6_value = 47 };
@ -5390,13 +5390,13 @@ void tst_QVariant::metaEnums_data()
QTest::newRow(#Value) << &testVariantMetaEnum<decltype(Value), Value> << #Value;
METAENUMS_TEST(MetaEnumTest_Enum0_value);
METAENUMS_TEST(MetaEnumTest_Enum1_value);
METAENUMS_TEST(MetaEnumTest_Enum1_bigValue);
METAENUMS_TEST(MetaEnumTest_Enum3_value);
METAENUMS_TEST(MetaEnumTest_Enum3_bigValue);
METAENUMS_TEST(MetaEnumTest_Enum3_bigNegValue);
METAENUMS_TEST(MetaEnumTest_Enum4_value);
METAENUMS_TEST(MetaEnumTest_Enum4_bigValue);
// METAENUMS_TEST(MetaEnumTest_Enum1_value);
// METAENUMS_TEST(MetaEnumTest_Enum1_bigValue);
// METAENUMS_TEST(MetaEnumTest_Enum3_value);
// METAENUMS_TEST(MetaEnumTest_Enum3_bigValue);
// METAENUMS_TEST(MetaEnumTest_Enum3_bigNegValue);
// METAENUMS_TEST(MetaEnumTest_Enum4_value);
// METAENUMS_TEST(MetaEnumTest_Enum4_bigValue);
METAENUMS_TEST(MetaEnumTest_Enum5_value);
METAENUMS_TEST(MetaEnumTest_Enum6_value);
METAENUMS_TEST(MetaEnumTest_Enum8_value);

View File

@ -2541,7 +2541,8 @@ void tst_Moc::warnings()
#endif
}
class tst_Moc::PrivateClass : public QObject {
class tst_Moc::PrivateClass : public QObject
{
Q_PROPERTY(int someProperty READ someSlot WRITE someSlot2)
Q_OBJECT
Q_SIGNALS:
@ -2551,6 +2552,13 @@ public Q_SLOTS:
void someSlot2(int) {}
public:
Q_INVOKABLE PrivateClass() {}
#ifdef QT_MOC_HAS_UINTDATA // access to private class' enums was fixed for Qt 6.9
enum SomeEnum {
Value0,
Value1,
};
Q_ENUM(SomeEnum)
#endif
};
void tst_Moc::privateClass()

View File

@ -470,7 +470,7 @@ void tst_MocHelpers::constructorUintData()
QCOMPARE(result.header[1], 1U);
QCOMPARE(result.header[3], 1U);
QCOMPARE(result.header[4], AccessPublic | MethodConstructor);
QCOMPARE(result.header[5], 1U);
QCOMPARE(result.header[5], 0U);
QCOMPARE(result.payload[0], NoType);
QCOMPARE(result.payload[1], uint(QMetaType::QObjectStar));
QCOMPARE(result.payload[2], 2U);