v4: Enable primitive conversation to QQmlScriptString in javascript
authorSebastian Sauer <sebastian.sauer.ford@kdab.com>
Tue, 12 Aug 2014 11:52:50 +0000 (18:52 +0700)
committerSebastian Sauer <sebastian.sauer@kdab.com>
Fri, 22 Aug 2014 18:46:08 +0000 (20:46 +0200)
This makes following QML-code proper working:

ParentChange {
  x: 0
  Component.onCompleted: x = 10
}

where x is a QQmlScriptString.

Before this patch an error-message would be thrown that the
bool/int/string/etc cannot be converted to a QQmlScriptString.

With the patch primitive types including null and undefined are
proper converted to a QQmlScriptString. The patch ignores (as
in not implements) function/binding assignment.

Unfortunately since commit aa25ad8d5f4 its not possible any
longer to instanciate QQmlScriptString what means there is
otherwise no (easy) way to inject a QQmlScriptString from
within Javascript.

Change-Id: I18aac6a6e9a57f3b7d0a2d66cdab2be6c3c153c5
Reviewed-by: Simon Hausmann <simon.hausmann@digia.com>
src/qml/compiler/qv4compileddata.cpp
src/qml/compiler/qv4compileddata_p.h
src/qml/jsruntime/qv4qobjectwrapper.cpp
src/qml/qml/qqmlscriptstring.h
tests/auto/qml/qqmllanguage/data/scriptStringJs.qml [new file with mode: 0644]
tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp

index cd9d8fe..01561eb 100644 (file)
@@ -233,7 +233,7 @@ QString Binding::valueAsString(const Unit *unit) const
 }
 
 //reverse of Lexer::singleEscape()
-static QString escapedString(const QString &string)
+QString Binding::escapedString(const QString &string)
 {
     QString tmp = QLatin1String("\"");
     for (int i = 0; i < string.length(); ++i) {
index 869caad..78b7b46 100644 (file)
@@ -284,6 +284,8 @@ struct Q_QML_PRIVATE_EXPORT Binding
         return false;
     }
 
+    static QString escapedString(const QString &string);
+
     bool evaluatesToString() const { return type == Type_String || type == Type_Translation || type == Type_TranslationById; }
 
     QString valueAsString(const Unit *unit) const;
index e12b8f1..c5c04c0 100644 (file)
@@ -65,6 +65,8 @@
 #include <private/qv4regexpobject_p.h>
 #include <private/qv4scopedvalue_p.h>
 #include <private/qv4mm_p.h>
+#include <private/qqmlscriptstring_p.h>
+#include <private/qv4compileddata_p.h>
 
 #include <QtQml/qjsvalue.h>
 #include <QtCore/qjsonarray.h>
@@ -511,7 +513,7 @@ void QObjectWrapper::setProperty(QObject *object, ExecutionContext *ctx, QQmlPro
         PROPERTY_STORE(QJsonValue, QJsonValue(QJsonValue::Undefined));
     } else if (!newBinding && property->propType == qMetaTypeId<QJSValue>()) {
         PROPERTY_STORE(QJSValue, new QJSValuePrivate(ctx->d()->engine, value));
-    } else if (value->isUndefined()) {
+    } else if (value->isUndefined() && property->propType != qMetaTypeId<QQmlScriptString>()) {
         QString error = QLatin1String("Cannot assign [undefined] to ");
         if (!QMetaType::typeName(property->propType))
             error += QLatin1String("[unknown property type]");
@@ -535,6 +537,16 @@ void QObjectWrapper::setProperty(QObject *object, ExecutionContext *ctx, QQmlPro
         QQmlVMEMetaObject *vmemo = QQmlVMEMetaObject::get(object);
         Q_ASSERT(vmemo);
         vmemo->setVMEProperty(property->coreIndex, value);
+    } else if (property->propType == qMetaTypeId<QQmlScriptString>() && (value->isUndefined() || value->isPrimitive())) {
+        QQmlScriptString ss(value->toQStringNoThrow(), 0 /* context */, object);
+        if (value->isNumber()) {
+            ss.d->numberValue = value->toNumber();
+            ss.d->isNumberLiteral = true;
+        } else if (value->isString()) {
+            ss.d->script = QV4::CompiledData::Binding::escapedString(ss.d->script);
+            ss.d->isStringLiteral = true;
+        }
+        PROPERTY_STORE(QQmlScriptString, ss);
     } else {
         QVariant v;
         if (property->isQList())
index 2c10df6..d85df0e 100644 (file)
@@ -54,6 +54,9 @@ class QObject;
 class QQmlContext;
 class QQmlScriptStringPrivate;
 class QQmlObjectCreator;
+namespace QV4 {
+    struct QObjectWrapper;
+}
 class Q_QML_EXPORT QQmlScriptString
 {
 public:
@@ -79,6 +82,7 @@ private:
     friend class QQmlScriptStringPrivate;
     friend class QQmlExpression;
     friend class QQmlBinding;
+    friend struct QV4::QObjectWrapper;
 };
 
 QT_END_NAMESPACE
diff --git a/tests/auto/qml/qqmllanguage/data/scriptStringJs.qml b/tests/auto/qml/qqmllanguage/data/scriptStringJs.qml
new file mode 100644 (file)
index 0000000..bf6a8b9
--- /dev/null
@@ -0,0 +1,5 @@
+import Test 1.0
+
+MyTypeObject {
+    scriptProperty: " hello \" world "
+}
index bba558d..adf3f09 100644 (file)
@@ -146,6 +146,7 @@ private slots:
     void onCompleted();
     void onDestruction();
     void scriptString();
+    void scriptStringJs();
     void scriptStringWithoutSourceCode();
     void defaultPropertyListOrder();
     void declaredPropertyValues();
@@ -1946,6 +1947,84 @@ void tst_qqmllanguage::scriptString()
     }
 }
 
+// Check that assignments to QQmlScriptString properties works also from within Javascript
+void tst_qqmllanguage::scriptStringJs()
+{
+    QQmlComponent component(&engine, testFileUrl("scriptStringJs.qml"));
+    VERIFY_ERRORS(0);
+
+    MyTypeObject *object = qobject_cast<MyTypeObject*>(component.create());
+    QVERIFY(object != 0);
+    QQmlContext *context = QQmlEngine::contextForObject(object);
+    QVERIFY(context != 0);
+    bool ok;
+
+    QCOMPARE(QQmlScriptStringPrivate::get(object->scriptProperty())->script, QString("\" hello \\\" world \""));
+    QVERIFY(!object->scriptProperty().isEmpty());
+    QVERIFY(!object->scriptProperty().isUndefinedLiteral());
+    QVERIFY(!object->scriptProperty().isNullLiteral());
+    QCOMPARE(object->scriptProperty().stringLiteral(), QString(" hello \\\" world "));
+    QVERIFY(object->scriptProperty().numberLiteral(&ok) == 0.0 && !ok);
+    QVERIFY(!object->scriptProperty().booleanLiteral(&ok) && !ok);
+
+    QJSValue inst = engine.newQObject(object);
+    QJSValue func = engine.evaluate("function(value) { this.scriptProperty = value }");
+
+    func.callWithInstance(inst, QJSValueList() << "test a \"string ");
+    QCOMPARE(QQmlScriptStringPrivate::get(object->scriptProperty())->script, QString("\"test a \\\"string \""));
+    QVERIFY(!object->scriptProperty().isEmpty());
+    QVERIFY(!object->scriptProperty().isUndefinedLiteral());
+    QVERIFY(!object->scriptProperty().isNullLiteral());
+    QCOMPARE(object->scriptProperty().stringLiteral(), QString("test a \\\"string "));
+    QVERIFY(object->scriptProperty().numberLiteral(&ok) == 0.0 && !ok);
+    QVERIFY(!object->scriptProperty().booleanLiteral(&ok) && !ok);
+
+    func.callWithInstance(inst, QJSValueList() << QJSValue::UndefinedValue);
+    QCOMPARE(QQmlScriptStringPrivate::get(object->scriptProperty())->script, QString("undefined"));
+    QVERIFY(!object->scriptProperty().isEmpty());
+    QVERIFY(object->scriptProperty().isUndefinedLiteral());
+    QVERIFY(!object->scriptProperty().isNullLiteral());
+    QVERIFY(object->scriptProperty().stringLiteral().isEmpty());
+    QVERIFY(object->scriptProperty().numberLiteral(&ok) == 0.0 && !ok);
+    QVERIFY(!object->scriptProperty().booleanLiteral(&ok) && !ok);
+
+    func.callWithInstance(inst, QJSValueList() << true);
+    QCOMPARE(QQmlScriptStringPrivate::get(object->scriptProperty())->script, QString("true"));
+    QVERIFY(!object->scriptProperty().isEmpty());
+    QVERIFY(!object->scriptProperty().isUndefinedLiteral());
+    QVERIFY(!object->scriptProperty().isNullLiteral());
+    QVERIFY(object->scriptProperty().stringLiteral().isEmpty());
+    QVERIFY(object->scriptProperty().numberLiteral(&ok) == 0.0 && !ok);
+    QVERIFY(object->scriptProperty().booleanLiteral(&ok) && ok);
+
+    func.callWithInstance(inst, QJSValueList() << false);
+    QCOMPARE(QQmlScriptStringPrivate::get(object->scriptProperty())->script, QString("false"));
+    QVERIFY(!object->scriptProperty().isEmpty());
+    QVERIFY(!object->scriptProperty().isUndefinedLiteral());
+    QVERIFY(!object->scriptProperty().isNullLiteral());
+    QVERIFY(object->scriptProperty().stringLiteral().isEmpty());
+    QVERIFY(object->scriptProperty().numberLiteral(&ok) == 0.0 && !ok);
+    QVERIFY(!object->scriptProperty().booleanLiteral(&ok) && ok);
+
+    func.callWithInstance(inst, QJSValueList() << QJSValue::NullValue);
+    QCOMPARE(QQmlScriptStringPrivate::get(object->scriptProperty())->script, QString("null"));
+    QVERIFY(!object->scriptProperty().isEmpty());
+    QVERIFY(!object->scriptProperty().isUndefinedLiteral());
+    QVERIFY(object->scriptProperty().isNullLiteral());
+    QVERIFY(object->scriptProperty().stringLiteral().isEmpty());
+    QVERIFY(object->scriptProperty().numberLiteral(&ok) == 0.0 && !ok);
+    QVERIFY(!object->scriptProperty().booleanLiteral(&ok) && !ok);
+
+    func.callWithInstance(inst, QJSValueList() << 12.34);
+    QCOMPARE(QQmlScriptStringPrivate::get(object->scriptProperty())->script, QString("12.34"));
+    QVERIFY(!object->scriptProperty().isEmpty());
+    QVERIFY(!object->scriptProperty().isUndefinedLiteral());
+    QVERIFY(!object->scriptProperty().isNullLiteral());
+    QVERIFY(object->scriptProperty().stringLiteral().isEmpty());
+    QVERIFY(object->scriptProperty().numberLiteral(&ok) == 12.34 && ok);
+    QVERIFY(!object->scriptProperty().booleanLiteral(&ok) && !ok);
+}
+
 void tst_qqmllanguage::scriptStringWithoutSourceCode()
 {
     QUrl url = testFileUrl("scriptString7.qml");