Add support for exposing public QProperty members in the meta-object system

At the moment this makes the type as well as the setter/getter available
through the meta-call as well as the ability to register observers and
bindings. Only QProperty members that are annotated with
Q_PROPERTY(type name) are made public through the meta-object.

Change-Id: I16b98fd318122c722b85ce61e39975284e0c2404
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
bb10
Simon Hausmann 2019-11-08 16:20:44 +01:00
parent b5f6a85d27
commit d4f0445331
9 changed files with 260 additions and 30 deletions

View File

@ -3505,6 +3505,21 @@ bool QMetaProperty::isRequired() const
return flags & Required;
}
/*!
\since 6.0
Returns \c true if the property is implemented using a QProperty member; otherwise returns \c false.
This can be used to detect the availability of QProperty related meta-call types ahead of
performing the call itself.
*/
bool QMetaProperty::isQProperty() const
{
if (!mobj)
return false;
int flags = mobj->d.data[handle + 2];
return flags & IsQProperty;
}
/*!
\obsolete

View File

@ -265,6 +265,7 @@ public:
bool isConstant() const;
bool isFinal() const;
bool isRequired() const;
bool isQProperty() const;
bool isFlagType() const;
bool isEnumType() const;

View File

@ -87,6 +87,7 @@ enum PropertyFlags {
Notify = 0x00400000,
Revisioned = 0x00800000,
Required = 0x01000000,
IsQProperty = 0x02000000
};
enum MethodFlags {

View File

@ -390,7 +390,9 @@ struct Q_CORE_EXPORT QMetaObject
CreateInstance,
IndexOfMethod,
RegisterPropertyMetaType,
RegisterMethodArgumentMetaType
RegisterMethodArgumentMetaType,
RegisterQPropertyObserver,
SetQPropertyBinding
};
int static_metacall(Call, int, void **) const;

View File

@ -869,6 +869,9 @@ void Generator::generateProperties()
if (p.required)
flags |= Required;
if (p.isQProperty)
flags |= IsQProperty;
fprintf(out, " %4d, ", stridx(p.name));
generateTypeInfo(p.type);
fprintf(out, ", 0x%.8x,\n", flags);
@ -1017,7 +1020,9 @@ void Generator::generateMetacall()
fprintf(out, "else ");
fprintf(out,
"if (_c == QMetaObject::ReadProperty || _c == QMetaObject::WriteProperty\n"
" || _c == QMetaObject::ResetProperty || _c == QMetaObject::RegisterPropertyMetaType) {\n"
" || _c == QMetaObject::ResetProperty || _c == QMetaObject::RegisterPropertyMetaType\n"
" || _c == QMetaObject::RegisterQPropertyObserver\n"
" || _c == QMetaObject::SetQPropertyBinding) {\n"
" qt_static_metacall(this, _c, _id, _a);\n"
" _id -= %d;\n }", cdef->propertyList.count());
@ -1354,6 +1359,7 @@ void Generator::generateStaticMetacall()
bool needTempVarForGet = false;
bool needSet = false;
bool needReset = false;
bool haveQProperties = false;
for (int i = 0; i < cdef->propertyList.size(); ++i) {
const PropertyDef &p = cdef->propertyList.at(i);
needGet |= !p.read.isEmpty() || !p.member.isEmpty();
@ -1363,13 +1369,15 @@ void Generator::generateStaticMetacall()
needSet |= !p.write.isEmpty() || (!p.member.isEmpty() && !p.constant);
needReset |= !p.reset.isEmpty();
haveQProperties |= p.isQProperty;
}
fprintf(out, "\n#ifndef QT_NO_PROPERTIES\n ");
if (needElse)
fprintf(out, "else ");
fprintf(out, "if (_c == QMetaObject::ReadProperty) {\n");
if (needGet) {
auto setupMemberAccess = [this]() {
if (cdef->hasQObject) {
#ifndef QT_NO_DEBUG
fprintf(out, " Q_ASSERT(staticMetaObject.cast(_o));\n");
@ -1379,6 +1387,10 @@ void Generator::generateStaticMetacall()
fprintf(out, " auto *_t = reinterpret_cast<%s *>(_o);\n", cdef->classname.constData());
}
fprintf(out, " Q_UNUSED(_t)\n");
};
if (needGet) {
setupMemberAccess();
if (needTempVarForGet)
fprintf(out, " void *_v = _a[0];\n");
fprintf(out, " switch (_id) {\n");
@ -1416,15 +1428,7 @@ void Generator::generateStaticMetacall()
fprintf(out, "if (_c == QMetaObject::WriteProperty) {\n");
if (needSet) {
if (cdef->hasQObject) {
#ifndef QT_NO_DEBUG
fprintf(out, " Q_ASSERT(staticMetaObject.cast(_o));\n");
#endif
fprintf(out, " auto *_t = static_cast<%s *>(_o);\n", cdef->classname.constData());
} else {
fprintf(out, " auto *_t = reinterpret_cast<%s *>(_o);\n", cdef->classname.constData());
}
fprintf(out, " Q_UNUSED(_t)\n");
setupMemberAccess();
fprintf(out, " void *_v = _a[0];\n");
fprintf(out, " switch (_id) {\n");
for (int propindex = 0; propindex < cdef->propertyList.size(); ++propindex) {
@ -1472,15 +1476,7 @@ void Generator::generateStaticMetacall()
fprintf(out, " else ");
fprintf(out, "if (_c == QMetaObject::ResetProperty) {\n");
if (needReset) {
if (cdef->hasQObject) {
#ifndef QT_NO_DEBUG
fprintf(out, " Q_ASSERT(staticMetaObject.cast(_o));\n");
#endif
fprintf(out, " %s *_t = static_cast<%s *>(_o);\n", cdef->classname.constData(), cdef->classname.constData());
} else {
fprintf(out, " %s *_t = reinterpret_cast<%s *>(_o);\n", cdef->classname.constData(), cdef->classname.constData());
}
fprintf(out, " Q_UNUSED(_t)\n");
setupMemberAccess();
fprintf(out, " switch (_id) {\n");
for (int propindex = 0; propindex < cdef->propertyList.size(); ++propindex) {
const PropertyDef &p = cdef->propertyList.at(propindex);
@ -1497,6 +1493,42 @@ void Generator::generateStaticMetacall()
fprintf(out, " }\n");
}
fprintf(out, " }");
fprintf(out, " else ");
fprintf(out, "if (_c == QMetaObject::RegisterQPropertyObserver) {\n");
if (haveQProperties) {
setupMemberAccess();
fprintf(out, " QPropertyObserver *observer = reinterpret_cast<QPropertyObserver *>(_a[0]);\n");
fprintf(out, " switch (_id) {\n");
for (int propindex = 0; propindex < cdef->propertyList.size(); ++propindex) {
const PropertyDef &p = cdef->propertyList.at(propindex);
if (!p.isQProperty)
continue;
fprintf(out, " case %d: observer->setSource(_t->%s); break;\n",
propindex, p.name.constData());
}
fprintf(out, " default: break;\n");
fprintf(out, " }\n");
}
fprintf(out, " }");
fprintf(out, " else ");
fprintf(out, "if (_c == QMetaObject::SetQPropertyBinding) {\n");
if (haveQProperties) {
setupMemberAccess();
fprintf(out, " switch (_id) {\n");
for (int propindex = 0; propindex < cdef->propertyList.size(); ++propindex) {
const PropertyDef &p = cdef->propertyList.at(propindex);
if (!p.isQProperty)
continue;
fprintf(out, " case %d: _t->%s.setBinding(*reinterpret_cast<QPropertyBinding<%s> *>(_a[0])); break;\n",
propindex, p.name.constData(), p.type.constData());
}
fprintf(out, " default: break;\n");
fprintf(out, " }\n");
}
fprintf(out, " }");
fprintf(out, "\n#endif // QT_NO_PROPERTIES");
needElse = true;
}

View File

@ -564,6 +564,32 @@ bool Moc::parseMaybeFunction(const ClassDef *cdef, FunctionDef *def)
return true;
}
// Try to parse QProperty<MyType> propertName; members
bool Moc::parseMaybeQProperty(ClassDef *def)
{
if (!test(IDENTIFIER))
return false;
if (lexem() != "QProperty")
return false;
if (!test(LANGLE))
return false;
until(RANGLE);
next();
const auto propName = lexem();
if (!test(SEMIC))
return false;
def->qPropertyMembers.insert(propName);
return true;
}
void Moc::parse()
{
QVector<NamespaceDef> namespaceList;
@ -909,7 +935,9 @@ void Moc::parse()
}
}
} else {
index = rewind;
index = rewind - 1;
if (!parseMaybeQProperty(&def))
index = rewind;
}
}
}
@ -1198,11 +1226,14 @@ void Moc::parseSignals(ClassDef *def)
void Moc::createPropertyDef(PropertyDef &propDef)
{
propDef.location = index;
QByteArray type = parseType().name;
if (type.isEmpty())
error();
propDef.designable = propDef.scriptable = propDef.stored = "true";
propDef.user = "false";
/*
The Q_PROPERTY construct cannot contain any commas, since
commas separate macro arguments. We therefore expect users
@ -1234,6 +1265,17 @@ void Moc::createPropertyDef(PropertyDef &propDef)
next();
propDef.name = lexem();
// Could be Q_PROPERTY(type field) and later QProperty<int> field; -- to be resolved later.
if (lookup() == RPAREN) {
propDef.isQProperty = true;
propDef.designable = propDef.scriptable = propDef.stored = "true";
propDef.user = "false";
propDef.read = propDef.name + ".value";
propDef.write = propDef.name + ".setValue";
return;
}
while (test(IDENTIFIER)) {
const QByteArray l = lexem();
if (l[0] == 'C' && l == "CONSTANT") {
@ -1320,11 +1362,6 @@ void Moc::createPropertyDef(PropertyDef &propDef)
error(2);
}
}
if (propDef.read.isNull() && propDef.member.isNull()) {
const QByteArray msg = "Property declaration " + propDef.name
+ " has no READ accessor function or associated MEMBER variable. The property will be invalid.";
warning(msg.constData());
}
if (propDef.constant && !propDef.write.isNull()) {
const QByteArray msg = "Property declaration " + propDef.name
+ " is both WRITEable and CONSTANT. CONSTANT will be ignored.";
@ -1777,6 +1814,25 @@ void Moc::checkProperties(ClassDef *cdef)
}
definedProperties.insert(p.name);
const auto skipProperty = [&](const QByteArray &msg) {
const int rewind = index;
if (p.location >= 0)
index = p.location;
warning(msg.constData());
index = rewind;
cdef->propertyList.removeAt(i);
--i;
};
if (p.isQProperty) {
if (!cdef->qPropertyMembers.contains(p.name)) {
QByteArray msg = "Property declaration " + p.name + " has neither an associated QProperty<> member"
", nor a READ accessor function nor an associated MEMBER variable. The property will be invalid.";
skipProperty(msg);
break;
}
}
for (int j = 0; j < cdef->publicList.count(); ++j) {
const FunctionDef &f = cdef->publicList.at(j);
if (f.name != p.read)
@ -1989,6 +2045,7 @@ QJsonObject PropertyDef::toJson() const
prop[QLatin1String("constant")] = constant;
prop[QLatin1String("final")] = final;
prop[QLatin1String("required")] = required;
prop[QLatin1String("isQProperty")] = isQProperty;
if (revision > 0)
prop[QLatin1String("revision")] = revision;

View File

@ -140,6 +140,9 @@ struct PropertyDef
bool constant = false;
bool final = false;
bool required = false;
bool isQProperty = false;
int location = -1; // token index, used for error reporting
QJsonObject toJson() const;
};
@ -188,6 +191,7 @@ struct ClassDef : BaseDef {
QVector<FunctionDef> signalList, slotList, methodList, publicList;
QVector<QByteArray> nonClassSignalList;
QVector<PropertyDef> propertyList;
QSet<QByteArray> qPropertyMembers;
int notifyableProperties = 0;
int revisionedMethods = 0;
int revisionedProperties = 0;
@ -247,6 +251,7 @@ public:
bool parseFunction(FunctionDef *def, bool inMacro = false);
bool parseMaybeFunction(const ClassDef *cdef, FunctionDef *def);
bool parseMaybeQProperty(ClassDef *def);
void parseSlots(ClassDef *def, FunctionDef::Access access);
void parseSignals(ClassDef *def);

View File

@ -1046,6 +1046,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "prop1",
"read": "getProp1",
"required": false,
@ -1059,6 +1060,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "prop2",
"read": "getProp2",
"required": false,
@ -1072,6 +1074,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "prop3",
"read": "getProp3",
"required": false,
@ -1184,6 +1187,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "flags",
"read": "flags",
"required": false,
@ -1210,6 +1214,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "flags",
"read": "flags",
"required": false,
@ -1223,6 +1228,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "flagsList",
"read": "flagsList",
"required": false,
@ -1747,6 +1753,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "blah",
"read": "blah",
"required": false,
@ -1799,6 +1806,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "blah",
"read": "blah",
"required": false,
@ -1974,6 +1982,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "gadgetPoperty",
"read": "gadgetPoperty",
"required": false,
@ -1986,6 +1995,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "objectPoperty",
"read": "objectPoperty",
"required": false,
@ -2011,6 +2021,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "nestedGadgetPoperty",
"read": "nestedGadgetPoperty",
"required": false,
@ -2036,6 +2047,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "nestedObjectPoperty",
"read": "nestedObjectPoperty",
"required": false,
@ -2169,6 +2181,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "gadgetPoperty",
"read": "gadgetPoperty",
"required": false,
@ -2181,6 +2194,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "objectPoperty",
"read": "objectPoperty",
"required": false,
@ -2206,6 +2220,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "nestedGadgetPoperty",
"read": "nestedGadgetPoperty",
"required": false,
@ -2231,6 +2246,7 @@
"constant": false,
"designable": true,
"final": false,
"isQProperty": false,
"name": "nestedObjectPoperty",
"read": "nestedObjectPoperty",
"required": false,

View File

@ -723,6 +723,9 @@ private slots:
void mocJsonOutput();
void mocInclude();
void requiredProperties();
void qpropertyMembers();
void observerMetaCall();
void setQPRopertyBinding();
signals:
void sigWithUnsignedArg(unsigned foo);
@ -1603,7 +1606,7 @@ void tst_Moc::warnOnPropertyWithoutREAD()
QVERIFY(!mocOut.isEmpty());
QString mocWarning = QString::fromLocal8Bit(proc.readAllStandardError());
QCOMPARE(mocWarning, header +
QString(":36: Warning: Property declaration foo has no READ accessor function or associated MEMBER variable. The property will be invalid.\n"));
QString(":36: Warning: Property declaration foo has neither an associated QProperty<> member, nor a READ accessor function nor an associated MEMBER variable. The property will be invalid.\n"));
#else
QSKIP("Only tested on linux/gcc");
#endif
@ -2087,7 +2090,7 @@ void tst_Moc::warnings_data()
<< QStringList()
<< 0
<< QString("IGNORE_ALL_STDOUT")
<< QString("standard input:1: Warning: Property declaration x has no READ accessor function or associated MEMBER variable. The property will be invalid.");
<< QString("standard input:1: Warning: Property declaration x has neither an associated QProperty<> member, nor a READ accessor function nor an associated MEMBER variable. The property will be invalid.");
// This should output a warning
QTest::newRow("Duplicate property warning")
@ -2103,7 +2106,7 @@ void tst_Moc::warnings_data()
<< (QStringList() << "-nn")
<< 0
<< QString("IGNORE_ALL_STDOUT")
<< QString("standard input:1: Warning: Property declaration x has no READ accessor function or associated MEMBER variable. The property will be invalid.");
<< QString("standard input:1: Warning: Property declaration x has neither an associated QProperty<> member, nor a READ accessor function nor an associated MEMBER variable. The property will be invalid.");
// Passing "-nw" should suppress the warning
QTest::newRow("Invalid property warning with -nw")
@ -4100,6 +4103,104 @@ void tst_Moc::requiredProperties()
QVERIFY(!notRequired.isRequired());
}
class ClassWithQPropertyMembers : public QObject
{
Q_OBJECT
Q_PROPERTY(int publicProperty)
Q_PROPERTY(int privateExposedProperty)
public:
QProperty<int> publicProperty;
QProperty<int> notExposed;
protected:
QProperty<int> protectedProperty;
private:
QProperty<int> privateProperty;
QProperty<int> privateExposedProperty;
};
void tst_Moc::qpropertyMembers()
{
const auto metaObject = &ClassWithQPropertyMembers::staticMetaObject;
QCOMPARE(metaObject->propertyCount() - metaObject->superClass()->propertyCount(), 2);
QCOMPARE(metaObject->indexOfProperty("notExposed"), -1);
QMetaProperty prop = metaObject->property(metaObject->indexOfProperty("publicProperty"));
QVERIFY(prop.isValid());
QVERIFY(metaObject->property(metaObject->indexOfProperty("privateExposedProperty")).isValid());
ClassWithQPropertyMembers instance;
prop.write(&instance, 42);
QCOMPARE(instance.publicProperty.value(), 42);
instance.publicProperty.setValue(100);
QCOMPARE(prop.read(&instance).toInt(), 100);
QCOMPARE(prop.metaType(), QMetaType(QMetaType::Int));
QVERIFY(!prop.notifySignal().isValid());
}
void tst_Moc::observerMetaCall()
{
const auto metaObject = &ClassWithQPropertyMembers::staticMetaObject;
QMetaProperty prop = metaObject->property(metaObject->indexOfProperty("publicProperty"));
QVERIFY(prop.isValid());
ClassWithQPropertyMembers instance;
int observerCallCount = 0;
QProperty<int> dummy;
auto handler = dummy.onValueChanged([&observerCallCount]() {
++observerCallCount;
});
{
void *argv[] = { &handler };
instance.qt_metacall(QMetaObject::RegisterQPropertyObserver, prop.propertyIndex(), argv);
}
instance.publicProperty.setValue(100);
QCOMPARE(observerCallCount, 1);
instance.publicProperty.setValue(101);
QCOMPARE(observerCallCount, 2);
}
void tst_Moc::setQPRopertyBinding()
{
const auto metaObject = &ClassWithQPropertyMembers::staticMetaObject;
QMetaProperty prop = metaObject->property(metaObject->indexOfProperty("publicProperty"));
QVERIFY(prop.isValid());
ClassWithQPropertyMembers instance;
bool bindingCalled = false;
auto binding = Qt::makePropertyBinding([&bindingCalled]() {
bindingCalled = true;
return 42;
});
{
void *argv[] = { &binding };
instance.qt_metacall(QMetaObject::SetQPropertyBinding, prop.propertyIndex(), argv);
}
QVERIFY(!bindingCalled); // not yet!
QCOMPARE(instance.publicProperty.value(), 42);
QVERIFY(bindingCalled); // but now it should've been called :)
}
QTEST_MAIN(tst_Moc)
// the generated code must compile with QT_NO_KEYWORDS