diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2022-09-08 14:41:42 +0200 |
---|---|---|
committer | Volker Hilsheimer <volker.hilsheimer@qt.io> | 2022-09-24 07:54:29 +0000 |
commit | 3195b44e1c9678584c05ed823aab2eb32518d868 (patch) | |
tree | 7c13d0ee0b6f9074841862e160d19d744daeda58 /src/qml/qml/qqmlglobal.cpp | |
parent | b101be9be64b6cc82dc357da0faeffbaab771b8f (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.cpp | 325 |
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; } } |