aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2018-12-20 18:58:10 +0100
committerUlf Hermann <ulf.hermann@qt.io>2019-01-21 08:40:33 +0000
commit0b863bca305ad43b3c48287ec4e62e262c8e8849 (patch)
treedb0e7a1f2ae4aed7ee7cc66351ecc04d719cbff7
parent9d0ce63d3297bbc1ecfb29b26ffafa80d6a28fb0 (diff)
QML: Special case null as binding type
This gives us the opportunity to map the JavaScript null to QVariant's concept of isNull(). [ChangeLog][QML] Assigning JavaScript null to incompatibly typed properties generates a compile error now, rather than a runtime error. Fixes: QTBUG-72098 Change-Id: I72fd1c30d84128c774230eaaea10455b2a0e064c Reviewed-by: Simon Hausmann <simon.hausmann@qt.io>
-rw-r--r--src/qml/compiler/qqmlirbuilder.cpp3
-rw-r--r--src/qml/compiler/qqmlpropertyvalidator.cpp3
-rw-r--r--src/qml/compiler/qv4compileddata.cpp2
-rw-r--r--src/qml/compiler/qv4compileddata_p.h2
-rw-r--r--src/qml/qml/qqmlobjectcreator.cpp19
-rw-r--r--src/qml/types/qqmllistmodel.cpp2
-rw-r--r--src/quick/util/qquickpropertychanges.cpp3
-rw-r--r--tests/auto/qml/qqmlcomponent/data/createObjectWithScript.qml2
-rw-r--r--tests/auto/qml/qqmlecmascript/data/qmlVarNullBinding.qml7
-rw-r--r--tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp7
-rw-r--r--tests/auto/qml/qqmllanguage/data/objectDeletionNotify.1.qml2
-rw-r--r--tests/auto/qml/qqmllanguage/data/objectDeletionNotify.2.qml2
-rw-r--r--tests/auto/qml/qqmllanguage/data/objectDeletionNotify.3.qml2
13 files changed, 52 insertions, 4 deletions
diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp
index 84fd66b114..be5e0480b8 100644
--- a/src/qml/compiler/qqmlirbuilder.cpp
+++ b/src/qml/compiler/qqmlirbuilder.cpp
@@ -1083,6 +1083,9 @@ void IRBuilder::setBindingValue(QV4::CompiledData::Binding *binding, QQmlJS::AST
binding->type = QV4::CompiledData::Binding::Type_Number;
binding->value.constantValueIndex = jsGenerator->registerConstant(QV4::Encode(-lit->value));
}
+ } else if (QQmlJS::AST::cast<QQmlJS::AST::NullExpression *>(expr)) {
+ binding->type = QV4::CompiledData::Binding::Type_Null;
+ binding->value.nullMarker = 0;
}
}
diff --git a/src/qml/compiler/qqmlpropertyvalidator.cpp b/src/qml/compiler/qqmlpropertyvalidator.cpp
index 4cb922ca8d..3b97e364e1 100644
--- a/src/qml/compiler/qqmlpropertyvalidator.cpp
+++ b/src/qml/compiler/qqmlpropertyvalidator.cpp
@@ -575,6 +575,9 @@ QQmlCompileError QQmlPropertyValidator::validateLiteralBinding(QQmlPropertyCache
break;
} else if (property->propType() == qMetaTypeId<QQmlScriptString>()) {
break;
+ } else if (property->isQObject()
+ && binding->type == QV4::CompiledData::Binding::Type_Null) {
+ break;
}
// otherwise, try a custom type assignment
diff --git a/src/qml/compiler/qv4compileddata.cpp b/src/qml/compiler/qv4compileddata.cpp
index e68a563a45..9379160a65 100644
--- a/src/qml/compiler/qv4compileddata.cpp
+++ b/src/qml/compiler/qv4compileddata.cpp
@@ -745,6 +745,8 @@ QString Binding::valueAsString(const CompilationUnit *unit) const
case Type_Script:
case Type_String:
return unit->stringAt(stringIndex);
+ case Type_Null:
+ return QStringLiteral("null");
case Type_Boolean:
return value.b ? QStringLiteral("true") : QStringLiteral("false");
case Type_Number:
diff --git a/src/qml/compiler/qv4compileddata_p.h b/src/qml/compiler/qv4compileddata_p.h
index 7c26b0b67d..5732855cc9 100644
--- a/src/qml/compiler/qv4compileddata_p.h
+++ b/src/qml/compiler/qv4compileddata_p.h
@@ -440,6 +440,7 @@ struct Q_QML_PRIVATE_EXPORT Binding
Type_Boolean,
Type_Number,
Type_String,
+ Type_Null,
Type_Translation,
Type_TranslationById,
Type_Script,
@@ -471,6 +472,7 @@ struct Q_QML_PRIVATE_EXPORT Binding
quint32_le compiledScriptIndex; // used when Type_Script
quint32_le objectIndex;
quint32_le translationDataIndex; // used when Type_Translation
+ quint32 nullMarker;
} value;
quint32_le stringIndex; // Set for Type_String and Type_Script (the latter because of script strings)
diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp
index 68e2c2c928..d015a2c0e8 100644
--- a/src/qml/qml/qqmlobjectcreator.cpp
+++ b/src/qml/qml/qqmlobjectcreator.cpp
@@ -381,6 +381,16 @@ void QQmlObjectCreator::setPropertyValue(const QQmlPropertyData *property, const
}
}
+ if (property->isQObject()) {
+ if (binding->type == QV4::CompiledData::Binding::Type_Null) {
+ QObject *value = nullptr;
+ const bool ok = property->writeProperty(_qobject, &value, propertyWriteFlags);
+ Q_ASSERT(ok);
+ Q_UNUSED(ok);
+ return;
+ }
+ }
+
switch (propertyType) {
case QMetaType::QVariant: {
if (binding->type == QV4::CompiledData::Binding::Type_Number) {
@@ -408,6 +418,13 @@ void QQmlObjectCreator::setPropertyValue(const QQmlPropertyData *property, const
QVariant value(binding->valueAsBoolean());
property->writeProperty(_qobject, &value, propertyWriteFlags);
}
+ } else if (binding->type == QV4::CompiledData::Binding::Type_Null) {
+ if (property->isVarProperty()) {
+ _vmeMetaObject->setVMEProperty(property->coreIndex(), QV4::Value::nullValue());
+ } else {
+ QVariant nullValue = QVariant::fromValue(nullptr);
+ property->writeProperty(_qobject, &nullValue, propertyWriteFlags);
+ }
} else {
QString stringValue = binding->valueAsString(compilationUnit.data());
if (property->isVarProperty()) {
@@ -663,6 +680,8 @@ void QQmlObjectCreator::setPropertyValue(const QQmlPropertyData *property, const
value = QJSValue(int(n));
} else
value = QJSValue(n);
+ } else if (binding->type == QV4::CompiledData::Binding::Type_Null) {
+ value = QJSValue::NullValue;
} else {
value = QJSValue(binding->valueAsString(compilationUnit.data()));
}
diff --git a/src/qml/types/qqmllistmodel.cpp b/src/qml/types/qqmllistmodel.cpp
index debf14df97..27171b9bd4 100644
--- a/src/qml/types/qqmllistmodel.cpp
+++ b/src/qml/types/qqmllistmodel.cpp
@@ -2758,6 +2758,8 @@ bool QQmlListModelParser::applyProperty(const QQmlRefPointer<QV4::CompiledData::
value = binding->valueAsNumber(compilationUnit->constants);
} else if (binding->type == QV4::CompiledData::Binding::Type_Boolean) {
value = binding->valueAsBoolean();
+ } else if (binding->type == QV4::CompiledData::Binding::Type_Null) {
+ value = QVariant::fromValue(nullptr);
} else if (binding->type == QV4::CompiledData::Binding::Type_Script) {
QString scriptStr = binding->valueAsScriptString(compilationUnit.data());
if (definesEmptyList(scriptStr)) {
diff --git a/src/quick/util/qquickpropertychanges.cpp b/src/quick/util/qquickpropertychanges.cpp
index 8e2ac32ace..d739c8a017 100644
--- a/src/quick/util/qquickpropertychanges.cpp
+++ b/src/quick/util/qquickpropertychanges.cpp
@@ -336,6 +336,9 @@ void QQuickPropertyChangesPrivate::decodeBinding(const QString &propertyPrefix,
case QV4::CompiledData::Binding::Type_Boolean:
var = binding->valueAsBoolean();
break;
+ case QV4::CompiledData::Binding::Type_Null:
+ var = QVariant::fromValue(nullptr);
+ break;
default:
break;
}
diff --git a/tests/auto/qml/qqmlcomponent/data/createObjectWithScript.qml b/tests/auto/qml/qqmlcomponent/data/createObjectWithScript.qml
index 989b295cb5..c5ebed612c 100644
--- a/tests/auto/qml/qqmlcomponent/data/createObjectWithScript.qml
+++ b/tests/auto/qml/qqmlcomponent/data/createObjectWithScript.qml
@@ -18,7 +18,7 @@ Item{
id: b
Item{
property bool testBool: false
- property int testInt: null
+ property int testInt: { return null; }
property QtObject testObject: null
}
}
diff --git a/tests/auto/qml/qqmlecmascript/data/qmlVarNullBinding.qml b/tests/auto/qml/qqmlecmascript/data/qmlVarNullBinding.qml
new file mode 100644
index 0000000000..6666b85ffd
--- /dev/null
+++ b/tests/auto/qml/qqmlecmascript/data/qmlVarNullBinding.qml
@@ -0,0 +1,7 @@
+import QtQml 2.2
+
+QtObject {
+ property var foo: null
+ property bool signalSeen: false
+ onFooChanged: signalSeen = true
+}
diff --git a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
index 085cd5ffd0..e247ed4eff 100644
--- a/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
+++ b/tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp
@@ -5991,6 +5991,13 @@ void tst_qqmlecmascript::nullObjectInitializer()
QVERIFY(value.userType() == qMetaTypeId<QObject*>());
QVERIFY(!value.value<QObject*>());
}
+
+ {
+ QQmlComponent component(&engine, testFileUrl("qmlVarNullBinding.qml"));
+ QScopedPointer<QObject> obj(component.create());
+ QVERIFY(!obj.isNull());
+ QVERIFY(!obj->property("signalSeen").toBool());
+ }
}
// Test that bindings don't evaluate once the engine has been destroyed
diff --git a/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.1.qml b/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.1.qml
index acd5463a3c..ac5622f9fb 100644
--- a/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.1.qml
+++ b/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.1.qml
@@ -10,7 +10,7 @@ Item {
}
}
- property bool expectNull: null
+ property bool expectNull: { return null; }
function setExpectNull(b) {
success = false;
diff --git a/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.2.qml b/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.2.qml
index ed0e0d10f0..3c18739c32 100644
--- a/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.2.qml
+++ b/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.2.qml
@@ -10,7 +10,7 @@ Item {
}
}
- property bool expectNull: null
+ property bool expectNull: { return null; }
function setExpectNull(b) {
success = false;
diff --git a/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.3.qml b/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.3.qml
index f5e94ba715..e2e560199f 100644
--- a/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.3.qml
+++ b/tests/auto/qml/qqmllanguage/data/objectDeletionNotify.3.qml
@@ -10,7 +10,7 @@ Item {
}
}
- property bool expectNull: null
+ property bool expectNull: { return null; }
function setExpectNull(b) {
success = false;