Provide a way of exposing private QProperties with a fake API

The API reduces the amount of manual plumbing required to offer a
conceptual property through the traditional setter/getter API as well as
through QProperty<T> API. Since the latter would require inlining the
type and thus making it impossible to add new properties without
breaking binary compatibility, this patch introduces a fake API that
behaves similar but does not contain the property by value.

Change-Id: Ib9bccd867f0e4e36a520e5583ba348e728284253
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Simon Hausmann 2020-04-15 20:23:28 +02:00
parent b480acb372
commit 3d7265db90
9 changed files with 315 additions and 36 deletions

View File

@ -91,6 +91,34 @@ QT_BEGIN_NAMESPACE
#define Q_INTERFACES(x) QT_ANNOTATE_CLASS(qt_interfaces, x)
#define Q_PROPERTY(...) QT_ANNOTATE_CLASS(qt_property, __VA_ARGS__)
#define Q_PRIVATE_PROPERTY(d, text) QT_ANNOTATE_CLASS2(qt_private_property, d, text)
#define Q_PRIVATE_QPROPERTY(accessor, type, name, setter, ...) \
struct _qt_property_api_##name { \
type value() const; \
type operator()() const { return value(); } \
void setValue(type &&); \
void setValue(const type &); \
void operator=(const type &v) { setValue(v); } \
void operator=(type &&v) { setValue(std::move(v)); } \
QPropertyBinding<type> setBinding(const QPropertyBinding<type> &); \
QPropertyBinding<type> setBinding(QPropertyBinding<type> &&); \
QPropertyBinding<type> operator=(const QPropertyBinding<type> &b) { return setBinding(b); } \
QPropertyBinding<type> operator=(QPropertyBinding<type> &&b) { return setBinding(std::move(b)); } \
bool setBinding(const QUntypedPropertyBinding &); \
template <typename Functor> \
QPropertyBinding<type> setBinding(Functor f, \
const QPropertyBindingSourceLocation &location = QT_PROPERTY_DEFAULT_BINDING_LOCATION) \
{ \
return setBinding(Qt::makePropertyBinding(f, location)); \
} \
bool hasBinding() const; \
QPropertyBinding<type> binding() const; \
QPropertyBinding<type> takeBinding(); \
}; \
void setter(const type &value);
#define Q_PRIVATE_QPROPERTIES_BEGIN union {
#define Q_PRIVATE_QPROPERTY_IMPL(name) \
_qt_property_api_##name name;
#define Q_PRIVATE_QPROPERTIES_END };
#ifndef Q_REVISION
# define Q_REVISION(...)
#endif
@ -211,6 +239,10 @@ private: \
#define Q_INTERFACES(x) Q_INTERFACES(x)
#define Q_PROPERTY(text) Q_PROPERTY(text)
#define Q_PRIVATE_PROPERTY(d, text) Q_PRIVATE_PROPERTY(d, text)
#define Q_PRIVATE_QPROPERTY(accessor, type, name, setter, ...) Q_PRIVATE_QPROPERTY(accessor, type, name, setter, __VA_ARGS__)
#define Q_PRIVATE_QPROPERTIES_BEGIN
#define Q_PRIVATE_QPROPERTY_IMPL(name)
#define Q_PRIVATE_QPROPERTIES_END
#define Q_REVISION(...) Q_REVISION(__VA_ARGS__)
#define Q_OVERRIDE(text) Q_OVERRIDE(text)
#define Q_ENUMS(x) Q_ENUMS(x)

View File

@ -615,6 +615,11 @@ void Generator::generateCode()
for (int signalindex = 0; signalindex < cdef->signalList.size(); ++signalindex)
generateSignal(&cdef->signalList[signalindex], signalindex);
//
// Generate QProperty forwarding API
//
generateQPropertyApi();
//
// Generate plugin meta data
//
@ -1621,6 +1626,106 @@ void Generator::generateSignal(FunctionDef *def,int index)
fprintf(out, "}\n");
}
void Generator::generateQPropertyApi()
{
for (const PrivateQPropertyDef &property: cdef->privateQProperties) {
auto printAccessor = [this, property](bool constAccessor = false) {
const char *constOrNot = constAccessor ? "const " : " ";
fprintf(out, " const size_t propertyMemberOffset = reinterpret_cast<size_t>(&(static_cast<%s *>(nullptr)->%s));\n", cdef->qualified.constData(), property.name.constData());
fprintf(out, " %sauto *thisPtr = reinterpret_cast<%s%s *>(reinterpret_cast<%schar *>(this) - propertyMemberOffset);\n", constOrNot, constOrNot, cdef->qualified.constData(), constOrNot);
};
// property accessor
fprintf(out, "\n%s %s::_qt_property_api_%s::value() const\n{\n",
property.type.name.constData(),
cdef->qualified.constData(),
property.name.constData());
printAccessor(/*const*/true);
fprintf(out, " return thisPtr->%s->%s.value();\n", property.accessor.constData(), property.name.constData());
fprintf(out, "}\n");
// property value setter
fprintf(out, "\nvoid %s::_qt_property_api_%s::setValue(const %s &value)\n{\n",
cdef->qualified.constData(),
property.name.constData(),
property.type.name.constData());
printAccessor();
fprintf(out, " return thisPtr->%s->%s.setValue(value);\n", property.accessor.constData(), property.name.constData());
fprintf(out, "}\n");
// property value move setter
fprintf(out, "\nvoid %s::_qt_property_api_%s::setValue(%s &&value)\n{\n",
cdef->qualified.constData(),
property.name.constData(),
property.type.name.constData());
printAccessor();
fprintf(out, " return thisPtr->%s->%s.setValue(std::move(value));\n", property.accessor.constData(), property.name.constData());
fprintf(out, "}\n");
// binding setter
fprintf(out, "\nQPropertyBinding<%s> %s::_qt_property_api_%s::setBinding(const QPropertyBinding<%s> &binding)\n{\n",
property.type.name.constData(),
cdef->qualified.constData(),
property.name.constData(),
property.type.name.constData());
printAccessor();
fprintf(out, " return thisPtr->%s->%s.setBinding(binding);\n", property.accessor.constData(), property.name.constData());
fprintf(out, "}\n");
// binding move setter
fprintf(out, "\nQPropertyBinding<%s> %s::_qt_property_api_%s::setBinding(QPropertyBinding<%s> &&binding)\n{\n",
property.type.name.constData(),
cdef->qualified.constData(),
property.name.constData(),
property.type.name.constData());
printAccessor();
fprintf(out, " return thisPtr->%s->%s.setBinding(std::move(binding));\n", property.accessor.constData(), property.name.constData());
fprintf(out, "}\n");
// untyped binding setter
fprintf(out, "\nbool %s::_qt_property_api_%s::setBinding(const QUntypedPropertyBinding &binding)\n{\n",
cdef->qualified.constData(),
property.name.constData());
printAccessor();
fprintf(out, " return thisPtr->%s->%s.setBinding(binding);\n", property.accessor.constData(), property.name.constData());
fprintf(out, "}\n");
// binding bool getter
fprintf(out, "\nbool %s::_qt_property_api_%s::hasBinding() const\n{\n",
cdef->qualified.constData(),
property.name.constData());
printAccessor(/*const*/true);
fprintf(out, " return thisPtr->%s->%s.hasBinding();\n", property.accessor.constData(), property.name.constData());
fprintf(out, "}\n");
// binding getter
fprintf(out, "\nQPropertyBinding<%s> %s::_qt_property_api_%s::binding() const\n{\n",
property.type.name.constData(),
cdef->qualified.constData(),
property.name.constData());
printAccessor(/*const*/true);
fprintf(out, " return thisPtr->%s->%s.binding();\n", property.accessor.constData(), property.name.constData());
fprintf(out, "}\n");
// binding taker
fprintf(out, "\nQPropertyBinding<%s> %s::_qt_property_api_%s::takeBinding()\n{\n",
property.type.name.constData(),
cdef->qualified.constData(),
property.name.constData());
printAccessor();
fprintf(out, " return thisPtr->%s->%s.takeBinding();\n", property.accessor.constData(), property.name.constData());
fprintf(out, "}\n");
// property setter function
fprintf(out, "\nvoid %s::%s(const %s &value)\n{\n",
cdef->qualified.constData(),
property.setter.constData(),
property.type.name.constData());
fprintf(out, " %s->%s.setValue(value);\n", property.accessor.constData(), property.name.constData());
fprintf(out, "}\n\n");
}
}
static CborError jsonValueToCbor(CborEncoder *parent, const QJsonValue &v);
static CborError jsonObjectToCbor(CborEncoder *parent, const QJsonObject &o)
{

View File

@ -58,6 +58,7 @@ private:
void generateMetacall();
void generateStaticMetacall();
void generateSignal(FunctionDef *def, int index);
void generateQPropertyApi();
void generatePluginMetaData();
QMultiMap<QByteArray, int> automaticPropertyMetaTypesHelper();
QMap<int, QMultiMap<QByteArray, int> > methodsWithAutomaticTypesHelper(const QVector<FunctionDef> &methodList);

View File

@ -30,12 +30,12 @@
// DO NOT EDIT.
static const short keyword_trans[][128] = {
{0,0,0,0,0,0,0,0,0,579,576,0,0,0,0,0,
{0,0,0,0,0,0,0,0,0,588,585,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
579,252,577,580,8,38,239,578,25,26,236,234,30,235,27,237,
588,252,586,589,8,38,239,587,25,26,236,234,30,235,27,237,
22,22,22,22,22,22,22,22,22,22,34,41,23,39,24,43,
0,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,
8,21,8,8,8,8,8,8,8,8,8,31,582,32,238,8,
8,21,8,8,8,8,8,8,8,8,8,31,591,32,238,8,
0,1,2,3,4,5,6,7,8,9,8,8,10,11,12,13,
14,8,15,16,17,18,19,20,8,8,8,36,245,37,248,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@ -177,7 +177,7 @@ static const short keyword_trans[][128] = {
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,42,0,0,0,28,0,
585,585,585,585,585,585,585,585,585,585,0,0,0,0,0,0,
594,594,594,594,594,594,594,594,594,594,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@ -336,7 +336,7 @@ static const short keyword_trans[][128] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,584,0,0,0,0,583,
0,0,0,0,0,0,0,0,0,0,593,0,0,0,0,592,
0,0,0,0,0,0,0,0,0,0,0,0,0,258,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@ -378,8 +378,8 @@ static const short keyword_trans[][128] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,475,424,408,416,380,0,484,0,0,0,565,364,358,
386,0,557,472,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,475,424,408,416,380,0,484,0,0,0,574,364,358,
386,0,566,472,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
@ -443,7 +443,7 @@ static const short keyword_trans[][128] = {
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
549,0,0,517,0,0,0,0,0,0,0,0,0,0,0,0,
549,557,0,517,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}
};
@ -1013,30 +1013,39 @@ static const struct
{CHARACTER, 0, 84, 555, CHARACTER},
{CHARACTER, 0, 89, 556, CHARACTER},
{Q_PRIVATE_PROPERTY_TOKEN, 0, 0, 0, CHARACTER},
{CHARACTER, 0, 69, 558, CHARACTER},
{CHARACTER, 0, 86, 559, CHARACTER},
{CHARACTER, 0, 73, 560, CHARACTER},
{CHARACTER, 0, 83, 561, CHARACTER},
{CHARACTER, 0, 73, 562, CHARACTER},
{CHARACTER, 0, 79, 563, CHARACTER},
{CHARACTER, 0, 78, 564, CHARACTER},
{Q_REVISION_TOKEN, 0, 0, 0, CHARACTER},
{CHARACTER, 0, 79, 566, CHARACTER},
{CHARACTER, 0, 67, 567, CHARACTER},
{CHARACTER, 0, 95, 568, CHARACTER},
{CHARACTER, 0, 80, 558, CHARACTER},
{CHARACTER, 0, 82, 559, CHARACTER},
{CHARACTER, 0, 79, 560, CHARACTER},
{CHARACTER, 0, 80, 561, CHARACTER},
{CHARACTER, 0, 69, 562, CHARACTER},
{CHARACTER, 0, 82, 563, CHARACTER},
{CHARACTER, 0, 84, 564, CHARACTER},
{CHARACTER, 0, 89, 565, CHARACTER},
{Q_PRIVATE_QPROPERTY_TOKEN, 0, 0, 0, CHARACTER},
{CHARACTER, 0, 69, 567, CHARACTER},
{CHARACTER, 0, 86, 568, CHARACTER},
{CHARACTER, 0, 73, 569, CHARACTER},
{CHARACTER, 0, 78, 570, CHARACTER},
{CHARACTER, 0, 67, 571, CHARACTER},
{CHARACTER, 0, 76, 572, CHARACTER},
{CHARACTER, 0, 85, 573, CHARACTER},
{CHARACTER, 0, 68, 574, CHARACTER},
{CHARACTER, 0, 69, 575, CHARACTER},
{CHARACTER, 0, 83, 570, CHARACTER},
{CHARACTER, 0, 73, 571, CHARACTER},
{CHARACTER, 0, 79, 572, CHARACTER},
{CHARACTER, 0, 78, 573, CHARACTER},
{Q_REVISION_TOKEN, 0, 0, 0, CHARACTER},
{CHARACTER, 0, 79, 575, CHARACTER},
{CHARACTER, 0, 67, 576, CHARACTER},
{CHARACTER, 0, 95, 577, CHARACTER},
{CHARACTER, 0, 73, 578, CHARACTER},
{CHARACTER, 0, 78, 579, CHARACTER},
{CHARACTER, 0, 67, 580, CHARACTER},
{CHARACTER, 0, 76, 581, CHARACTER},
{CHARACTER, 0, 85, 582, CHARACTER},
{CHARACTER, 0, 68, 583, CHARACTER},
{CHARACTER, 0, 69, 584, CHARACTER},
{Q_MOC_INCLUDE_TOKEN, 0, 0, 0, CHARACTER},
{NEWLINE, 0, 0, 0, NOTOKEN},
{QUOTE, 0, 0, 0, NOTOKEN},
{SINGLEQUOTE, 0, 0, 0, NOTOKEN},
{WHITESPACE, 0, 0, 0, NOTOKEN},
{HASH, 0, 35, 581, HASH},
{HASH, 0, 35, 590, HASH},
{PP_HASHHASH, 0, 0, 0, NOTOKEN},
{BACKSLASH, 0, 0, 0, NOTOKEN},
{CPP_COMMENT, 0, 0, 0, NOTOKEN},

View File

@ -878,6 +878,9 @@ void Moc::parse()
case Q_PRIVATE_PROPERTY_TOKEN:
parsePrivateProperty(&def);
break;
case Q_PRIVATE_QPROPERTY_TOKEN:
parsePrivateQProperty(&def);
break;
case ENUM: {
EnumDef enumDef;
if (parseEnum(&enumDef))
@ -1031,10 +1034,14 @@ static QByteArrayList requiredQtContainers(const QVector<ClassDef> &classes)
QByteArrayList required;
required.reserve(candidates.size());
bool needsQProperty = false;
for (const auto &candidate : candidates) {
const QByteArray pattern = candidate + '<';
for (const auto &c : classes) {
if (!c.privateQProperties.isEmpty())
needsQProperty = true;
if (any_type_contains(c.propertyList, pattern) ||
any_arg_contains(c.slotList, pattern) ||
any_arg_contains(c.signalList, pattern) ||
@ -1045,6 +1052,9 @@ static QByteArrayList requiredQtContainers(const QVector<ClassDef> &classes)
}
}
if (needsQProperty)
required.push_back("QProperty");
return required;
}
@ -1268,6 +1278,14 @@ void Moc::createPropertyDef(PropertyDef &propDef)
propDef.type = type;
next();
propDef.name = lexem();
parsePropertyAttributes(propDef);
}
void Moc::parsePropertyAttributes(PropertyDef &propDef)
{
auto checkIsFunction = [&](const QByteArray &def, const char *name) {
if (def.endsWith(')')) {
QByteArray msg = "Providing a function for ";
@ -1277,9 +1295,6 @@ void Moc::createPropertyDef(PropertyDef &propDef)
}
};
next();
propDef.name = lexem();
while (test(IDENTIFIER)) {
const QByteArray l = lexem();
if (l[0] == 'C' && l == "CONSTANT") {
@ -1459,23 +1474,30 @@ void Moc::parsePluginData(ClassDef *def)
next(RPAREN);
}
void Moc::parsePrivateProperty(ClassDef *def)
QByteArray Moc::parsePropertyAccessor()
{
next(LPAREN);
PropertyDef propDef;
next(IDENTIFIER);
propDef.inPrivateClass = lexem();
QByteArray accessor = lexem();
while (test(SCOPE)) {
propDef.inPrivateClass += lexem();
accessor += lexem();
next(IDENTIFIER);
propDef.inPrivateClass += lexem();
accessor += lexem();
}
// also allow void functions
if (test(LPAREN)) {
next(RPAREN);
propDef.inPrivateClass += "()";
accessor += "()";
}
return accessor;
}
void Moc::parsePrivateProperty(ClassDef *def)
{
next(LPAREN);
PropertyDef propDef;
propDef.inPrivateClass = parsePropertyAccessor();
next(COMMA);
createPropertyDef(propDef);
@ -1488,6 +1510,42 @@ void Moc::parsePrivateProperty(ClassDef *def)
def->propertyList += propDef;
}
void Moc::parsePrivateQProperty(ClassDef *def)
{
next(LPAREN);
const QByteArray accessor = parsePropertyAccessor();
next(COMMA);
const Type type = parseType();
next(COMMA);
next(IDENTIFIER);
const QByteArray name = lexem();
next(COMMA);
next(IDENTIFIER);
const QByteArray setter = lexem();
def->privateQProperties += PrivateQPropertyDef{type, name, setter, accessor};
PropertyDef propDef;
propDef.name = name;
propDef.type = type.name;
propDef.read = name + ".value";
propDef.write = name + ".setValue";
propDef.isQProperty = true;
propDef.inPrivateClass = accessor;
propDef.designable = propDef.scriptable = propDef.stored = "true";
propDef.user = "false";
if (test(COMMA))
parsePropertyAttributes(propDef);
next(RPAREN);
if (!propDef.notify.isEmpty())
def->notifyableProperties++;
def->propertyList += propDef;
}
void Moc::parseEnumOrFlag(BaseDef *def, bool isFlag)
{
next(LPAREN);

View File

@ -148,6 +148,14 @@ struct PropertyDef
};
Q_DECLARE_TYPEINFO(PropertyDef, Q_MOVABLE_TYPE);
struct PrivateQPropertyDef
{
Type type;
QByteArray name;
QByteArray setter;
QByteArray accessor;
};
Q_DECLARE_TYPEINFO(PrivateQPropertyDef, Q_MOVABLE_TYPE);
struct ClassInfoDef
{
@ -191,6 +199,7 @@ struct ClassDef : BaseDef {
QVector<FunctionDef> signalList, slotList, methodList, publicList;
QVector<QByteArray> nonClassSignalList;
QVector<PropertyDef> propertyList;
QVector<PrivateQPropertyDef> privateQProperties;
QSet<QByteArray> qPropertyMembers;
int notifyableProperties = 0;
int revisionedMethods = 0;
@ -258,6 +267,7 @@ public:
void parseProperty(ClassDef *def);
void parsePluginData(ClassDef *def);
void createPropertyDef(PropertyDef &def);
void parsePropertyAttributes(PropertyDef &propDef);
void parseEnumOrFlag(BaseDef *def, bool isFlag);
void parseFlag(BaseDef *def);
void parseClassInfo(BaseDef *def);
@ -266,7 +276,9 @@ public:
void parseDeclareMetatype();
void parseMocInclude();
void parseSlotInPrivate(ClassDef *def, FunctionDef::Access access);
QByteArray parsePropertyAccessor();
void parsePrivateProperty(ClassDef *def);
void parsePrivateQProperty(ClassDef *def);
void parseFunctionArguments(FunctionDef *def);

View File

@ -178,6 +178,7 @@ QT_BEGIN_NAMESPACE
F(Q_INVOKABLE_TOKEN) \
F(Q_SCRIPTABLE_TOKEN) \
F(Q_PRIVATE_PROPERTY_TOKEN) \
F(Q_PRIVATE_QPROPERTY_TOKEN) \
F(Q_REVISION_TOKEN) \
F(Q_MOC_INCLUDE_TOKEN) \
F(SPECIAL_TREATMENT_MARK) \

View File

@ -242,6 +242,7 @@ static const Keyword keywords[] = {
{ "Q_SLOT", "Q_SLOT_TOKEN" },
{ "Q_SCRIPTABLE", "Q_SCRIPTABLE_TOKEN" },
{ "Q_PRIVATE_PROPERTY", "Q_PRIVATE_PROPERTY_TOKEN" },
{ "Q_PRIVATE_QPROPERTY", "Q_PRIVATE_QPROPERTY_TOKEN" },
{ "Q_REVISION", "Q_REVISION_TOKEN" },
{ "Q_MOC_INCLUDE", "Q_MOC_INCLUDE_TOKEN" },
{ "\n", "NEWLINE" },

View File

@ -727,6 +727,7 @@ private slots:
void qpropertyMembers();
void observerMetaCall();
void setQPRopertyBinding();
void privateQPropertyShim();
signals:
void sigWithUnsignedArg(unsigned foo);
@ -4212,6 +4213,65 @@ void tst_Moc::setQPRopertyBinding()
QVERIFY(bindingCalled); // but now it should've been called :)
}
class ClassWithPrivateQPropertyShim :public QObject
{
Q_OBJECT
public:
Q_PRIVATE_QPROPERTY(d_func(), int, testProperty, setTestProperty, NOTIFY testPropertyChanged)
Q_PRIVATE_QPROPERTIES_BEGIN
Q_PRIVATE_QPROPERTY_IMPL(testProperty)
Q_PRIVATE_QPROPERTIES_END
signals:
void testPropertyChanged();
public:
struct Private {
Private(ClassWithPrivateQPropertyShim *pub)
: q(pub)
{}
ClassWithPrivateQPropertyShim *q = nullptr;
QProperty<int> testProperty;
void onTestPropertyChanged() { q->testPropertyChanged(); }
QPropertyMemberChangeHandler<&Private::testProperty, &Private::onTestPropertyChanged> testChangeHandler{this};
};
Private priv{this};
Private *d_func() { return &priv; }
const Private *d_func() const { return &priv; }
};
void tst_Moc::privateQPropertyShim()
{
ClassWithPrivateQPropertyShim testObject;
{
auto metaObject = &ClassWithPrivateQPropertyShim::staticMetaObject;
QMetaProperty prop = metaObject->property(metaObject->indexOfProperty("testProperty"));
QVERIFY(prop.isValid());
QVERIFY(prop.notifySignal().isValid());
}
testObject.priv.testProperty.setValue(42);
QCOMPARE(testObject.property("testProperty").toInt(), 42);
// Behave like a QProperty
QVERIFY(!testObject.testProperty.hasBinding());
testObject.testProperty.setBinding([]() { return 100; });
QCOMPARE(testObject.testProperty.value(), 100);
QVERIFY(testObject.testProperty.hasBinding());
// Old style setter getters
testObject.setTestProperty(400);
QVERIFY(!testObject.testProperty.hasBinding());
QCOMPARE(testObject.testProperty(), 400);
}
QTEST_MAIN(tst_Moc)
// the generated code must compile with QT_NO_KEYWORDS