Readonly QML property support
authorAaron Kennedy <aaron.kennedy@nokia.com>
Wed, 26 Oct 2011 14:04:58 +0000 (15:04 +0100)
committerQt by Nokia <qt-info@nokia.com>
Wed, 26 Oct 2011 16:38:40 +0000 (18:38 +0200)
Task-number: QTBUG-15257
Change-Id: I539b6e6a9e0e0172b68e8002aaa3f7c7e6648769
Reviewed-by: Aaron Kennedy <aaron.kennedy@nokia.com>
13 files changed:
src/declarative/qml/qdeclarativecompiler.cpp
src/declarative/qml/qdeclarativecompiler_p.h
src/declarative/qml/qdeclarativescript.cpp
src/declarative/qml/qdeclarativescript_p.h
tests/auto/declarative/qdeclarativeecmascript/data/readonlyDeclaration.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativeecmascript/tst_qdeclarativeecmascript.cpp
tests/auto/declarative/qdeclarativelanguage/data/ReadOnlyType.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativelanguage/data/property.5.errors.txt [deleted file]
tests/auto/declarative/qdeclarativelanguage/data/property.5.qml [deleted file]
tests/auto/declarative/qdeclarativelanguage/data/readOnly.5.errors.txt
tests/auto/declarative/qdeclarativelanguage/data/readOnly.5.qml
tests/auto/declarative/qdeclarativelanguage/data/readonly.qml [new file with mode: 0644]
tests/auto/declarative/qdeclarativelanguage/tst_qdeclarativelanguage.cpp

index 4788491..782a20e 100644 (file)
@@ -208,7 +208,7 @@ bool QDeclarativeCompiler::testLiteralAssignment(QDeclarativeScript::Property *p
 {
     const QDeclarativeScript::Variant &value = v->value;
 
-    if (!prop->core.isWritable())
+    if (!prop->core.isWritable() && !prop->isReadOnlyDeclaration)
         COMPILE_EXCEPTION(v, tr("Invalid property assignment: \"%1\" is a read-only property").arg(prop->name().toString()));
 
     if (prop->core.isEnum()) {
@@ -2004,7 +2004,7 @@ bool QDeclarativeCompiler::buildGroupedProperty(QDeclarativeScript::Property *pr
                 }
             }
 
-            if (!obj->metaObject()->property(prop->index).isWritable()) {
+            if (!prop->core.isWritable() && !prop->isReadOnlyDeclaration) {
                 COMPILE_EXCEPTION(prop, tr( "Invalid property assignment: \"%1\" is a read-only property").arg(prop->name().toString()));
             }
 
@@ -2082,8 +2082,7 @@ bool QDeclarativeCompiler::buildValueTypeProperty(QObject *type,
                 bool isEnumAssignment = false;
 
                 if (prop->core.isEnum()) 
-                    COMPILE_CHECK(testQualifiedEnumAssignment(obj->metatype->property(prop->index), obj, 
-                                                              value, &isEnumAssignment));
+                    COMPILE_CHECK(testQualifiedEnumAssignment(prop, obj, value, &isEnumAssignment));
 
                 if (isEnumAssignment) {
                     value->type = Value::Literal;
@@ -2222,7 +2221,7 @@ bool QDeclarativeCompiler::buildPropertyObjectAssignment(QDeclarativeScript::Pro
     Q_ASSERT(prop->index != -1);
     Q_ASSERT(v->object->type != -1);
 
-    if (!obj->metaObject()->property(prop->index).isWritable())
+    if (!prop->core.isWritable() && !prop->isReadOnlyDeclaration)
         COMPILE_EXCEPTION(v, tr("Invalid property assignment: \"%1\" is a read-only property").arg(prop->name().toString()));
 
     if (QDeclarativeMetaType::isInterface(prop->type)) {
@@ -2303,7 +2302,9 @@ bool QDeclarativeCompiler::buildPropertyOnAssignment(QDeclarativeScript::Propert
     Q_ASSERT(prop->index != -1);
     Q_ASSERT(v->object->type != -1);
 
-    if (!obj->metaObject()->property(prop->index).isWritable())
+    Q_UNUSED(obj);
+
+    if (!prop->core.isWritable())
         COMPILE_EXCEPTION(v, tr("Invalid property assignment: \"%1\" is a read-only property").arg(prop->name().toString()));
 
 
@@ -2350,8 +2351,7 @@ bool QDeclarativeCompiler::buildPropertyLiteralAssignment(QDeclarativeScript::Pr
         //optimization for <Type>.<EnumValue> enum assignments
         if (prop->core.isEnum()) {
             bool isEnumAssignment = false;
-            COMPILE_CHECK(testQualifiedEnumAssignment(obj->metaObject()->property(prop->index), obj, 
-                                                      v, &isEnumAssignment));
+            COMPILE_CHECK(testQualifiedEnumAssignment(prop, obj, v, &isEnumAssignment));
             if (isEnumAssignment) {
                 v->type = Value::Literal;
                 return true;
@@ -2372,17 +2372,19 @@ bool QDeclarativeCompiler::buildPropertyLiteralAssignment(QDeclarativeScript::Pr
     return true;
 }
 
-bool QDeclarativeCompiler::testQualifiedEnumAssignment(const QMetaProperty &prop,
+bool QDeclarativeCompiler::testQualifiedEnumAssignment(QDeclarativeScript::Property *prop,
                                                        QDeclarativeScript::Object *obj,
                                                        QDeclarativeScript::Value *v,
                                                        bool *isAssignment)
 {
     *isAssignment = false;
-    if (!prop.isEnumType())
+    if (!prop->core.isEnum())
         return true;
 
-    if (!prop.isWritable())
-        COMPILE_EXCEPTION(v, tr("Invalid property assignment: \"%1\" is a read-only property").arg(QString::fromUtf8(prop.name())));
+    QMetaProperty mprop = obj->metaObject()->property(prop->index);
+
+    if (!prop->core.isWritable() && !prop->isReadOnlyDeclaration)
+        COMPILE_EXCEPTION(v, tr("Invalid property assignment: \"%1\" is a read-only property").arg(prop->name().toString()));
 
     QString string = v->value.asString();
     if (!string.at(0).isUpper())
@@ -2413,10 +2415,10 @@ bool QDeclarativeCompiler::testQualifiedEnumAssignment(const QMetaProperty &prop
 
     if (objTypeName == type->qmlTypeName()) {
         // When these two match, we can short cut the search
-        if (prop.isFlagType()) {
-            value = prop.enumerator().keysToValue(enumValue.toUtf8().constData(), &ok);
+        if (mprop.isFlagType()) {
+            value = mprop.enumerator().keysToValue(enumValue.toUtf8().constData(), &ok);
         } else {
-            value = prop.enumerator().keyToValue(enumValue.toUtf8().constData(), &ok);
+            value = mprop.enumerator().keyToValue(enumValue.toUtf8().constData(), &ok);
         }
     } else {
         // Otherwise we have to search the whole type
@@ -2571,7 +2573,8 @@ bool QDeclarativeCompiler::checkDynamicMeta(QDeclarativeScript::Object *obj)
 
 bool QDeclarativeCompiler::mergeDynamicMetaProperties(QDeclarativeScript::Object *obj)
 {
-    for (Object::DynamicProperty *p = obj->dynamicProperties.first(); p; p = obj->dynamicProperties.next(p)) {
+    for (Object::DynamicProperty *p = obj->dynamicProperties.first(); p;
+         p = obj->dynamicProperties.next(p)) {
 
         if (!p->defaultValue || p->type == Object::DynamicProperty::Alias)
             continue;
@@ -2585,6 +2588,9 @@ bool QDeclarativeCompiler::mergeDynamicMetaProperties(QDeclarativeScript::Object
                 COMPILE_EXCEPTION(property, tr("Property value set multiple times"));
         }
 
+        if (p->isReadOnly)
+            property->isReadOnlyDeclaration = true;
+
         if (property->value)
             COMPILE_EXCEPTION(property, tr("Invalid property nesting"));
 
@@ -2755,6 +2761,9 @@ bool QDeclarativeCompiler::buildDynamicMeta(QDeclarativeScript::Object *obj, Dyn
             if (p->type == Object::DynamicProperty::Var)
                 continue;
 
+            if (p->isReadOnly)
+                readonly = true;
+
             if (buildData) {
                 VMD *vmd = (QDeclarativeVMEMetaData *)dynamicData.data();
                 vmd->propertyCount++;
@@ -2790,8 +2799,10 @@ bool QDeclarativeCompiler::buildDynamicMeta(QDeclarativeScript::Object *obj, Dyn
                         (vmd->propertyData() + effectivePropertyIndex)->propertyType = -1;
                     }
 
-                    builder.setProperty(effectivePropertyIndex, p->nameRef, typeRef, (QMetaType::Type)-1,
-                                        QFastMetaBuilder::Writable, effectivePropertyIndex);
+                    builder.setProperty(effectivePropertyIndex, p->nameRef, typeRef,
+                                        (QMetaType::Type)-1,
+                                        p->isReadOnly?QFastMetaBuilder::None:QFastMetaBuilder::Writable,
+                                        effectivePropertyIndex);
 
                     p->changedSignatureRef = builder.newString(p->name.utf8length() + strlen("Changed()"));
                     builder.setSignal(effectivePropertyIndex, p->changedSignatureRef);
@@ -3098,8 +3109,8 @@ bool QDeclarativeCompiler::compileAlias(QFastMetaBuilder &builder,
         if (!aliasProperty.isScriptable())
             COMPILE_EXCEPTION(prop.defaultValue, tr("Invalid alias location"));
 
-        writable = aliasProperty.isWritable();
-        resettable = aliasProperty.isResettable();
+        writable = aliasProperty.isWritable() && !prop.isReadOnly;
+        resettable = aliasProperty.isResettable() && !prop.isReadOnly;
 
         if (aliasProperty.type() < QVariant::UserType)
             type = aliasProperty.type();
@@ -3175,7 +3186,7 @@ bool QDeclarativeCompiler::buildBinding(QDeclarativeScript::Value *value,
     Q_ASSERT(prop->parent);
     Q_ASSERT(prop->parent->metaObject());
 
-    if (!prop->core.isWritable() && !prop->core.isQList())
+    if (!prop->core.isWritable() && !prop->core.isQList() && !prop->isReadOnlyDeclaration)
         COMPILE_EXCEPTION(prop, tr("Invalid property assignment: \"%1\" is a read-only property").arg(prop->name().toString()));
 
     BindingReference *reference = pool->New<BindingReference>();
index 2615109..f6229bc 100644 (file)
@@ -336,7 +336,7 @@ private:
     bool doesPropertyExist(QDeclarativeScript::Property *prop, QDeclarativeScript::Object *obj);
     bool testLiteralAssignment(QDeclarativeScript::Property *prop,
                                QDeclarativeScript::Value *value);
-    bool testQualifiedEnumAssignment(const QMetaProperty &prop,
+    bool testQualifiedEnumAssignment(QDeclarativeScript::Property *prop,
                                      QDeclarativeScript::Object *obj,
                                      QDeclarativeScript::Value *value,
                                      bool *isAssignment);
index fb4f26a..9d18cb3 100644 (file)
@@ -182,7 +182,7 @@ Property *QDeclarativeScript::Object::getProperty(const QString &name, bool crea
 }
 
 QDeclarativeScript::Object::DynamicProperty::DynamicProperty()
-: isDefaultProperty(false), type(Variant), defaultValue(0), nextProperty(0), 
+: isDefaultProperty(false), isReadOnly(false), type(Variant), defaultValue(0), nextProperty(0),
   resolvedCustomTypeName(0)
 {
 }
@@ -225,8 +225,8 @@ int QDeclarativeScript::Object::DynamicSlot::parameterNamesLength() const
 
 QDeclarativeScript::Property::Property()
 : parent(0), type(0), index(-1), value(0), isDefault(true), isDeferred(false), 
-  isValueTypeSubProperty(false), isAlias(false), scriptStringScope(-1), 
-  nextMainProperty(0), nextProperty(0)
+  isValueTypeSubProperty(false), isAlias(false), isReadOnlyDeclaration(false),
+  scriptStringScope(-1), nextMainProperty(0), nextProperty(0)
 {
 }
 
@@ -1028,18 +1028,9 @@ bool ProcessAST::visit(AST::UiPublicMember *node)
             return false;
         }
 
-        if (node->isReadonlyMember) {
-            QDeclarativeError error;
-            error.setDescription(QCoreApplication::translate("QDeclarativeParser","Readonly not yet supported"));
-            error.setLine(node->readonlyToken.startLine);
-            error.setColumn(node->readonlyToken.startColumn);
-            _parser->_errors << error;
-            return false;
-
-        }
-
         Object::DynamicProperty *property = _parser->_pool.New<Object::DynamicProperty>();
         property->isDefaultProperty = node->isDefaultMember;
+        property->isReadOnly = node->isReadonlyMember;
         property->type = type;
         if (type >= Object::DynamicProperty::Custom) {
             QDeclarativeScript::TypeReference *typeRef =
index dbf94b2..a83cd9f 100644 (file)
@@ -279,6 +279,8 @@ public:
     // True if this property is a property alias.  Set by the 
     // QDeclarativeCompiler
     bool isAlias;
+    // True if this is a readonly property declaration
+    bool isReadOnlyDeclaration;
 
     // Used for scriptStringProperties
     int scriptStringScope;
@@ -388,7 +390,9 @@ public:
         enum Type { Var, Variant, Int, Bool, Real, String, Url, Color,
                     Time, Date, DateTime, Alias, Custom, CustomList };
 
-        bool isDefaultProperty;
+        quint32 isDefaultProperty:1;
+        quint32 isReadOnly:1;
+
         Type type;
 
         QHashedStringRef customType;
diff --git a/tests/auto/declarative/qdeclarativeecmascript/data/readonlyDeclaration.qml b/tests/auto/declarative/qdeclarativeecmascript/data/readonlyDeclaration.qml
new file mode 100644 (file)
index 0000000..5377d2d
--- /dev/null
@@ -0,0 +1,45 @@
+import QtQuick 2.0
+
+QtObject {
+    property int dummy: 13
+
+    readonly property int test1: 19
+    readonly property int test2: dummy * 49
+    readonly property alias test3: other.test
+
+    property bool test: false
+
+    property var dummyObj: QtObject {
+        id: other
+        property int test: 9
+    }
+
+    Component.onCompleted: {
+        if (test1 != 19) return;
+        if (test2 != 637) return;
+        if (test3 != 9) return;
+
+        var caught = false;
+
+        caught = false;
+        try { test1 = 13 } catch (e) { caught = true; }
+        if (!caught) return;
+
+        caught = false;
+        try { test2 = 13 } catch (e) { caught = true; }
+        if (!caught) return;
+
+        caught = false;
+        try { test3 = 13 } catch (e) { caught = true; }
+        if (!caught) return;
+
+        other.test = 13;
+        dummy = 9;
+
+        if (test1 != 19) return;
+        if (test2 != 441) return;
+        if (test3 != 13) return;
+
+        test = true;
+    }
+}
index 31aefd2..c92dc80 100644 (file)
@@ -169,7 +169,7 @@ private slots:
     void booleanConversion();
     void handleReferenceManagement();
     void stringArg();
-
+    void readonlyDeclaration();
     void bug1();
     void bug2();
     void dynamicCreationCrash();
@@ -4036,6 +4036,18 @@ void tst_qdeclarativeecmascript::stringArg()
     delete object;
 }
 
+void tst_qdeclarativeecmascript::readonlyDeclaration()
+{
+    QDeclarativeComponent component(&engine, TEST_FILE("readonlyDeclaration.qml"));
+
+    QObject *object = component.create();
+    QVERIFY(object != 0);
+
+    QCOMPARE(object->property("test").toBool(), true);
+
+    delete object;
+}
+
 // Test that assigning a null object works 
 // Regressed with: df1788b4dbbb2826ae63f26bdf166342595343f4
 void tst_qdeclarativeecmascript::nullObjectBinding()
diff --git a/tests/auto/declarative/qdeclarativelanguage/data/ReadOnlyType.qml b/tests/auto/declarative/qdeclarativelanguage/data/ReadOnlyType.qml
new file mode 100644 (file)
index 0000000..456ac76
--- /dev/null
@@ -0,0 +1,5 @@
+import QtQuick 2.0
+
+QtObject {
+    readonly property int readOnlyProperty: 19
+}
diff --git a/tests/auto/declarative/qdeclarativelanguage/data/property.5.errors.txt b/tests/auto/declarative/qdeclarativelanguage/data/property.5.errors.txt
deleted file mode 100644 (file)
index 32a8dc1..0000000
+++ /dev/null
@@ -1 +0,0 @@
-4:5:Readonly not yet supported
diff --git a/tests/auto/declarative/qdeclarativelanguage/data/property.5.qml b/tests/auto/declarative/qdeclarativelanguage/data/property.5.qml
deleted file mode 100644 (file)
index a1401d2..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-import QtQuick 2.0
-
-QtObject {
-    readonly property int a: value
-}
-
index baf4766..e71ae44 100644 (file)
@@ -1 +1 @@
-3:27:Invalid property assignment: "readOnlyEnumProperty" is a read-only property
+2:23:Invalid property assignment: "readOnlyProperty" is a read-only property
index 422d13d..d80b27a 100644 (file)
@@ -1,4 +1,3 @@
-import Test 1.0
-MyTypeObject {
-    readOnlyEnumProperty: MyTypeObject.EnumValue1
+ReadOnlyType {
+    readOnlyProperty: 13
 }
diff --git a/tests/auto/declarative/qdeclarativelanguage/data/readonly.qml b/tests/auto/declarative/qdeclarativelanguage/data/readonly.qml
new file mode 100644 (file)
index 0000000..493a9ad
--- /dev/null
@@ -0,0 +1,17 @@
+import Test 1.0
+
+MyQmlObject {
+    property int testData: 9
+    property alias testData2: myObject.test1
+
+    readonly property int test1: 10
+    readonly property int test2: testData + 9
+    readonly property alias test3: myObject.test1
+
+
+    property variant dummy: MyQmlObject {
+        id: myObject
+        property int test1: 13
+    }
+}
+
index 538ebbb..64ab5d8 100644 (file)
@@ -156,6 +156,7 @@ private slots:
     void inlineAssignmentsOverrideBindings();
     void nestedComponentRoots();
     void registrationOrder();
+    void readonly();
 
     void basicRemote_data();
     void basicRemote();
@@ -364,7 +365,6 @@ void tst_qdeclarativelanguage::errors_data()
     QTest::newRow("property.2") << "property.2.qml" << "property.2.errors.txt" << false;
     QTest::newRow("property.3") << "property.3.qml" << "property.3.errors.txt" << false;
     QTest::newRow("property.4") << "property.4.qml" << "property.4.errors.txt" << false;
-    QTest::newRow("property.5") << "property.5.qml" << "property.5.errors.txt" << false;
     QTest::newRow("property.6") << "property.6.qml" << "property.6.errors.txt" << false;
     QTest::newRow("property.7") << "property.7.qml" << "property.7.errors.txt" << false;
 
@@ -2142,6 +2142,40 @@ void tst_qdeclarativelanguage::registrationOrder()
     delete o;
 }
 
+void tst_qdeclarativelanguage::readonly()
+{
+    QDeclarativeComponent component(&engine, TEST_FILE("readonly.qml"));
+
+    QObject *o = component.create();
+    QVERIFY(o != 0);
+
+    QCOMPARE(o->property("test1").toInt(), 10);
+    QCOMPARE(o->property("test2").toInt(), 18);
+    QCOMPARE(o->property("test3").toInt(), 13);
+
+    o->setProperty("testData", 13);
+
+    QCOMPARE(o->property("test1").toInt(), 10);
+    QCOMPARE(o->property("test2").toInt(), 22);
+    QCOMPARE(o->property("test3").toInt(), 13);
+
+    o->setProperty("testData2", 2);
+
+    QCOMPARE(o->property("test1").toInt(), 10);
+    QCOMPARE(o->property("test2").toInt(), 22);
+    QCOMPARE(o->property("test3").toInt(), 2);
+
+    o->setProperty("test1", 11);
+    o->setProperty("test2", 11);
+    o->setProperty("test3", 11);
+
+    QCOMPARE(o->property("test1").toInt(), 10);
+    QCOMPARE(o->property("test2").toInt(), 22);
+    QCOMPARE(o->property("test3").toInt(), 2);
+
+    delete o;
+}
+
 // QTBUG-18268
 void tst_qdeclarativelanguage::remoteLoadCrash()
 {