Fix property access and method invocation on value types that use inheritance
authorSimon Hausmann <simon.hausmann@theqtcompany.com>
Wed, 7 Jan 2015 10:51:37 +0000 (11:51 +0100)
committerSimon Hausmann <simon.hausmann@digia.com>
Wed, 7 Jan 2015 14:34:19 +0000 (15:34 +0100)
For gadgets/value types we use moc's static_metacall, which doesn't call the
parent class implementation. Therefore before placing a static metacall we
must resolve the indicies and find the right meta-object.

Change-Id: I258e3d9ecfc704498c68772dc42b16134a3bfd83
Reviewed-by: Olivier Goffart <ogoffart@woboq.com>
Reviewed-by: Alex Blasche <alexander.blasche@theqtcompany.com>
src/qml/qml/qqmlpropertycache.cpp
src/qml/qml/qqmlpropertycache_p.h
src/qml/qml/qqmlvaluetype.cpp
src/qml/qml/qqmlvaluetypewrapper.cpp
tests/auto/qml/qqmlvaluetypes/data/customvaluetype.qml
tests/auto/qml/qqmlvaluetypes/tst_qqmlvaluetypes.cpp

index b5bc29a..182e6a7 100644 (file)
@@ -1510,6 +1510,39 @@ bool QQmlMetaObject::canConvert(const QQmlMetaObject &from, const QQmlMetaObject
     return false;
 }
 
+void QQmlMetaObject::resolveGadgetMethodOrPropertyIndex(QMetaObject::Call type, const QMetaObject **metaObject, int *index)
+{
+    int offset;
+
+    switch (type) {
+    case QMetaObject::ReadProperty:
+    case QMetaObject::WriteProperty:
+    case QMetaObject::ResetProperty:
+    case QMetaObject::QueryPropertyDesignable:
+    case QMetaObject::QueryPropertyEditable:
+    case QMetaObject::QueryPropertyScriptable:
+    case QMetaObject::QueryPropertyStored:
+    case QMetaObject::QueryPropertyUser:
+        offset = (*metaObject)->propertyOffset();
+        while (*index < offset) {
+            *metaObject = (*metaObject)->superClass();
+            offset = (*metaObject)->propertyOffset();
+        }
+        break;
+    case QMetaObject::InvokeMetaMethod:
+        offset = (*metaObject)->methodOffset();
+        while (*index < offset) {
+            *metaObject = (*metaObject)->superClass();
+            offset = (*metaObject)->methodOffset();
+        }
+        break;
+    default:
+        Q_UNIMPLEMENTED();
+    }
+
+    *index -= offset;
+}
+
 QQmlPropertyCache *QQmlMetaObject::propertyCache(QQmlEnginePrivate *e) const
 {
     if (_m.isNull()) return 0;
@@ -1652,8 +1685,11 @@ void QQmlObjectOrGadget::metacall(QMetaObject::Call type, int index, void **argv
 {
     if (ptr.isT1())
         QMetaObject::metacall(ptr.asT1(), type, index, argv);
-    else
-        _m.asT1()->metaObject()->d.static_metacall(reinterpret_cast<QObject*>(ptr.asT2()), type, index, argv);
+    else {
+        const QMetaObject *metaObject = _m.asT1()->metaObject();
+        QQmlMetaObject::resolveGadgetMethodOrPropertyIndex(type, &metaObject, &index);
+        metaObject->d.static_metacall(reinterpret_cast<QObject*>(ptr.asT2()), type, index, argv);
+    }
 }
 
 QT_END_NAMESPACE
index 77bbd3d..033ff1d 100644 (file)
@@ -432,6 +432,10 @@ public:
 
     static bool canConvert(const QQmlMetaObject &from, const QQmlMetaObject &to);
 
+    // static_metacall (on Gadgets) doesn't call the base implementation and therefore
+    // we need a helper to find the correct meta object and property/method index.
+    static void resolveGadgetMethodOrPropertyIndex(QMetaObject::Call type, const QMetaObject **metaObject, int *index);
+
 protected:
     QBiPointer<QQmlPropertyCache, const QMetaObject> _m;
 };
index e901a9c..7f0e3a3 100644 (file)
@@ -231,7 +231,9 @@ void QQmlValueType::objectDestroyed(QObject *)
 
 int QQmlValueType::metaCall(QObject *, QMetaObject::Call type, int _id, void **argv)
 {
-    d.static_metacall(reinterpret_cast<QObject*>(gadgetPtr), type, _id, argv);
+    const QMetaObject *mo = _metaObject;
+    QQmlMetaObject::resolveGadgetMethodOrPropertyIndex(type, &mo, &_id);
+    mo->d.static_metacall(reinterpret_cast<QObject*>(gadgetPtr), type, _id, argv);
     return _id;
 }
 
index 332cfef..85a391f 100644 (file)
@@ -307,11 +307,15 @@ ReturnedValue QQmlValueTypeWrapper::get(Managed *m, String *name, bool *hasPrope
     if (result->propType == metatype) { \
         cpptype v; \
         void *args[] = { &v, 0 }; \
-        metaObject->d.static_metacall(reinterpret_cast<QObject*>(gadget), QMetaObject::ReadProperty, result->coreIndex, args); \
+        metaObject->d.static_metacall(reinterpret_cast<QObject*>(gadget), QMetaObject::ReadProperty, index, args); \
         return QV4::Encode(constructor(v)); \
     }
 
     const QMetaObject *metaObject = r->d()->propertyCache->metaObject();
+
+    int index = result->coreIndex;
+    QQmlMetaObject::resolveGadgetMethodOrPropertyIndex(QMetaObject::ReadProperty, &metaObject, &index);
+
     void *gadget = r->d()->gadget();
 
     // These four types are the most common used by the value type wrappers
@@ -322,9 +326,9 @@ ReturnedValue QQmlValueTypeWrapper::get(Managed *m, String *name, bool *hasPrope
 
     QVariant v(result->propType, (void *)0);
     void *args[] = { v.data(), 0 };
-    metaObject->d.static_metacall(reinterpret_cast<QObject*>(gadget), QMetaObject::ReadProperty, result->coreIndex, args);
+    metaObject->d.static_metacall(reinterpret_cast<QObject*>(gadget), QMetaObject::ReadProperty, index, args);
     return QV8Engine::fromVariant(v4, v);
-#undef VALUE_TYPE_ACCESSOR
+#undef VALUE_TYPE_LOAD
 }
 
 void QQmlValueTypeWrapper::put(Managed *m, String *name, const ValueRef value)
index 7ae7347..380eb55 100644 (file)
@@ -5,4 +5,7 @@ TypeWithCustomValueType {
     desk {
         monitorCount: 3
     }
+    derivedGadget {
+        baseProperty: 42
+    }
 }
index f07a34b..45cbbeb 100644 (file)
@@ -90,6 +90,7 @@ private slots:
     void groupedInterceptors_data();
     void customValueType();
     void customValueTypeInQml();
+    void gadgetInheritance();
 
 private:
     QQmlEngine engine;
@@ -1484,13 +1485,48 @@ void tst_qqmlvaluetypes::customValueType()
     QCOMPARE(cppOffice.desk().monitorCount, 2);
 }
 
+struct BaseGadget
+{
+    Q_GADGET
+    Q_PROPERTY(int baseProperty READ baseProperty WRITE setBaseProperty)
+public:
+    BaseGadget() : m_baseProperty(0) {}
+
+    int baseProperty() const { return m_baseProperty; }
+    void setBaseProperty(int value) { m_baseProperty = value; }
+    int m_baseProperty;
+
+    Q_INVOKABLE void functionInBaseGadget(int value) { m_baseProperty = value; }
+};
+
+Q_DECLARE_METATYPE(BaseGadget)
+
+struct DerivedGadget : public BaseGadget
+{
+    Q_GADGET
+    Q_PROPERTY(int derivedProperty READ derivedProperty WRITE setDerivedProperty)
+public:
+    DerivedGadget() : m_derivedProperty(0) {}
+
+    int derivedProperty() const { return m_derivedProperty; }
+    void setDerivedProperty(int value) { m_derivedProperty = value; }
+    int m_derivedProperty;
+
+    Q_INVOKABLE void functionInDerivedGadget(int value) { m_derivedProperty = value; }
+};
+
 class TypeWithCustomValueType : public QObject
 {
     Q_OBJECT
     Q_PROPERTY(MyDesk desk MEMBER m_desk)
+    Q_PROPERTY(DerivedGadget derivedGadget READ derivedGadget WRITE setDerivedGadget)
 public:
 
     MyDesk m_desk;
+
+    DerivedGadget derivedGadget() const { return m_derivedGadget; }
+    void setDerivedGadget(const DerivedGadget &value) { m_derivedGadget = value; }
+    DerivedGadget m_derivedGadget;
 };
 
 void tst_qqmlvaluetypes::customValueTypeInQml()
@@ -1503,6 +1539,24 @@ void tst_qqmlvaluetypes::customValueTypeInQml()
     TypeWithCustomValueType *t = qobject_cast<TypeWithCustomValueType*>(object.data());
     Q_ASSERT(t);
     QCOMPARE(t->m_desk.monitorCount, 3);
+    QCOMPARE(t->m_derivedGadget.baseProperty(), 42);
+}
+
+Q_DECLARE_METATYPE(DerivedGadget)
+
+void tst_qqmlvaluetypes::gadgetInheritance()
+{
+    QJSEngine engine;
+
+    QJSValue value = engine.toScriptValue(DerivedGadget());
+
+    QCOMPARE(value.property("baseProperty").toInt(), 0);
+    value.setProperty("baseProperty", 10);
+    QCOMPARE(value.property("baseProperty").toInt(), 10);
+
+    QJSValue method = value.property("functionInBaseGadget");
+    method.call(QJSValueList() << QJSValue(42));
+    QCOMPARE(value.property("baseProperty").toInt(), 42);
 }
 
 QTEST_MAIN(tst_qqmlvaluetypes)