moc: Allow NOTIFY signals defined in parent classes

Limitation is that the signal needs to be parameter-less

[ChangeLog][moc] moc now supports NOTIFY signals of parent classes in Q_PROPERTY

Change-Id: Iad64c96c3ec65d4be8ad9ff1a9f889938ab9bf45
Reviewed-by: Olivier Goffart (Woboq GmbH) <ogoffart@woboq.com>
Reviewed-by: Brett Stottlemyer <bstottle@ford.com>
This commit is contained in:
Albert Astals Cid 2017-03-10 13:14:19 +01:00 committed by Olivier Goffart (Woboq GmbH)
parent f437fb2934
commit 2ca187caa3
9 changed files with 161 additions and 15 deletions

View File

@ -3347,7 +3347,21 @@ int QMetaProperty::notifySignalIndex() const
if (hasNotifySignal()) {
int offset = priv(mobj->d.data)->propertyData +
priv(mobj->d.data)->propertyCount * 3 + idx;
return mobj->d.data[offset] + mobj->methodOffset();
int methodIndex = mobj->d.data[offset];
if (methodIndex & IsUnresolvedSignal) {
methodIndex &= ~IsUnresolvedSignal;
const QByteArray signalName = stringData(mobj, methodIndex);
const QMetaObject *m = mobj;
const int idx = indexOfMethodRelative<MethodSignal>(&m, signalName, 0, nullptr);
if (idx >= 0) {
return idx + m->methodOffset();
} else {
qWarning("QMetaProperty::notifySignal: cannot find the NOTIFY signal %s in class %s for property '%s'",
signalName.constData(), objectClassName(mobj), name());
return -1;
}
}
return methodIndex + mobj->methodOffset();
} else {
return -1;
}

View File

@ -111,7 +111,8 @@ enum MetaObjectFlags {
enum MetaDataFlags {
IsUnresolvedType = 0x80000000,
TypeNameIndexMask = 0x7FFFFFFF
TypeNameIndexMask = 0x7FFFFFFF,
IsUnresolvedSignal = 0x70000000
};
enum EnumFlags {

View File

@ -218,6 +218,7 @@ void Generator::generateCode()
registerFunctionStrings(cdef->slotList);
registerFunctionStrings(cdef->methodList);
registerFunctionStrings(cdef->constructorList);
registerByteArrayVector(cdef->nonClassSignalList);
registerPropertyStrings();
registerEnumStrings();
@ -603,6 +604,19 @@ void Generator::generateCode()
// Generate plugin meta data
//
generatePluginMetaData();
//
// Generate function to make sure the non-class signals exist in the parent classes
//
if (!cdef->nonClassSignalList.isEmpty()) {
fprintf(out, "// If you get a compile error in this function it can be because either\n");
fprintf(out, "// a) You are using a NOTIFY signal that does not exist. Fix it.\n");
fprintf(out, "// b) You are using a NOTIFY signal that does exist (in a parent class) but has a non-empty parameter list. This is a moc limitation.\n");
fprintf(out, "Q_DECL_UNUSED static void checkNotifySignalValidity_%s(%s *t) {\n", qualifiedClassNameIdentifier.constData(), cdef->qualified.constData());
for (const QByteArray &nonClassSignal : cdef->nonClassSignalList)
fprintf(out, " t->%s();\n", nonClassSignal.constData());
fprintf(out, "}\n");
}
}
@ -648,6 +662,12 @@ void Generator::registerFunctionStrings(const QVector<FunctionDef>& list)
}
}
void Generator::registerByteArrayVector(const QVector<QByteArray> &list)
{
for (const QByteArray &ba : list)
strreg(ba);
}
void Generator::generateFunctions(const QVector<FunctionDef>& list, const char *functype, int type, int &paramsIndex)
{
if (list.isEmpty())
@ -841,12 +861,17 @@ void Generator::generateProperties()
fprintf(out, "\n // properties: notify_signal_id\n");
for (int i = 0; i < cdef->propertyList.count(); ++i) {
const PropertyDef &p = cdef->propertyList.at(i);
if(p.notifyId == -1)
if (p.notifyId == -1) {
fprintf(out, " %4d,\n",
0);
else
} else if (p.notifyId > -1) {
fprintf(out, " %4d,\n",
p.notifyId);
} else {
const int indexInStrings = strings.indexOf(p.notify);
fprintf(out, " %4d,\n",
indexInStrings | IsUnresolvedSignal);
}
}
}
if (cdef->revisionedProperties) {
@ -1401,13 +1426,15 @@ void Generator::generateStaticMetacall()
prefix.constData(), p.member.constData(), p.type.constData());
fprintf(out, " %s%s = *reinterpret_cast< %s*>(_v);\n",
prefix.constData(), p.member.constData(), p.type.constData());
if (!p.notify.isEmpty() && p.notifyId != -1) {
if (!p.notify.isEmpty() && p.notifyId > -1) {
const FunctionDef &f = cdef->signalList.at(p.notifyId);
if (f.arguments.size() == 0)
fprintf(out, " Q_EMIT _t->%s();\n", p.notify.constData());
else if (f.arguments.size() == 1 && f.arguments.at(0).normalizedType == p.type)
fprintf(out, " Q_EMIT _t->%s(%s%s);\n",
p.notify.constData(), prefix.constData(), p.member.constData());
} else if (!p.notify.isEmpty() && p.notifyId < -1) {
fprintf(out, " Q_EMIT _t->%s();\n", p.notify.constData());
}
fprintf(out, " }\n");
fprintf(out, " break;\n");

View File

@ -46,6 +46,7 @@ private:
void registerClassInfoStrings();
void generateClassInfos();
void registerFunctionStrings(const QVector<FunctionDef> &list);
void registerByteArrayVector(const QVector<QByteArray> &list);
void generateFunctions(const QVector<FunctionDef> &list, const char *functype, int type, int &paramsIndex);
void generateFunctionRevisions(const QVector<FunctionDef> &list, const char *functype);
void generateFunctionParameters(const QVector<FunctionDef> &list, const char *functype);

View File

@ -1727,9 +1727,13 @@ void Moc::checkProperties(ClassDef *cdef)
}
p.notifyId = notifyId;
if (notifyId == -1) {
QByteArray msg = "NOTIFY signal '" + p.notify + "' of property '" + p.name
+ "' does not exist in class " + cdef->classname + ".";
error(msg.constData());
int index = cdef->nonClassSignalList.indexOf(p.notify);
if (index == -1) {
cdef->nonClassSignalList << p.notify;
p.notifyId = -1 - cdef->nonClassSignalList.count();
} else {
p.notifyId = -2 - index;
}
}
}
}

View File

@ -121,7 +121,7 @@ struct PropertyDef
{
PropertyDef():notifyId(-1), constant(false), final(false), gspec(ValueSpec), revision(0){}
QByteArray name, type, member, read, write, reset, designable, scriptable, editable, stored, user, notify, inPrivateClass;
int notifyId;
int notifyId; // -1 means no notifyId, >= 0 means signal defined in this class, < -1 means signal not defined in this class
bool constant;
bool final;
enum Specification { ValueSpec, ReferenceSpec, PointerSpec };
@ -179,6 +179,7 @@ struct ClassDef : BaseDef {
QVector<FunctionDef> constructorList;
QVector<FunctionDef> signalList, slotList, methodList, publicList;
QVector<QByteArray> nonClassSignalList;
int notifyableProperties = 0;
QVector<PropertyDef> propertyList;
int revisionedMethods = 0;

View File

@ -147,6 +147,77 @@ namespace MyNamespace {
{
Q_OBJECT
};
class ClassWithSetterGetterSignals : public QObject
{
Q_OBJECT
public:
int value1() const { return m_value1; }
void setValue1(int v) {
if (v != m_value1) {
m_value1 = v;
Q_EMIT value1Changed();
}
}
int value2() const { return m_value2; }
void setValue2(int v) {
if (v != m_value2) {
m_value2 = v;
Q_EMIT value2Changed();
}
}
Q_SIGNALS:
void value1Changed();
void value2Changed();
private:
int m_value1 = 0;
int m_value2 = 0;
};
class ClassWithSetterGetterSignalsAddsProperties : public ClassWithSetterGetterSignals
{
Q_OBJECT
Q_PROPERTY(int value1 READ value1 WRITE setValue1 NOTIFY value1Changed)
Q_PROPERTY(int value2 READ value2 WRITE setValue2 NOTIFY value2Changed)
};
class ClassWithChangedSignal : public QObject
{
Q_OBJECT
public:
int value1() const { return m_value1; }
void setValue1(int v) {
if (v != m_value1) {
m_value1 = v;
Q_EMIT propertiesChanged();
}
}
void thisIsNotASignal() { }
Q_SIGNALS:
void propertiesChanged();
private:
int m_value1 = 0;
};
class ClassWithChangedSignalNewValue : public ClassWithChangedSignal
{
Q_OBJECT
Q_PROPERTY(int value2 MEMBER m_value2 NOTIFY propertiesChanged)
Q_PROPERTY(int value3 MEMBER m_value3 NOTIFY thisIsNotASignal)
private:
int m_value2 = 0;
int m_value3 = 0;
};
}
@ -245,6 +316,8 @@ private slots:
void inherits_data();
void inherits();
void notifySignalsInParentClass();
signals:
void value6Changed();
void value7Changed(const QString &);
@ -1672,5 +1745,18 @@ void tst_QMetaObject::inherits()
QCOMPARE(derivedMetaObject->inherits(baseMetaObject), inheritsResult);
}
void tst_QMetaObject::notifySignalsInParentClass()
{
MyNamespace::ClassWithSetterGetterSignalsAddsProperties obj;
QCOMPARE(obj.metaObject()->property(obj.metaObject()->indexOfProperty("value1")).notifySignal().name(), QByteArray("value1Changed"));
QCOMPARE(obj.metaObject()->property(obj.metaObject()->indexOfProperty("value2")).notifySignal().name(), QByteArray("value2Changed"));
MyNamespace::ClassWithChangedSignalNewValue obj2;
QCOMPARE(obj2.metaObject()->property(obj2.metaObject()->indexOfProperty("value2")).notifySignal().name(), QByteArray("propertiesChanged"));
QTest::ignoreMessage(QtWarningMsg, "QMetaProperty::notifySignal: cannot find the NOTIFY signal thisIsNotASignal in class MyNamespace::ClassWithChangedSignalNewValue for property 'value3'");
obj2.metaObject()->property(obj2.metaObject()->indexOfProperty("value3")).notifySignal();
}
QTEST_MAIN(tst_QMetaObject)
#include "tst_qmetaobject.moc"

View File

@ -28,7 +28,7 @@
#ifndef ERROR_ON_WRONG_NOTIFY_H
#define ERROR_ON_WRONG_NOTIFY_H
#include <QObject>
#include <QtCore/QObject>
class ClassWithWrongNOTIFY : public QObject
{

View File

@ -1830,13 +1830,25 @@ void tst_Moc::notifyError()
const QString header = m_sourceDirectory + QStringLiteral("/error-on-wrong-notify.h");
proc.start(m_moc, QStringList(header));
QVERIFY(proc.waitForFinished());
QCOMPARE(proc.exitCode(), 1);
QCOMPARE(proc.exitCode(), 0);
QCOMPARE(proc.exitStatus(), QProcess::NormalExit);
QByteArray mocOut = proc.readAllStandardOutput();
QVERIFY(mocOut.isEmpty());
QString mocError = QString::fromLocal8Bit(proc.readAllStandardError());
QCOMPARE(mocError, header +
QString(":42: Error: NOTIFY signal 'fooChanged' of property 'foo' does not exist in class ClassWithWrongNOTIFY.\n"));
QVERIFY(!mocOut.isEmpty());
QCOMPARE(proc.readAllStandardError(), QByteArray());
QStringList args;
args << "-c" << "-x" << "c++" << "-I" << "."
<< "-I" << qtIncludePath << "-o" << "/dev/null" << "-fPIC" << "-std=c++11" << "-";
proc.start("gcc", args);
QVERIFY(proc.waitForStarted());
proc.write(mocOut);
proc.closeWriteChannel();
QVERIFY(proc.waitForFinished());
QCOMPARE(proc.exitCode(), 1);
const QString gccOutput = QString::fromLocal8Bit(proc.readAllStandardError());
QVERIFY(gccOutput.contains(QLatin1String("error")));
QVERIFY(gccOutput.contains(QLatin1String("fooChanged")));
#else
QSKIP("Only tested on linux/gcc");
#endif