Honour the resettable flag of aliased properties
authorChris Adams <christopher.adams@nokia.com>
Tue, 12 Jul 2011 04:05:24 +0000 (14:05 +1000)
committerQt by Nokia <qt-info@nokia.com>
Thu, 4 Aug 2011 04:07:22 +0000 (06:07 +0200)
Previously, alias properties were not considered isResettable even
if the property they alias is resettable.  This commit ensures that
the IsResettable flag is set for alias properties iff the aliased
property is resettable, and that it is honoured during property
reset operations.

Task-number: QTBUG-18182
Change-Id: I9cab11923a952df72e976a48489a78b24a34314f
Reviewed-on: http://codereview.qt.nokia.com/1471
Reviewed-by: Qt Sanity Bot <qt_sanity_bot@ovi.com>
Reviewed-by: Aaron Kennedy <aaron.kennedy@nokia.com>
src/declarative/qml/qdeclarativecompiler.cpp
src/declarative/qml/qdeclarativevmemetaobject.cpp
tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/AliasPropertyComponent.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.1.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.2.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.3.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.4.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.5.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.error.1.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp

index 5b61421..02c517c 100644 (file)
@@ -2750,6 +2750,7 @@ bool QDeclarativeCompiler::compileAlias(QMetaObjectBuilder &builder,
     int propIdx = -1;
     int flags = 0;
     bool writable = false;
+    bool resettable = false;
     if (alias.count() == 2 || alias.count() == 3) {
         propIdx = indexOfProperty(idObject, alias.at(1).toUtf8());
 
@@ -2764,6 +2765,7 @@ bool QDeclarativeCompiler::compileAlias(QMetaObjectBuilder &builder,
             COMPILE_EXCEPTION(prop.defaultValue, tr("Invalid alias location"));
 
         writable = aliasProperty.isWritable();
+        resettable = aliasProperty.isResettable();
 
         if (alias.count() == 3) {
             QDeclarativeValueType *valueType = enginePrivate->valueTypes[aliasProperty.type()];
@@ -2808,6 +2810,7 @@ bool QDeclarativeCompiler::compileAlias(QMetaObjectBuilder &builder,
     QMetaPropertyBuilder propBuilder = 
         builder.addProperty(prop.name, typeName.constData(), builder.methodCount() - 1);
     propBuilder.setWritable(writable);
+    propBuilder.setResettable(resettable);
     return true;
 }
 
index 746c9f6..bcd46f2 100644 (file)
@@ -458,7 +458,7 @@ int QDeclarativeVMEMetaObject::metaCall(QMetaObject::Call c, int _id, void **a)
             }
         }
     }
-    if(c == QMetaObject::ReadProperty || c == QMetaObject::WriteProperty) {
+    if (c == QMetaObject::ReadProperty || c == QMetaObject::WriteProperty || c == QMetaObject::ResetProperty) {
         if (id >= propOffset) {
             id -= propOffset;
 
diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/AliasPropertyComponent.qml b/tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/AliasPropertyComponent.qml
new file mode 100644 (file)
index 0000000..9135e79
--- /dev/null
@@ -0,0 +1,17 @@
+import QtQuick 2.0
+
+Item {
+    id: apc
+    property alias sourceComponent: loader.sourceComponent
+
+    Component {
+        id: redSquare
+        Rectangle { color: "red"; width: 10; height: 10 }
+    }
+
+    Loader {
+        id: loader
+        objectName: "loader"
+        sourceComponent: redSquare
+    }
+}
diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.1.qml b/tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.1.qml
new file mode 100644 (file)
index 0000000..a27c16d
--- /dev/null
@@ -0,0 +1,23 @@
+import QtQuick 1.0
+import Qt.test 1.0
+
+Item {
+    id: first
+    property bool aliasIsUndefined: false
+    property alias sourceComponentAlias: loader.sourceComponent
+
+    Component {
+        id: redSquare
+        Rectangle { color: "red"; width: 10; height: 10 }
+    }
+
+    Loader {
+        id: loader
+        sourceComponent: redSquare
+    }
+
+    function resetAliased() {
+        loader.sourceComponent = undefined;
+        aliasIsUndefined = (sourceComponentAlias == undefined);
+    }
+}
diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.2.qml b/tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.2.qml
new file mode 100644 (file)
index 0000000..fa2bb5e
--- /dev/null
@@ -0,0 +1,23 @@
+import QtQuick 1.0
+import Qt.test 1.0
+
+Item {
+    id: first
+    property bool loaderSourceComponentIsUndefined: false
+    property alias sourceComponentAlias: loader.sourceComponent
+
+    Component {
+        id: redSquare
+        Rectangle { color: "red"; width: 10; height: 10 }
+    }
+
+    Loader {
+        id: loader
+        sourceComponent: redSquare
+    }
+
+    function resetAlias() {
+        sourceComponentAlias = undefined;
+        loaderSourceComponentIsUndefined = (loader.sourceComponent == undefined);
+    }
+}
diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.3.qml b/tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.3.qml
new file mode 100644 (file)
index 0000000..4fa6861
--- /dev/null
@@ -0,0 +1,31 @@
+import QtQuick 1.0
+import Qt.test 1.0
+
+Item {
+    id: first
+    property bool loaderTwoSourceComponentIsUndefined: false
+    property bool loaderOneSourceComponentIsUndefined: false
+    property alias sourceComponentAlias: loaderOne.sourceComponent
+
+    Component {
+        id: redSquare
+        Rectangle { color: "red"; width: 10; height: 10 }
+    }
+
+    Loader {
+        id: loaderOne
+        sourceComponent: loaderTwo.sourceComponent
+    }
+
+    Loader {
+        id: loaderTwo
+        sourceComponent: redSquare
+        x: 15
+    }
+
+    function resetAlias() {
+        sourceComponentAlias = undefined; // loaderOne.sourceComponent should be set to undefined instead of l2.sc
+        loaderOneSourceComponentIsUndefined = (loaderOne.sourceComponent == undefined); // should be true
+        loaderTwoSourceComponentIsUndefined = (loaderTwo.sourceComponent == undefined); // should be false
+    }
+}
diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.4.qml b/tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.4.qml
new file mode 100644 (file)
index 0000000..56fcc2e
--- /dev/null
@@ -0,0 +1,26 @@
+import QtQuick 1.0
+import Qt.test 1.0
+
+Item {
+    id: first
+    property alias sourceComponentAlias: loader.sourceComponent
+
+    Component {
+        id: redSquare
+        Rectangle { color: "red"; width: 10; height: 10 }
+    }
+
+    Loader {
+        id: loader
+        objectName: "loader"
+        sourceComponent: redSquare
+    }
+
+    function resetAlias() {
+        sourceComponentAlias = undefined; // ensure we don't crash after deletion of loader.
+    }
+
+    function setAlias() {
+        sourceComponentAlias = redSquare;
+    }
+}
diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.5.qml b/tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.5.qml
new file mode 100644 (file)
index 0000000..a046bbf
--- /dev/null
@@ -0,0 +1,14 @@
+import QtQuick 1.0
+import Qt.test 1.0
+
+Item {
+    id: root
+
+    AliasPropertyComponent {
+        sourceComponent: returnsUndefined()
+    }
+
+    function returnsUndefined() {
+        return undefined;
+    }
+}
diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.error.1.qml b/tests/auto/declarative/qdeclarativeecmascript/data/aliasreset/aliasPropertyReset.error.1.qml
new file mode 100644 (file)
index 0000000..22311d6
--- /dev/null
@@ -0,0 +1,18 @@
+import QtQuick 1.0
+import Qt.test 1.0
+
+MyQmlObject {
+    id: first
+    property bool aliasedIntIsUndefined: false
+    property alias intAlias: objprop.intp
+
+    objectProperty: QtObject {
+        id: objprop
+        property int intp: 12
+    }
+
+    function resetAlias() {
+        intAlias = undefined; // should error
+        aliasedIntIsUndefined = (objprop.intp == undefined);
+    }
+}
index c5f7ed1..3efe189 100644 (file)
@@ -104,6 +104,7 @@ private slots:
     void constantsOverrideBindings();
     void outerBindingOverridesInnerBinding();
     void aliasPropertyAndBinding();
+    void aliasPropertyReset();
     void nonExistentAttachedObject();
     void scope();
     void importScope();
@@ -1038,6 +1039,87 @@ void tst_qdeclarativeecmascript::aliasPropertyAndBinding()
     delete object;
 }
 
+/*
+Ensure that we can write undefined value to an alias property,
+and that the aliased property is reset correctly if possible.
+*/
+void tst_qdeclarativeecmascript::aliasPropertyReset()
+{
+    QObject *object = 0;
+
+    // test that a manual write (of undefined) to a resettable aliased property succeeds
+    QDeclarativeComponent c1(&engine, TEST_FILE("aliasreset/aliasPropertyReset.1.qml"));
+    object = c1.create();
+    QVERIFY(object != 0);
+    QVERIFY(object->property("sourceComponentAlias").value<QDeclarativeComponent*>() != 0);
+    QCOMPARE(object->property("aliasIsUndefined"), QVariant(false));
+    QMetaObject::invokeMethod(object, "resetAliased");
+    QVERIFY(object->property("sourceComponentAlias").value<QDeclarativeComponent*>() == 0);
+    QCOMPARE(object->property("aliasIsUndefined"), QVariant(true));
+    delete object;
+
+    // test that a manual write (of undefined) to a resettable alias property succeeds
+    QDeclarativeComponent c2(&engine, TEST_FILE("aliasreset/aliasPropertyReset.2.qml"));
+    object = c2.create();
+    QVERIFY(object != 0);
+    QVERIFY(object->property("sourceComponentAlias").value<QDeclarativeComponent*>() != 0);
+    QCOMPARE(object->property("loaderSourceComponentIsUndefined"), QVariant(false));
+    QMetaObject::invokeMethod(object, "resetAlias");
+    QVERIFY(object->property("sourceComponentAlias").value<QDeclarativeComponent*>() == 0);
+    QCOMPARE(object->property("loaderSourceComponentIsUndefined"), QVariant(true));
+    delete object;
+
+    // test that an alias to a bound property works correctly
+    QDeclarativeComponent c3(&engine, TEST_FILE("aliasreset/aliasPropertyReset.3.qml"));
+    object = c3.create();
+    QVERIFY(object != 0);
+    QVERIFY(object->property("sourceComponentAlias").value<QDeclarativeComponent*>() != 0);
+    QCOMPARE(object->property("loaderOneSourceComponentIsUndefined"), QVariant(false));
+    QCOMPARE(object->property("loaderTwoSourceComponentIsUndefined"), QVariant(false));
+    QMetaObject::invokeMethod(object, "resetAlias");
+    QVERIFY(object->property("sourceComponentAlias").value<QDeclarativeComponent*>() == 0);
+    QCOMPARE(object->property("loaderOneSourceComponentIsUndefined"), QVariant(true));
+    QCOMPARE(object->property("loaderTwoSourceComponentIsUndefined"), QVariant(false));
+    delete object;
+
+    // test that a manual write (of undefined) to a resettable alias property
+    // whose aliased property's object has been deleted, does not crash.
+    QDeclarativeComponent c4(&engine, TEST_FILE("aliasreset/aliasPropertyReset.4.qml"));
+    object = c4.create();
+    QVERIFY(object != 0);
+    QVERIFY(object->property("sourceComponentAlias").value<QDeclarativeComponent*>() != 0);
+    QObject *loader = object->findChild<QObject*>("loader");
+    QVERIFY(loader != 0);
+    delete loader;
+    QVERIFY(object->property("sourceComponentAlias").value<QDeclarativeComponent*>() == 0); // deletion should have caused value unset.
+    QMetaObject::invokeMethod(object, "resetAlias"); // shouldn't crash.
+    QVERIFY(object->property("sourceComponentAlias").value<QDeclarativeComponent*>() == 0);
+    QMetaObject::invokeMethod(object, "setAlias");   // shouldn't crash, and shouldn't change value (since it's no longer referencing anything).
+    QVERIFY(object->property("sourceComponentAlias").value<QDeclarativeComponent*>() == 0);
+    delete object;
+
+    // test that binding an alias property to an undefined value works correctly
+    QDeclarativeComponent c5(&engine, TEST_FILE("aliasreset/aliasPropertyReset.5.qml"));
+    object = c5.create();
+    QVERIFY(object != 0);
+    QVERIFY(object->property("sourceComponentAlias").value<QDeclarativeComponent*>() == 0); // bound to undefined value.
+    delete object;
+
+    // test that a manual write (of undefined) to a non-resettable property fails properly
+    QUrl url = TEST_FILE("aliasreset/aliasPropertyReset.error.1.qml");
+    QString warning1 = url.toString() + QLatin1String(":15: Error: Cannot assign [undefined] to int");
+    QDeclarativeComponent e1(&engine, url);
+    object = e1.create();
+    QVERIFY(object != 0);
+    QCOMPARE(object->property("intAlias").value<int>(), 12);
+    QCOMPARE(object->property("aliasedIntIsUndefined"), QVariant(false));
+    QTest::ignoreMessage(QtWarningMsg, warning1.toLatin1().constData());
+    QMetaObject::invokeMethod(object, "resetAlias");
+    QCOMPARE(object->property("intAlias").value<int>(), 12);
+    QCOMPARE(object->property("aliasedIntIsUndefined"), QVariant(false));
+    delete object;
+}
+
 void tst_qdeclarativeecmascript::dynamicCreation_data()
 {
     QTest::addColumn<QString>("method");