diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2018-12-20 18:58:10 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2019-01-21 08:40:33 +0000 |
commit | 0b863bca305ad43b3c48287ec4e62e262c8e8849 (patch) | |
tree | db0e7a1f2ae4aed7ee7cc66351ecc04d719cbff7 | |
parent | 9d0ce63d3297bbc1ecfb29b26ffafa80d6a28fb0 (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.cpp | 3 | ||||
-rw-r--r-- | src/qml/compiler/qqmlpropertyvalidator.cpp | 3 | ||||
-rw-r--r-- | src/qml/compiler/qv4compileddata.cpp | 2 | ||||
-rw-r--r-- | src/qml/compiler/qv4compileddata_p.h | 2 | ||||
-rw-r--r-- | src/qml/qml/qqmlobjectcreator.cpp | 19 | ||||
-rw-r--r-- | src/qml/types/qqmllistmodel.cpp | 2 | ||||
-rw-r--r-- | src/quick/util/qquickpropertychanges.cpp | 3 | ||||
-rw-r--r-- | tests/auto/qml/qqmlcomponent/data/createObjectWithScript.qml | 2 | ||||
-rw-r--r-- | tests/auto/qml/qqmlecmascript/data/qmlVarNullBinding.qml | 7 | ||||
-rw-r--r-- | tests/auto/qml/qqmlecmascript/tst_qqmlecmascript.cpp | 7 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/data/objectDeletionNotify.1.qml | 2 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/data/objectDeletionNotify.2.qml | 2 | ||||
-rw-r--r-- | tests/auto/qml/qqmllanguage/data/objectDeletionNotify.3.qml | 2 |
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; |