aboutsummaryrefslogtreecommitdiffstats
path: root/src/qml/qml/qqmlglobal.cpp
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2022-09-08 14:41:42 +0200
committerVolker Hilsheimer <volker.hilsheimer@qt.io>2022-09-24 07:54:29 +0000
commit3195b44e1c9678584c05ed823aab2eb32518d868 (patch)
tree7c13d0ee0b6f9074841862e160d19d744daeda58 /src/qml/qml/qqmlglobal.cpp
parentb101be9be64b6cc82dc357da0faeffbaab771b8f (diff)
Allow more options for creating value types from JS objects
We allow value types to be created 1. by calling Q_INVOKABLE constructors 2. by setting their values from properties of a JS object Both have to be opted into by setting a class info. If opted into, these options override the existing methods. When a a type can be created by setting its properties, that implies you can also initialize it using an invokable constructor. However, when given a JS object, the properties method is used. We keep this internal and undocumented for now. As the last try (the create(QJSValue) methods and QJSValue ctors) was not that stellar, let's first wait a bit and see if we're getting it right this time around. Fixes: QTBUG-106480 Change-Id: I767230924afcba032d501846cc3263dad57b7bf0 Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
Diffstat (limited to 'src/qml/qml/qqmlglobal.cpp')
-rw-r--r--src/qml/qml/qqmlglobal.cpp325
1 files changed, 321 insertions, 4 deletions
diff --git a/src/qml/qml/qqmlglobal.cpp b/src/qml/qml/qqmlglobal.cpp
index 48244f3259..74eeaee761 100644
--- a/src/qml/qml/qqmlglobal.cpp
+++ b/src/qml/qml/qqmlglobal.cpp
@@ -3,6 +3,7 @@
#include <private/qqmlglobal_p.h>
#include <QtQml/private/qqmlmetatype_p.h>
+#include <QtQml/private/qjsvalue_p.h>
#include <QtQml/qqmlengine.h>
#include <QtCore/qvariant.h>
@@ -12,14 +13,330 @@
QT_BEGIN_NAMESPACE
-bool QQmlValueTypeProvider::createValueType(QMetaType metaType, const QJSValue &s, QVariant &data)
+// Pre-filter the metatype before poking QQmlMetaType::qmlType() and locking its mutex.
+static bool isConstructibleMetaType(const QMetaType metaType)
{
- const QQmlType qmlType = QQmlMetaType::qmlType(metaType);
- if (auto valueTypeFunction = qmlType.createValueTypeFunction()) {
+ switch (metaType.id()) {
+ // The builtins are not constructible this way.
+ case QMetaType::Void:
+ case QMetaType::Nullptr:
+ case QMetaType::QVariant:
+ case QMetaType::Int:
+ case QMetaType::UInt:
+ case QMetaType::LongLong:
+ case QMetaType::ULongLong:
+ case QMetaType::Float:
+ case QMetaType::Double:
+ case QMetaType::Long:
+ case QMetaType::ULong:
+ case QMetaType::Short:
+ case QMetaType::UShort:
+ case QMetaType::Char:
+ case QMetaType::SChar:
+ case QMetaType::UChar:
+ case QMetaType::QChar:
+ case QMetaType::QString:
+ case QMetaType::Bool:
+ case QMetaType::QDateTime:
+ case QMetaType::QDate:
+ case QMetaType::QTime:
+ case QMetaType::QUrl:
+ case QMetaType::QRegularExpression:
+ case QMetaType::QByteArray:
+ case QMetaType::QLocale:
+ return false;
+ default:
+ break;
+ }
+
+ // QJSValue is also builtin
+ if (metaType == QMetaType::fromType<QJSValue>())
+ return false;
+
+ // We also don't want to construct pointers of any kind, or lists, or enums.
+ if (metaType.flags() &
+ (QMetaType::PointerToQObject
+ | QMetaType::IsEnumeration
+ | QMetaType::SharedPointerToQObject
+ | QMetaType::WeakPointerToQObject
+ | QMetaType::TrackingPointerToQObject
+ | QMetaType::IsUnsignedEnumeration
+ | QMetaType::PointerToGadget
+ | QMetaType::IsPointer
+ | QMetaType::IsQmlList)) {
+ return false;
+ }
+
+ return true;
+}
+
+static void callConstructor(
+ const QMetaObject *mo, int i, void *parameter, QMetaType metaType, void *data)
+{
+ // Unfortunately CreateInstance unconditionally creates the instance on the heap.
+ void *gadget = nullptr;
+ void *p[] = { &gadget, parameter };
+ mo->static_metacall(QMetaObject::CreateInstance, i, p);
+ Q_ASSERT(gadget);
+ metaType.destruct(data);
+ metaType.construct(data, gadget);
+ metaType.destroy(gadget);
+}
+
+static bool fromMatchingType(
+ const QMetaObject *mo, const QV4::Value &s, const QMetaType metaType, void *data)
+{
+ for (int i = 0, end = mo->constructorCount(); i < end; ++i) {
+ const QMetaMethod ctor = mo->constructor(i);
+ if (ctor.parameterCount() != 1)
+ continue;
+
+ const QMetaType parameterType = ctor.parameterMetaType(0);
+ QVariant parameter = QV4::ExecutionEngine::toVariant(s, parameterType);
+ if (parameter.metaType() == parameterType) {
+ callConstructor(mo, i, parameter.data(), metaType, data);
+ return true;
+ }
+
+ QVariant converted(parameterType);
+ if (QQmlValueTypeProvider::createValueType(s, parameterType, converted.data())) {
+ callConstructor(mo, i, converted.data(), metaType, data);
+ return true;
+ }
+
+ if (QMetaType::convert(parameter.metaType(), parameter.constData(),
+ parameterType, converted.data())) {
+ callConstructor(mo, i, converted.data(), metaType, data);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool fromMatchingType(
+ const QMetaObject *mo, QVariant s, const QMetaType metaType, void *data)
+{
+ const QMetaType sourceMetaType = s.metaType();
+ if (sourceMetaType == QMetaType::fromType<QJSValue>()) {
+ QJSValue val = s.value<QJSValue>();
+ return fromMatchingType(
+ mo, QV4::Value(QJSValuePrivate::asReturnedValue(&val)), metaType, data);
+ }
+
+ for (int i = 0, end = mo->constructorCount(); i < end; ++i) {
+ const QMetaMethod ctor = mo->constructor(i);
+ if (ctor.parameterCount() != 1)
+ continue;
+
+ const QMetaType parameterType = ctor.parameterMetaType(0);
+ if (sourceMetaType == parameterType) {
+ callConstructor(mo, i, s.data(), metaType, data);
+ return true;
+ }
+
+ QVariant parameter(parameterType);
+ if (QQmlValueTypeProvider::createValueType(s, parameterType, parameter.data())) {
+ callConstructor(mo, i, parameter.data(), metaType, data);
+ return true;
+ }
+
+ // At this point, s should be a builtin type. For builtin types
+ // the QMetaType converters are good enough.
+ if (QMetaType::convert(sourceMetaType, s.constData(), parameterType, parameter.data())) {
+ callConstructor(mo, i, s.data(), metaType, data);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool fromString(
+ const QMetaObject *mo, QString s, const QMetaType metaType, void *data)
+{
+ for (int i = 0, end = mo->constructorCount(); i < end; ++i) {
+ const QMetaMethod ctor = mo->constructor(i);
+ if (ctor.parameterCount() != 1)
+ continue;
+
+ if (ctor.parameterMetaType(0) == QMetaType::fromType<QString>()) {
+ callConstructor(mo, i, &s, metaType, data);
+ return true;
+ }
+ }
+
+
+ return false;
+}
+
+static bool byProperties(
+ const QMetaObject *mo, const QV4::Value &s, void *data)
+{
+ if (!s.isObject())
+ return false;
+
+ if (!mo)
+ return false;
+
+ const QV4::Object *o = static_cast<const QV4::Object *>(&s);
+ QV4::Scope scope(o->engine());
+ QV4::ScopedObject object(scope, o);
+
+ for (int i = 0; i < mo->propertyCount(); ++i) {
+ const QMetaProperty metaProperty = mo->property(i);
+ const QString propertyName = QString::fromUtf8(metaProperty.name());
+
+ QV4::ScopedString v4PropName(scope, scope.engine->newString(propertyName));
+ QV4::ScopedValue v4PropValue(scope, object->get(v4PropName));
+
+ // We assume that data is freshly constructed.
+ // There is no point in reset()'ing properties of a freshly created object.
+ if (v4PropValue->isUndefined())
+ continue;
+
+ const QMetaType propertyType = metaProperty.metaType();
+ QVariant property = QV4::ExecutionEngine::toVariant(v4PropValue, propertyType);
+ if (property.metaType() == propertyType) {
+ metaProperty.writeOnGadget(data, property);
+ continue;
+ }
+
+ QVariant converted(propertyType);
+ if (QQmlValueTypeProvider::createValueType(v4PropValue, propertyType, converted.data())) {
+ metaProperty.writeOnGadget(data, converted);
+ continue;
+ }
+
+ if (QMetaType::convert(property.metaType(), property.constData(),
+ propertyType, converted.data())) {
+ metaProperty.writeOnGadget(data, converted);
+ continue;
+ }
+
+ qWarning().noquote()
+ << QLatin1String("Could not convert %1 to %2 for property %3")
+ .arg(v4PropValue->toQStringNoThrow(), QString::fromUtf8(propertyType.name()),
+ propertyName);
+ }
+ return true;
+}
+
+static bool byProperties(
+ const QMetaObject *mo, const QVariant &s, void *data)
+{
+ if (!mo)
+ return false;
+
+ if (s.metaType() == QMetaType::fromType<QJSValue>()) {
+ QJSValue val = s.value<QJSValue>();
+ return byProperties(mo, QV4::Value(QJSValuePrivate::asReturnedValue(&val)), data);
+ }
+
+ return false;
+}
+
+static bool fromJSValue(
+ const QQmlType &type, const QJSValue &s, QMetaType metaType, void *data)
+{
+ if (const auto valueTypeFunction = type.createValueTypeFunction()) {
QVariant result = valueTypeFunction(s);
if (result.metaType() == metaType) {
- data = std::move(result);
+ metaType.destruct(data);
+ metaType.construct(data, result.constData());
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool QQmlValueTypeProvider::constructFromJSValue(
+ const QJSValue &s, QMetaType metaType, void *data)
+{
+ return isConstructibleMetaType(metaType)
+ && fromJSValue(QQmlMetaType::qmlType(metaType), s, metaType, data);
+}
+
+bool QQmlValueTypeProvider::createValueType(
+ const QString &s, QMetaType metaType, void *data)
+{
+ if (!isConstructibleMetaType(metaType))
+ return false;
+ const QQmlType type = QQmlMetaType::qmlType(metaType);
+ const QMetaObject *mo = QQmlMetaType::metaObjectForValueType(type);
+ if (mo && type.canConstructValueType()) {
+ if (fromString(mo, s, metaType, data))
+ return true;
+ }
+
+ return fromJSValue(type, s, metaType, data);
+}
+
+bool QQmlValueTypeProvider::createValueType(
+ const QJSValue &s, QMetaType metaType, void *data)
+{
+ if (!isConstructibleMetaType(metaType))
+ return false;
+ const QQmlType type = QQmlMetaType::qmlType(metaType);
+ if (const QMetaObject *mo = QQmlMetaType::metaObjectForValueType(type)) {
+ if (type.canPopulateValueType()
+ && byProperties(mo, QV4::Value(QJSValuePrivate::asReturnedValue(&s)), data)) {
+ return true;
+ }
+
+ if (type.canConstructValueType()
+ && fromMatchingType(mo, QV4::Value(QJSValuePrivate::asReturnedValue(&s)),
+ metaType, data)) {
+ return true;
+ }
+ }
+
+ return constructFromJSValue(s, metaType, data);
+}
+
+bool QQmlValueTypeProvider::createValueType(
+ const QV4::Value &s, QMetaType metaType, void *data)
+{
+ if (!isConstructibleMetaType(metaType))
+ return false;
+ const QQmlType type = QQmlMetaType::qmlType(metaType);
+ if (const QMetaObject *mo = QQmlMetaType::metaObjectForValueType(type)) {
+ if (type.canPopulateValueType() && byProperties(mo, s, data))
+ return true;
+ if (type.canConstructValueType()) {
+ if (fromMatchingType(mo, s, metaType, data))
+ return true;
+ qWarning().noquote()
+ << "Could not find any constructor for value type"
+ << mo->className() << "to call with value" << s.toQStringNoThrow();
+ }
+ }
+
+ return constructFromJSValue(
+ QJSValuePrivate::fromReturnedValue(s.asReturnedValue()), metaType, data);
+
+}
+
+/*!
+ * \internal
+ * This should only be called with either builtin types or wrapped QJSValues as source.
+ */
+bool QQmlValueTypeProvider::createValueType(
+ const QVariant &s, QMetaType metaType, void *data)
+{
+ if (!isConstructibleMetaType(metaType))
+ return false;
+ const QQmlType type = QQmlMetaType::qmlType(metaType);
+ if (const QMetaObject *mo = QQmlMetaType::metaObjectForValueType(type)) {
+ if (type.canPopulateValueType() && byProperties(mo, s, data))
return true;
+ if (type.canConstructValueType()) {
+ if (fromMatchingType(mo, s, metaType, data))
+ return true;
+ qWarning().noquote()
+ << "Could not find any constructor for value type"
+ << mo->className() << "to call with value" << s;
}
}