diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2022-01-07 11:01:56 +0100 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2022-01-28 15:03:00 +0100 |
commit | b0fc028cb5a5dfa9e95640a32e9b38ca6df0734d (patch) | |
tree | 7a446acca0f5bcbe4e62a1ac22bdb0185913bc5a | |
parent | 8c4c0605b077d63e3d73d34ad6dcc4a2cf607b4c (diff) |
QML: Allow named lists of value types
We register QList<T> as sequential container type for any value type T
we get. This way we can always find a type to use for list<t> with t
being a value type. The metatypes are shuffled around so that we have an
easier time associating a type with its list and vice versa.
As QQmlPropertyData's isQList flag denotes both QQmlListProperty<T> and
QList<T> now, we need to use QMetaType::IsQmlList more often.
Conversely, any name given to extra sequential containers registered via
QML_SEQUENTIAL_CONTAINER is explicitly ignored now. As you can do
list<foo> for any type foo now, there is not much of a point in having
further named container registrations for the same type. It would just
make things more complicated. Mind that the name had already been
ignored before, just not explicitly.
[ChangeLog][QtQml] You can now use lists of value types in QML. For
example a property of type list<int> will hold a list of integers.
Task-number: QTBUG-82443
Change-Id: I7bee61cee3963dae5d231bf59f70b8012984371d
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
26 files changed, 614 insertions, 364 deletions
diff --git a/src/qml/doc/src/qmllanguageref/typesystem/valuetypes.qdoc b/src/qml/doc/src/qmllanguageref/typesystem/valuetypes.qdoc index 804a4c5596..53fe1c2875 100644 --- a/src/qml/doc/src/qmllanguageref/typesystem/valuetypes.qdoc +++ b/src/qml/doc/src/qmllanguageref/typesystem/valuetypes.qdoc @@ -307,7 +307,7 @@ property is only invoked when the property is reassigned to a different object v \ingroup qmlvaluetypes \brief a list of QML objects. - The \c list type refers to a list of QML objects. + The \c list type refers to a list of QML objects or values. A list value can be accessed in a similar way to a JavaScript array: @@ -320,14 +320,14 @@ property is only invoked when the property is reassigned to a different object v Values can be dynamically added to the list by using the \c push method, as if it were a JavaScript Array - A \c list can only store QML objects, and cannot contain any - \l {QML Value Types}{value type} values. (To store value types within a - list, use the \l var type instead.) + A \c list can store QML objects or \l{QML Value Types}{value type} values. When integrating with C++, note that any QQmlListProperty value \l{qtqml-cppintegration-data.html}{passed into QML from C++} is automatically converted into a \c list value, and vice-versa. + Similarly any \c{QList<T>} of a registered value type \c{T} is automatically + converted into a \c list value, and vice-versa. \section1 Using the list Type @@ -367,13 +367,17 @@ property is only invoked when the property is reassigned to a different object v } \endqml - Objects in a list can be replaced with the \c{[]} operator, just like - entries of JavaScript arrays. You can also use \c{push()} to append entries, - or you can set the \c length property of the list to truncate or extend it. - You can not automatically extend the list by assigning to an index currently - out of range, though. Furthermore, if you insert \c null values into the - list, those are converted to \c nullptr entries in the underlying - QQmlListProperty. + Objects and values in a list can be replaced with the \c{[]} operator, just + like entries of JavaScript arrays. You can also use \c{push()} to append + entries, or you can set the \c length property of the list to truncate or + extend it. You can not automatically extend the list by assigning to an + index currently out of range, though. Furthermore, if you insert \c null + values into a list of objects, those are converted to \c nullptr entries in + the underlying QQmlListProperty. + + A list of value types is different from a JavaScript array in one important + aspect: Growing it by setting its length does not produce undefined entries, + but rather default-constructed instances of the value type. This value type is provided by the QML language. diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp index a63f1fc7f9..c775d66638 100644 --- a/src/qml/jsruntime/qv4qobjectwrapper.cpp +++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp @@ -131,11 +131,12 @@ static ReturnedValue loadProperty(ExecutionEngine *v4, QObject *object, Q_ASSERT(!property.isFunction()); Scope scope(v4); + const QMetaType propMetaType = property.propType(); if (property.isQObject()) { QObject *rv = nullptr; property.readProperty(object, &rv); ReturnedValue ret = QObjectWrapper::wrap(v4, rv); - if (property.propType().flags().testFlag(QMetaType::IsConst)) { + if (propMetaType.flags().testFlag(QMetaType::IsConst)) { ScopedValue v(scope, ret); if (auto obj = v->as<Object>()) { obj->setInternalClass(obj->internalClass()->cryopreserved()); @@ -145,10 +146,9 @@ static ReturnedValue loadProperty(ExecutionEngine *v4, QObject *object, return ret; } - if (property.isQList()) - return QmlListWrapper::create(v4, object, property.coreIndex(), property.propType()); + if (property.isQList() && propMetaType.flags().testFlag(QMetaType::IsQmlList)) + return QmlListWrapper::create(v4, object, property.coreIndex(), propMetaType); - const QMetaType propMetaType = property.propType(); switch (property.isEnum() ? QMetaType::Int : propMetaType.id()) { case QMetaType::Int: { int v = 0; @@ -205,23 +205,23 @@ static ReturnedValue loadProperty(ExecutionEngine *v4, QObject *object, if (QQmlMetaType::isValueType(propMetaType)) { if (const QMetaObject *valueTypeMetaObject = QQmlMetaType::metaObjectForValueType(propMetaType)) return QQmlValueTypeWrapper::create(v4, object, property.coreIndex(), valueTypeMetaObject, propMetaType); - } else { - // see if it's a sequence type - bool succeeded = false; - ScopedValue retn(scope, SequencePrototype::newSequence( - v4, propMetaType, object, property.coreIndex(), - !property.isWritable(), &succeeded)); - if (succeeded) - return retn->asReturnedValue(); } + // see if it's a sequence type + bool succeeded = false; + QV4::ScopedValue retn(scope, QV4::SequencePrototype::newSequence( + v4, propMetaType, object, property.coreIndex(), + !property.isWritable(), &succeeded)); + if (succeeded) + return retn->asReturnedValue(); + if (!propMetaType.isValid()) { QMetaProperty p = object->metaObject()->property(property.coreIndex()); qWarning("QMetaProperty::read: Unable to handle unregistered datatype '%s' for property " "'%s::%s'", p.typeName(), object->metaObject()->className(), p.name()); return Encode::undefined(); } else { - QVariant v(property.propType(), (void *)nullptr); + QVariant v(propMetaType); property.readProperty(object, v.data()); return scope.engine->fromVariant(v); } @@ -1541,8 +1541,8 @@ static int MatchScore(const Value &actual, QMetaType conversionMetaType) } } - if (auto sequenceMetaType = SequencePrototype::metaTypeForSequence(obj); sequenceMetaType != -1) { - if (sequenceMetaType == conversionType) + if (auto sequenceMetaType = SequencePrototype::metaTypeForSequence(obj); sequenceMetaType.isValid()) { + if (sequenceMetaType == conversionMetaType) return 1; else return 10; @@ -1888,7 +1888,8 @@ bool CallArgument::fromContainerValue(const Value &value, M CallArgument::*membe { const Object *object = value.as<Object>(); if (object && object->isListType()) { - if (T* ptr = static_cast<T *>(SequencePrototype::getRawContainerPtr(object, type))) { + if (T* ptr = static_cast<T *>(SequencePrototype::getRawContainerPtr( + object, QMetaType(type)))) { (this->*member) = ptr; return true; } diff --git a/src/qml/jsruntime/qv4sequenceobject.cpp b/src/qml/jsruntime/qv4sequenceobject.cpp index 7d4c91bb50..42ad6b8716 100644 --- a/src/qml/jsruntime/qv4sequenceobject.cpp +++ b/src/qml/jsruntime/qv4sequenceobject.cpp @@ -81,7 +81,7 @@ struct QV4Sequence : Object { void init(const QQmlType &qmlType, const void *container); void init(QObject *object, int propertyIndex, const QQmlType &qmlType, bool readOnly); void destroy() { - typePrivate->typeId.destroy(container); + typePrivate->listId.destroy(container); QQmlType::derefHandle(typePrivate); object.destroy(); Object::destroy(); @@ -110,31 +110,41 @@ struct QV4Sequence : public QV4::Object V4_NEEDS_DESTROY public: + static const QMetaSequence *metaSequence(const Heap::QV4Sequence *p) + { + return p->typePrivate->extraData.ld; + } + + static const QMetaType valueMetaType(const Heap::QV4Sequence *p) + { + return p->typePrivate->typeId; + } + qsizetype size() const { const auto *p = d(); - return meta(p)->size(p->container); + return metaSequence(p)->size(p->container); } QVariant at(int index) const { const auto *p = d(); - const auto *m = meta(p); - QVariant result(m->valueMetaType()); - m->valueAtIndex(p->container, index, result.data()); + QVariant result(valueMetaType(p)); + metaSequence(p)->valueAtIndex(p->container, index, result.data()); return result; } void append(const QVariant &item) { const auto *p = d(); - const auto *m = meta(p); - if (item.metaType() == m->valueMetaType()) { + const auto *m = metaSequence(p); + const QMetaType v = valueMetaType(p); + if (item.metaType() == v) { m->addValueAtEnd(p->container, item.constData()); } else { QVariant converted = item; - if (!converted.convert(m->valueMetaType())) - converted = QVariant(m->valueMetaType()); + if (!converted.convert(v)) + converted = QVariant(v); m->addValueAtEnd(p->container, converted.constData()); } } @@ -142,13 +152,14 @@ public: void replace(int index, const QVariant &item) { const auto *p = d(); - const auto *m = meta(p); - if (item.metaType() == m->valueMetaType()) { + const auto *m = metaSequence(p); + const QMetaType v = valueMetaType(p); + if (item.metaType() == v) { m->setValueAtIndex(p->container, index, item.constData()); } else { QVariant converted = item; - if (!converted.convert(m->valueMetaType())) - converted = QVariant(m->valueMetaType()); + if (!converted.convert(v)) + converted = QVariant(v); m->setValueAtIndex(p->container, index, converted.constData()); } } @@ -157,9 +168,9 @@ public: void sort(const Compare &compare) { const auto *p = d(); - const auto *m = meta(p); + const auto *m = metaSequence(p); - QSequentialIterable iterable(*m, p->typePrivate->typeId, p->container); + QSequentialIterable iterable(*m, p->typePrivate->listId, p->container); if (iterable.canRandomAccessIterate()) { std::sort(QSequentialIterable::RandomAccessIterator(iterable.mutableBegin()), QSequentialIterable::RandomAccessIterator(iterable.mutableEnd()), @@ -176,7 +187,7 @@ public: void removeLast(int num) { const auto *p = d(); - const auto *m = meta(p); + const auto *m = metaSequence(p); if (m->canEraseRangeAtIterator() && m->hasRandomAccessIterator() && num > 1) { void *i = m->end(p->container); @@ -194,7 +205,7 @@ public: QVariant toVariant() { const auto *p = d(); - return QVariant(p->typePrivate->typeId, p->container); + return QVariant(p->typePrivate->listId, p->container); } // ### Qt 7 use qsizetype instead. @@ -249,8 +260,8 @@ public: } quint32 count = quint32(size()); - const QMetaType valueMetaType = meta(d())->valueMetaType(); - const QVariant element = engine()->toVariant(value, valueMetaType, false); + const QMetaType valueType = valueMetaType(d()); + const QVariant element = engine()->toVariant(value, valueType, false); if (index == count) { append(element); @@ -260,7 +271,7 @@ public: /* according to ECMA262r3 we need to insert */ /* the value at the given index, increasing length to index+1. */ while (index > count++) - append(QVariant(valueMetaType)); + append(QVariant(valueType)); append(element); } @@ -471,7 +482,7 @@ void Heap::QV4Sequence::init(const QQmlType &qmlType, const void *container) typePrivate = qmlType.priv(); QQmlType::refHandle(typePrivate); - this->container = QMetaType(typePrivate->typeId).create(container); + this->container = typePrivate->listId.create(container); propertyIndex = -1; isReference = false; isReadOnly = false; @@ -490,7 +501,7 @@ void Heap::QV4Sequence::init(QObject *object, int propertyIndex, const QQmlType Q_ASSERT(qmlType.isSequentialContainer()); typePrivate = qmlType.priv(); QQmlType::refHandle(typePrivate); - container = QMetaType(typePrivate->typeId).create(); + container = QMetaType(typePrivate->listId).create(); this->propertyIndex = propertyIndex; isReference = true; this->isReadOnly = readOnly; @@ -571,7 +582,6 @@ static QV4::ReturnedValue method_set_length(const FunctionObject *f, const Value RETURN_UNDEFINED(); } - void SequencePrototype::init() { defineDefaultProperty(QStringLiteral("sort"), method_sort, 1); @@ -612,7 +622,7 @@ ReturnedValue SequencePrototype::newSequence( // (as well as object ptr + property index for updated-read and write-back) // and so access/mutate avoids variant conversion. - const QQmlType qmlType = QQmlMetaType::qmlType(sequenceType); + const QQmlType qmlType = QQmlMetaType::qmlListType(sequenceType); if (qmlType.isSequentialContainer()) { *succeeded = true; QV4::ScopedObject obj(scope, engine->memoryManager->allocate<QV4Sequence>( @@ -638,7 +648,7 @@ ReturnedValue SequencePrototype::fromData(ExecutionEngine *engine, QMetaType typ // Access and mutation is extremely fast since it will not need to modify any // QObject property. - const QQmlType qmlType = QQmlMetaType::qmlType(type); + const QQmlType qmlType = QQmlMetaType::qmlListType(type); if (qmlType.isSequentialContainer()) { *succeeded = true; QV4::ScopedObject obj(scope, engine->memoryManager->allocate<QV4Sequence>(qmlType, data)); @@ -666,18 +676,25 @@ QVariant SequencePrototype::toVariant(const QV4::Value &array, QMetaType typeHin QV4::Scope scope(array.as<Object>()->engine()); QV4::ScopedArrayObject a(scope, array); - const QQmlType type = QQmlMetaType::qmlType(typeHint); + const QQmlType type = QQmlMetaType::qmlListType(typeHint); if (type.isSequentialContainer()) { - const QMetaSequence *meta = type.priv()->extraData.ld; - const QMetaType containerMetaType(type.priv()->typeId); + const QQmlTypePrivate *priv = type.priv(); + const QMetaSequence *meta = priv->extraData.ld; + const QMetaType containerMetaType(priv->listId); QVariant result(containerMetaType); quint32 length = a->getLength(); QV4::ScopedValue v(scope); for (quint32 i = 0; i < length; ++i) { - const QMetaType valueMetaType = meta->valueMetaType(); + const QMetaType valueMetaType = priv->typeId; QVariant variant = scope.engine->toVariant(a->get(i), valueMetaType, false); - if (variant.metaType() != valueMetaType && !variant.convert(valueMetaType)) + const QMetaType originalType = variant.metaType(); + if (originalType != valueMetaType && !variant.convert(valueMetaType)) { + qWarning() << QLatin1String( + "Could not convert array value at position %1 from %2 to %3") + .arg(QString::number(i), QString::fromUtf8(originalType.name()), + QString::fromUtf8(valueMetaType.name())); variant = QVariant(valueMetaType); + } meta->addValueAtEnd(result.data(), variant.constData()); } return result; @@ -687,20 +704,20 @@ QVariant SequencePrototype::toVariant(const QV4::Value &array, QMetaType typeHin return QVariant(); } -void* SequencePrototype::getRawContainerPtr(const Object *object, int typeHint) +void *SequencePrototype::getRawContainerPtr(const Object *object, QMetaType typeHint) { if (auto *s = object->as<QV4Sequence>()) { - if (s->d()->typePrivate->typeId.id() == typeHint) + if (s->d()->typePrivate->listId == typeHint) return s->getRawContainerPtr(); } return nullptr; } -int SequencePrototype::metaTypeForSequence(const QV4::Object *object) +QMetaType SequencePrototype::metaTypeForSequence(const QV4::Object *object) { if (auto *s = object->as<QV4Sequence>()) - return s->d()->typePrivate->typeId.id(); - return -1; + return s->d()->typePrivate->listId; + return QMetaType(); } QT_END_NAMESPACE diff --git a/src/qml/jsruntime/qv4sequenceobject_p.h b/src/qml/jsruntime/qv4sequenceobject_p.h index db489d039d..593a6696fb 100644 --- a/src/qml/jsruntime/qv4sequenceobject_p.h +++ b/src/qml/jsruntime/qv4sequenceobject_p.h @@ -81,10 +81,10 @@ struct Q_QML_PRIVATE_EXPORT SequencePrototype : public QV4::Object static ReturnedValue fromVariant(QV4::ExecutionEngine *engine, const QVariant &v, bool *succeeded); static ReturnedValue fromData(QV4::ExecutionEngine *engine, QMetaType type, const void *data, bool *succeeded); - static int metaTypeForSequence(const Object *object); + static QMetaType metaTypeForSequence(const Object *object); static QVariant toVariant(Object *object); static QVariant toVariant(const Value &array, QMetaType typeHint, bool *succeeded); - static void* getRawContainerPtr(const Object *object, int typeHint); + static void *getRawContainerPtr(const Object *object, QMetaType typeHint); }; } diff --git a/src/qml/qml/qqml.cpp b/src/qml/qml/qqml.cpp index 98eb7e40c3..cfcc8d5a2b 100644 --- a/src/qml/qml/qqml.cpp +++ b/src/qml/qml/qqml.cpp @@ -479,7 +479,7 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data) noCreateReason = QLatin1String("Type cannot be created in QML."); } - RegisterType revisionRegistration = { + RegisterType typeRevision = { 1, type.typeId, type.listId, @@ -504,6 +504,16 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data) type.structVersion > 0 ? type.finalizerCast : -1 }; + QQmlPrivate::RegisterSequentialContainer sequenceRevision = { + 0, + type.uri, + type.version, + nullptr, + type.listId, + type.structVersion > 1 ? type.listMetaSequence : QMetaSequence(), + QTypeRevision(), + }; + const QTypeRevision added = revisionClassInfo( type.classInfoMetaObject, "QML.AddedInVersion", QTypeRevision::fromMinorVersion(0)); @@ -524,19 +534,28 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data) break; // When removed, we still add revisions, but anonymous ones if (removed.isValid() && !(revision < removed)) { - revisionRegistration.elementName = nullptr; - revisionRegistration.create = nullptr; + typeRevision.elementName = nullptr; + typeRevision.create = nullptr; } else { - revisionRegistration.elementName = elementName; - revisionRegistration.create = creatable ? type.create : nullptr; - revisionRegistration.userdata = type.userdata; + typeRevision.elementName = elementName; + typeRevision.create = creatable ? type.create : nullptr; + typeRevision.userdata = type.userdata; } - assignVersions(&revisionRegistration, revision, type.version); - revisionRegistration.customParser = type.customParserFactory(); - const int id = qmlregister(TypeRegistration, &revisionRegistration); + assignVersions(&typeRevision, revision, type.version); + typeRevision.customParser = type.customParserFactory(); + const int id = qmlregister(TypeRegistration, &typeRevision); if (type.qmlTypeIds) type.qmlTypeIds->append(id); + + if (sequenceRevision.metaSequence != QMetaSequence()) { + sequenceRevision.version = typeRevision.version; + sequenceRevision.revision = typeRevision.revision; + const int id = QQmlPrivate::qmlregister( + QQmlPrivate::SequentialContainerRegistration, &sequenceRevision); + if (type.qmlTypeIds) + type.qmlTypeIds->append(id); + } } break; } @@ -594,12 +613,11 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data) case SequentialContainerAndRevisionsRegistration: { const RegisterSequentialContainerAndRevisions &type = *reinterpret_cast<RegisterSequentialContainerAndRevisions *>(data); - const char *elementName = classElementName(type.classInfoMetaObject); RegisterSequentialContainer revisionRegistration = { 0, type.uri, type.version, - elementName, + nullptr, type.typeId, type.metaSequence, QTypeRevision() @@ -608,10 +626,8 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data) const QTypeRevision added = revisionClassInfo( type.classInfoMetaObject, "QML.AddedInVersion", QTypeRevision::fromMinorVersion(0)); - const QTypeRevision removed = revisionClassInfo( - type.classInfoMetaObject, "QML.RemovedInVersion"); - QList<QTypeRevision> revisions = revisionClassInfos(type.classInfoMetaObject, - "QML.ExtraVersion"); + QList<QTypeRevision> revisions = revisionClassInfos( + type.classInfoMetaObject, "QML.ExtraVersion"); revisions.append(added); uniqueRevisions(&revisions, type.version, added); @@ -621,12 +637,6 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data) if (revision.hasMajorVersion() && revision.majorVersion() > type.version.majorVersion()) break; - // When removed, we still add revisions, but anonymous ones - if (removed.isValid() && !(revision < removed)) - revisionRegistration.typeName = nullptr; - else - revisionRegistration.typeName = elementName; - assignVersions(&revisionRegistration, revision, type.version); const int id = qmlregister(SequentialContainerRegistration, &revisionRegistration); if (type.qmlTypeIds) @@ -697,40 +707,42 @@ void QQmlPrivate::qmlunregister(RegistrationType type, quintptr data) namespace QQmlPrivate { template<> -void qmlRegisterTypeAndRevisions<QQmlTypeNotAvailable, void>(const char *uri, int versionMajor, - const QMetaObject *classInfoMetaObject, - QVector<int> *qmlTypeIds, - const QMetaObject *extension, bool) +void qmlRegisterTypeAndRevisions<QQmlTypeNotAvailable, void>( + const char *uri, int versionMajor, const QMetaObject *classInfoMetaObject, + QVector<int> *qmlTypeIds, const QMetaObject *extension, bool) { using T = QQmlTypeNotAvailable; - RegisterTypeAndRevisions type = { 1, - QMetaType::fromType<T *>(), - QMetaType::fromType<QQmlListProperty<T>>(), - 0, - nullptr, - nullptr, - nullptr, - - uri, - QTypeRevision::fromMajorVersion(versionMajor), - - &QQmlTypeNotAvailable::staticMetaObject, - classInfoMetaObject, - - attachedPropertiesFunc<T>(), - attachedPropertiesMetaObject<T>(), - - StaticCastSelector<T, QQmlParserStatus>::cast(), - StaticCastSelector<T, QQmlPropertyValueSource>::cast(), - StaticCastSelector<T, QQmlPropertyValueInterceptor>::cast(), - - nullptr, - extension, - qmlCreateCustomParser<T>, - qmlTypeIds, - QQmlPrivate::StaticCastSelector<T, QQmlFinalizerHook>::cast(), - false }; + RegisterTypeAndRevisions type = { + 3, + QmlMetaType<T>::self(), + QmlMetaType<T>::list(), + 0, + nullptr, + nullptr, + nullptr, + + uri, + QTypeRevision::fromMajorVersion(versionMajor), + + &QQmlTypeNotAvailable::staticMetaObject, + classInfoMetaObject, + + attachedPropertiesFunc<T>(), + attachedPropertiesMetaObject<T>(), + + StaticCastSelector<T, QQmlParserStatus>::cast(), + StaticCastSelector<T, QQmlPropertyValueSource>::cast(), + StaticCastSelector<T, QQmlPropertyValueInterceptor>::cast(), + + nullptr, + extension, + qmlCreateCustomParser<T>, + qmlTypeIds, + QQmlPrivate::StaticCastSelector<T, QQmlFinalizerHook>::cast(), + false, + QmlMetaType<T>::sequence(), + }; qmlregister(TypeAndRevisionsRegistration, &type); } diff --git a/src/qml/qml/qqml.h b/src/qml/qml/qqml.h index e205821bbf..b20b8dc5d3 100644 --- a/src/qml/qml/qqml.h +++ b/src/qml/qml/qqml.h @@ -843,35 +843,37 @@ inline void qmlRegisterNamespaceAndRevisions(const QMetaObject *metaObject, const QMetaObject *classInfoMetaObject, const QMetaObject *extensionMetaObject) { - QQmlPrivate::RegisterTypeAndRevisions type = { 1, - QMetaType(), - QMetaType(), - 0, - nullptr, - nullptr, - nullptr, - - uri, - QTypeRevision::fromMajorVersion(versionMajor), - - metaObject, - (classInfoMetaObject ? classInfoMetaObject - : metaObject), - - nullptr, - nullptr, - - -1, - -1, - -1, - - nullptr, - extensionMetaObject, - - &qmlCreateCustomParser<void>, - qmlTypeIds, - -1, - false }; + QQmlPrivate::RegisterTypeAndRevisions type = { + 3, + QMetaType(), + QMetaType(), + 0, + nullptr, + nullptr, + nullptr, + + uri, + QTypeRevision::fromMajorVersion(versionMajor), + + metaObject, + (classInfoMetaObject ? classInfoMetaObject : metaObject), + + nullptr, + nullptr, + + -1, + -1, + -1, + + nullptr, + extensionMetaObject, + + &qmlCreateCustomParser<void>, + qmlTypeIds, + -1, + false, + QMetaSequence() + }; qmlregister(QQmlPrivate::TypeAndRevisionsRegistration, &type); } diff --git a/src/qml/qml/qqmlbinding.cpp b/src/qml/qml/qqmlbinding.cpp index 54a188dbb0..6bb370eab7 100644 --- a/src/qml/qml/qqmlbinding.cpp +++ b/src/qml/qml/qqmlbinding.cpp @@ -530,7 +530,10 @@ Q_NEVER_INLINE bool QQmlBinding::slowWrite(const QQmlPropertyData &core, if (isUndefined) { } else if (core.isQList()) { - value = v4engine->toVariant(result, QMetaType::fromType<QList<QObject *> >()); + if (core.propType().flags() & QMetaType::IsQmlList) + value = v4engine->toVariant(result, QMetaType::fromType<QList<QObject *> >()); + else + value = v4engine->toVariant(result, core.propType()); } else if (result.isNull() && core.isQObject()) { value = QVariant::fromValue((QObject *)nullptr); } else if (core.propType() == QMetaType::fromType<QList<QUrl>>()) { diff --git a/src/qml/qml/qqmllist_p.h b/src/qml/qml/qqmllist_p.h index b0ae94655d..30c8e8e090 100644 --- a/src/qml/qml/qqmllist_p.h +++ b/src/qml/qml/qqmllist_p.h @@ -82,7 +82,7 @@ public: { if (!m_elementType) { m_elementType = QQmlMetaType::rawMetaObjectForType( - QQmlMetaType::listType(propertyType)).metaObject(); + QQmlMetaType::listValueType(propertyType)).metaObject(); } return m_elementType; diff --git a/src/qml/qml/qqmlmetatype.cpp b/src/qml/qml/qqmlmetatype.cpp index f8cf142f5e..fb0e2af578 100644 --- a/src/qml/qml/qqmlmetatype.cpp +++ b/src/qml/qml/qqmlmetatype.cpp @@ -475,12 +475,15 @@ static void addTypeToData(QQmlTypePrivate *type, QQmlMetaTypeData *data) if (type->baseMetaObject) data->metaObjectToType.insert(type->baseMetaObject, type); - if (type->typeId.isValid()) { - data->idToType.insert(type->typeId.id(), type); - } + if (type->regType == QQmlType::SequentialContainerType) { + if (type->listId.isValid()) + data->idToType.insert(type->listId.id(), type); + } else { + if (type->typeId.isValid()) + data->idToType.insert(type->typeId.id(), type); - if (type->listId.isValid()) { - data->idToType.insert(type->listId.id(), type); + if (type->listId.flags().testFlag(QMetaType::IsQmlList)) + data->idToType.insert(type->listId.id(), type); } if (!type->module.isEmpty()) { @@ -637,8 +640,7 @@ QQmlType QQmlMetaType::registerSequentialContainer( QQmlMetaTypeDataPtr data; - const QString typeName = QString::fromUtf8(container.typeName); - if (!checkRegistration(QQmlType::SequentialContainerType, data, container.uri, typeName, + if (!checkRegistration(QQmlType::SequentialContainerType, data, container.uri, QString(), container.version, {})) { return QQmlType(); } @@ -646,10 +648,11 @@ QQmlType QQmlMetaType::registerSequentialContainer( QQmlTypePrivate *priv = new QQmlTypePrivate(QQmlType::SequentialContainerType); data->registerType(priv); - priv->setName(QString::fromUtf8(container.uri), typeName); + priv->setName(QString::fromUtf8(container.uri), QString()); priv->version = container.version; priv->revision = container.revision; - priv->typeId = container.typeId; + priv->typeId = container.metaSequence.valueMetaType(); + priv->listId = container.typeId; *priv->extraData.ld = container.metaSequence; addTypeToData(priv, data); @@ -1067,13 +1070,16 @@ QObject *QQmlMetaType::toQObject(const QVariant &v, bool *ok) /* Returns the item type for a list of type \a id. */ -QMetaType QQmlMetaType::listType(QMetaType metaType) -{ - if (!isList(metaType)) - return QMetaType {}; - const auto iface = metaType.iface(); - if (iface->metaObjectFn == &dynamicQmlListMarker) - return QMetaType(static_cast<const QQmlListMetaTypeInterface *>(iface)->valueType); +QMetaType QQmlMetaType::listValueType(QMetaType metaType) +{ + if (isList(metaType)) { + const auto iface = metaType.iface(); + if (iface->metaObjectFn == &dynamicQmlListMarker) + return QMetaType(static_cast<const QQmlListMetaTypeInterface *>(iface)->valueType); + } else if (metaType.flags() & QMetaType::PointerToQObject) { + return QMetaType(); + } + QQmlMetaTypeDataPtr data; QQmlTypePrivate *type = data->idToType.value(metaType.id()); if (type && type->listId == metaType) @@ -1258,6 +1264,13 @@ QQmlType QQmlMetaType::qmlType(QMetaType metaType) return (type && type->typeId == metaType) ? QQmlType(type) : QQmlType(); } +QQmlType QQmlMetaType::qmlListType(QMetaType metaType) +{ + const QQmlMetaTypeDataPtr data; + QQmlTypePrivate *type = data->idToType.value(metaType.id()); + return (type && type->listId == metaType) ? QQmlType(type) : QQmlType(); +} + /*! Returns the type (if any) that corresponds to the given \a url in the set of composite types added through file imports. diff --git a/src/qml/qml/qqmlmetatype_p.h b/src/qml/qml/qqmlmetatype_p.h index aaa8bafada..1fb4d915da 100644 --- a/src/qml/qml/qqmlmetatype_p.h +++ b/src/qml/qml/qqmlmetatype_p.h @@ -172,6 +172,8 @@ public: static QQmlType qmlTypeById(int qmlTypeId); static QQmlType qmlType(QMetaType metaType); + static QQmlType qmlListType(QMetaType metaType); + static QQmlType qmlType(const QUrl &unNormalizedUrl, bool includeNonFileImports = false); static QQmlRefPointer<QQmlPropertyCache> propertyCache( @@ -198,7 +200,7 @@ public: static QObject *toQObject(const QVariant &, bool *ok = nullptr); - static QMetaType listType(QMetaType type); + static QMetaType listValueType(QMetaType type); static QQmlAttachedPropertiesFunc attachedPropertiesFunc(QQmlEnginePrivate *, const QMetaObject *); static bool isInterface(QMetaType type); diff --git a/src/qml/qml/qqmlobjectcreator.cpp b/src/qml/qml/qqmlobjectcreator.cpp index ce14f87ec5..b04de3a208 100644 --- a/src/qml/qml/qqmlobjectcreator.cpp +++ b/src/qml/qml/qqmlobjectcreator.cpp @@ -259,7 +259,7 @@ void QQmlObjectCreator::populateDeferred(QObject *instance, int deferredIndex, const QQmlPropertyData &property = qmlProperty->core; - if (property.isQList()) { + if (property.propType().flags().testFlag(QMetaType::IsQmlList)) { void *argv[1] = { (void*)&_currentList }; QMetaObject::metacall(_qobject, QMetaObject::ReadProperty, property.coreIndex(), argv); } else if (_currentList.object) { @@ -693,7 +693,7 @@ void QQmlObjectCreator::setupBindings(BindingSetupFlags mode) continue; } - if (property && property->isQList()) { + if (property && property->propType().flags().testFlag(QMetaType::IsQmlList)) { if (property->coreIndex() != currentListPropertyIndex) { void *argv[1] = { (void*)&_currentList }; QMetaObject::metacall(_qobject, QMetaObject::ReadProperty, property->coreIndex(), argv); @@ -1081,12 +1081,12 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper argv[0] = &value; QMetaObject::metacall(_qobject, QMetaObject::WriteProperty, bindingProperty->coreIndex(), argv); } - } else if (bindingProperty->isQList()) { + } else if (bindingProperty->propType().flags().testFlag(QMetaType::IsQmlList)) { Q_ASSERT(_currentList.object); void *itemToAdd = createdSubObject; - QMetaType listItemType = QQmlMetaType::listType(bindingProperty->propType()); + QMetaType listItemType = QQmlMetaType::listValueType(bindingProperty->propType()); if (listItemType.isValid()) { const char *iid = QQmlMetaType::interfaceIId(listItemType); if (iid) diff --git a/src/qml/qml/qqmlprivate.h b/src/qml/qml/qqmlprivate.h index 9539730d53..108b23b827 100644 --- a/src/qml/qml/qqmlprivate.h +++ b/src/qml/qml/qqmlprivate.h @@ -513,6 +513,7 @@ namespace QQmlPrivate int finalizerCast; bool forceAnonymous; + QMetaSequence listMetaSequence; }; struct RegisterInterface { @@ -590,7 +591,11 @@ namespace QQmlPrivate int structVersion; const char *uri; QTypeRevision version; + + // ### Qt7: Remove typeName. It's ignored because the only valid name is "list", + // and that's automatic. const char *typeName; + QMetaType typeId; QMetaSequence metaSequence; QTypeRevision revision; @@ -931,7 +936,15 @@ namespace QQmlPrivate if constexpr (std::is_base_of_v<QObject, T>) return QMetaType::fromType<QQmlListProperty<T>>(); else - return QMetaType(); + return QMetaType::fromType<QList<T>>(); + } + + static QMetaSequence sequence() + { + if constexpr (std::is_base_of_v<QObject, T>) + return QMetaSequence(); + else + return QMetaSequence::fromContainer<QList<T>>(); } }; @@ -973,7 +986,7 @@ namespace QQmlPrivate static_assert(std::is_base_of_v<QObject, T> || !QQmlTypeInfo<T>::hasAttachedProperties); RegisterTypeAndRevisions type = { - 2, + 3, QmlMetaType<T>::self(), QmlMetaType<T>::list(), int(sizeof(T)), @@ -1001,7 +1014,8 @@ namespace QQmlPrivate qmlTypeIds, StaticCastSelector<T, QQmlFinalizerHook>::cast(), - forceAnonymous + forceAnonymous, + QmlMetaType<T>::sequence(), }; // Initialize the extension so that we can find it by name or ID. diff --git a/src/qml/qml/qqmlproperty.cpp b/src/qml/qml/qqmlproperty.cpp index 31cc9374b4..7a49f53e9d 100644 --- a/src/qml/qml/qqmlproperty.cpp +++ b/src/qml/qml/qqmlproperty.cpp @@ -1462,43 +1462,54 @@ bool QQmlPropertyPrivate::write( : urlSequence(value); return property.writeProperty(object, &urlSeq, flags); } else if (property.isQList()) { - QQmlMetaObject listType; - - listType = QQmlMetaType::rawMetaObjectForType(QQmlMetaType::listType(propertyMetaType)); - if (listType.isNull()) - return false; + if (propertyMetaType.flags() & QMetaType::IsQmlList) { + QMetaType listValueType = QQmlMetaType::listValueType(propertyMetaType); + QQmlMetaObject valueMetaObject = QQmlMetaType::rawMetaObjectForType(listValueType); + if (valueMetaObject.isNull()) + return false; - QQmlListProperty<void> prop; - property.readProperty(object, &prop); + QQmlListProperty<void> prop; + property.readProperty(object, &prop); - if (!prop.clear) - return false; + if (!prop.clear) + return false; - prop.clear(&prop); + prop.clear(&prop); - if (variantMetaType == QMetaType::fromType<QQmlListReference>()) { - QQmlListReference qdlr = value.value<QQmlListReference>(); + if (variantMetaType == QMetaType::fromType<QQmlListReference>()) { + QQmlListReference qdlr = value.value<QQmlListReference>(); - for (qsizetype ii = 0; ii < qdlr.count(); ++ii) { - QObject *o = qdlr.at(ii); - if (o && !QQmlMetaObject::canConvert(o, listType)) - o = nullptr; - prop.append(&prop, o); - } - } else if (variantMetaType == QMetaType::fromType<QList<QObject *>>()) { - const QList<QObject *> &list = qvariant_cast<QList<QObject *> >(value); - - for (qsizetype ii = 0; ii < list.count(); ++ii) { - QObject *o = list.at(ii); - if (o && !QQmlMetaObject::canConvert(o, listType)) + for (qsizetype ii = 0; ii < qdlr.count(); ++ii) { + QObject *o = qdlr.at(ii); + if (o && !QQmlMetaObject::canConvert(o, valueMetaObject)) + o = nullptr; + prop.append(&prop, o); + } + } else if (variantMetaType == QMetaType::fromType<QList<QObject *>>()) { + const QList<QObject *> &list = qvariant_cast<QList<QObject *> >(value); + + for (qsizetype ii = 0; ii < list.count(); ++ii) { + QObject *o = list.at(ii); + if (o && !QQmlMetaObject::canConvert(o, valueMetaObject)) + o = nullptr; + prop.append(&prop, o); + } + } else { + QObject *o = QQmlMetaType::toQObject(value); + if (o && !QQmlMetaObject::canConvert(o, valueMetaObject)) o = nullptr; prop.append(&prop, o); } + } else if (variantMetaType == propertyMetaType) { + QVariant v = value; + property.writeProperty(object, v.data(), flags); } else { - QObject *o = QQmlMetaType::toQObject(value); - if (o && !QQmlMetaObject::canConvert(o, listType)) - o = nullptr; - prop.append(&prop, o); + QVariant list(propertyMetaType); + const QQmlType type = QQmlMetaType::qmlType(propertyMetaType); + const QMetaSequence sequence = type.listMetaSequence(); + if (sequence.canAddValue()) + sequence.addValue(list.data(), value.data()); + property.writeProperty(object, list.data(), flags); } } else if (variantMetaType == QMetaType::fromType<QJSValue>()) { QJSValue jsValue = qvariant_cast<QJSValue>(value); diff --git a/src/qml/qml/qqmlpropertycachecreator.cpp b/src/qml/qml/qqmlpropertycachecreator.cpp index 60739a8983..de2b7eefe7 100644 --- a/src/qml/qml/qqmlpropertycachecreator.cpp +++ b/src/qml/qml/qqmlpropertycachecreator.cpp @@ -66,6 +66,26 @@ QMetaType QQmlPropertyCacheCreatorBase::metaTypeForPropertyType(QV4::CompiledDat return QMetaType {}; } +QMetaType QQmlPropertyCacheCreatorBase::listTypeForPropertyType(QV4::CompiledData::BuiltinType type) +{ + switch (type) { + case QV4::CompiledData::BuiltinType::Var: return QMetaType::fromType<QList<QVariant>>(); + case QV4::CompiledData::BuiltinType::Int: return QMetaType::fromType<QList<int>>(); + case QV4::CompiledData::BuiltinType::Bool: return QMetaType::fromType<QList<bool>>(); + case QV4::CompiledData::BuiltinType::Real: return QMetaType::fromType<QList<qreal>>(); + case QV4::CompiledData::BuiltinType::String: return QMetaType::fromType<QList<QString>>(); + case QV4::CompiledData::BuiltinType::Url: return QMetaType::fromType<QList<QUrl>>(); + case QV4::CompiledData::BuiltinType::Time: return QMetaType::fromType<QList<QTime>>(); + case QV4::CompiledData::BuiltinType::Date: return QMetaType::fromType<QList<QDate>>(); + case QV4::CompiledData::BuiltinType::DateTime: return QMetaType::fromType<QList<QDateTime>>(); + case QV4::CompiledData::BuiltinType::Rect: return QMetaType::fromType<QList<QRectF>>(); + case QV4::CompiledData::BuiltinType::Point: return QMetaType::fromType<QList<QPointF>>(); + case QV4::CompiledData::BuiltinType::Size: return QMetaType::fromType<QList<QSizeF>>(); + case QV4::CompiledData::BuiltinType::InvalidBuiltin: break; + }; + return QMetaType {}; +} + QByteArray QQmlPropertyCacheCreatorBase::createClassNameTypeByUrl(const QUrl &url) { const QString path = url.path(); diff --git a/src/qml/qml/qqmlpropertycachecreator_p.h b/src/qml/qml/qqmlpropertycachecreator_p.h index e74e91c638..dc1a55c005 100644 --- a/src/qml/qml/qqmlpropertycachecreator_p.h +++ b/src/qml/qml/qqmlpropertycachecreator_p.h @@ -102,6 +102,7 @@ public: static QAtomicInt classIndexCounter; static QMetaType metaTypeForPropertyType(QV4::CompiledData::BuiltinType type); + static QMetaType listTypeForPropertyType(QV4::CompiledData::BuiltinType type); static QByteArray createClassNameTypeByUrl(const QUrl &url); @@ -650,11 +651,15 @@ inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject(int const QV4::CompiledData::BuiltinType type = p->builtinType(); - if (type == QV4::CompiledData::BuiltinType::Var) + if (p->isList) + propertyFlags.type = QQmlPropertyData::Flags::QListType; + else if (type == QV4::CompiledData::BuiltinType::Var) propertyFlags.type = QQmlPropertyData::Flags::VarPropertyType; if (type != QV4::CompiledData::BuiltinType::InvalidBuiltin) { - propertyType = metaTypeForPropertyType(type); + propertyType = p->isList + ? listTypeForPropertyType(type) + : metaTypeForPropertyType(type); } else { Q_ASSERT(!p->isBuiltinType); @@ -698,12 +703,11 @@ inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject(int propertyType = typeIds.id; } } else { - if (p->isList) { + if (p->isList) propertyType = qmltype.qListTypeId(); - } else { + else propertyType = qmltype.typeId(); - propertyTypeVersion = qmltype.version(); - } + propertyTypeVersion = qmltype.version(); } if (p->isList) @@ -714,7 +718,7 @@ inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject(int propertyFlags.type = QQmlPropertyData::Flags::ValueType; } - if (!p->isReadOnly && !p->isList) + if (!p->isReadOnly && !propertyType.flags().testFlag(QMetaType::IsQmlList)) propertyFlags.setIsWritable(true); diff --git a/src/qml/qml/qqmlpropertyvalidator.cpp b/src/qml/qml/qqmlpropertyvalidator.cpp index 936f35190c..558a7e87a1 100644 --- a/src/qml/qml/qqmlpropertyvalidator.cpp +++ b/src/qml/qml/qqmlpropertyvalidator.cpp @@ -745,7 +745,7 @@ QQmlError QQmlPropertyValidator::validateObjectBinding(QQmlPropertyData *propert // We can convert everything to QVariant :) return noError; } else if (property->isQList()) { - const QMetaType listType = QQmlMetaType::listType(property->propType()); + const QMetaType listType = QQmlMetaType::listValueType(property->propType()); if (!QQmlMetaType::isInterface(listType)) { QQmlRefPointer<QQmlPropertyCache> source = propertyCaches.at(binding->value.objectIndex); if (!canCoerce(listType, source)) { diff --git a/src/qml/qml/qqmltype.cpp b/src/qml/qml/qqmltype.cpp index f827a24660..aeea0e45ce 100644 --- a/src/qml/qml/qqmltype.cpp +++ b/src/qml/qml/qqmltype.cpp @@ -642,6 +642,11 @@ QMetaType QQmlType::qListTypeId() const return d ? d->listId : QMetaType{}; } +QMetaSequence QQmlType::listMetaSequence() const +{ + return isSequentialContainer() ? *d->extraData.ld : QMetaSequence(); +} + const QMetaObject *QQmlType::metaObject() const { if (!d) diff --git a/src/qml/qml/qqmltype_p.h b/src/qml/qml/qqmltype_p.h index a7e60743b7..56693efb33 100644 --- a/src/qml/qml/qqmltype_p.h +++ b/src/qml/qml/qqmltype_p.h @@ -132,6 +132,7 @@ public: QMetaType typeId() const; QMetaType qListTypeId() const; + QMetaSequence listMetaSequence() const; const QMetaObject *metaObject() const; const QMetaObject *baseMetaObject() const; diff --git a/src/qml/qml/qqmltypecompiler.cpp b/src/qml/qml/qqmltypecompiler.cpp index a4ad9d2327..a5c55958d4 100644 --- a/src/qml/qml/qqmltypecompiler.cpp +++ b/src/qml/qml/qqmltypecompiler.cpp @@ -492,7 +492,7 @@ bool QQmlEnumTypeResolver::resolveEnumBindings() const QString propertyName = stringAt(binding->propertyNameIndex); bool notInRevision = false; QQmlPropertyData *pd = resolver.property(propertyName, ¬InRevision); - if (!pd) + if (!pd || pd->isQList()) continue; if (!pd->isEnum() && pd->propType().id() != QMetaType::Int) diff --git a/src/qml/qml/qqmlvmemetaobject.cpp b/src/qml/qml/qqmlvmemetaobject.cpp index 70c6b2e45e..93abe3c83a 100644 --- a/src/qml/qml/qqmlvmemetaobject.cpp +++ b/src/qml/qml/qqmlvmemetaobject.cpp @@ -58,6 +58,7 @@ #include <private/qv4scopedvalue_p.h> #include <private/qv4jscall_p.h> #include <private/qv4qobjectwrapper_p.h> +#include <private/qv4sequenceobject_p.h> #include <private/qqmlpropertycachecreator_p.h> #include <private/qqmlpropertycachemethodarguments_p.h> @@ -713,54 +714,16 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void * : QQmlEnginePrivate::get(ctxt->engine()); if (c == QMetaObject::ReadProperty) { - switch (t) { - case QV4::CompiledData::BuiltinType::Int: - *reinterpret_cast<int *>(a[0]) = readPropertyAsInt(id); - break; - case QV4::CompiledData::BuiltinType::Bool: - *reinterpret_cast<bool *>(a[0]) = readPropertyAsBool(id); - break; - case QV4::CompiledData::BuiltinType::Real: - *reinterpret_cast<double *>(a[0]) = readPropertyAsDouble(id); - break; - case QV4::CompiledData::BuiltinType::String: - *reinterpret_cast<QString *>(a[0]) = readPropertyAsString(id); - break; - case QV4::CompiledData::BuiltinType::Url: - *reinterpret_cast<QUrl *>(a[0]) = readPropertyAsUrl(id); - break; - case QV4::CompiledData::BuiltinType::Date: - *reinterpret_cast<QDate *>(a[0]) = readPropertyAsDate(id); - break; - case QV4::CompiledData::BuiltinType::DateTime: - *reinterpret_cast<QDateTime *>(a[0]) = readPropertyAsDateTime(id); - break; - case QV4::CompiledData::BuiltinType::Rect: - *reinterpret_cast<QRectF *>(a[0]) = readPropertyAsRectF(id); - break; - case QV4::CompiledData::BuiltinType::Size: - *reinterpret_cast<QSizeF *>(a[0]) = readPropertyAsSizeF(id); - break; - case QV4::CompiledData::BuiltinType::Point: - *reinterpret_cast<QPointF *>(a[0]) = readPropertyAsPointF(id); - break; - case QV4::CompiledData::BuiltinType::Time: - *reinterpret_cast<QTime *>(a[0]) = readPropertyAsTime(id); - break; - case QV4::CompiledData::BuiltinType::Var: - if (ep) { - *reinterpret_cast<QVariant *>(a[0]) = readPropertyAsVariant(id); - } else { - // if the context was disposed, we just return an invalid variant from read. - *reinterpret_cast<QVariant *>(a[0]) = QVariant(); - } - break; - case QV4::CompiledData::BuiltinType::InvalidBuiltin: - if (property.isList) { + if (property.isList) { + // _id because this is an absolute property ID. + const QQmlPropertyData *propertyData = cache->property(_id); + const QMetaType propType = propertyData->propType(); + + if (propType.flags().testFlag(QMetaType::IsQmlList)) { // when reading from the list, we need to find the correct MetaObject, - // namely this. However, obejct->metaObject might point to any MetaObject - // down the inheritance hierarchy, so we need to store how far we have - // to go down + // namely this. However, obejct->metaObject might point to any + // MetaObject down the inheritance hierarchy, so we need to store how + // far we have to go down // To do this, we encode the hierarchy depth together with the id of the // property in a single quintptr, with the first half storing the depth // and the second half storing the property id @@ -771,16 +734,18 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void * mo = mo->parentVMEMetaObject(); ++inheritanceDepth; } - constexpr quintptr usableBits = sizeof(quintptr) * CHAR_BIT; - if (Q_UNLIKELY(inheritanceDepth >= (quintptr(1) << quintptr(usableBits / 2u) ) )) { - qmlWarning(object) << "Too many objects in inheritance hierarchy for list property"; + constexpr quintptr idBits = sizeof(quintptr) * CHAR_BIT / 2u; + if (Q_UNLIKELY(inheritanceDepth >= (quintptr(1) << idBits))) { + qmlWarning(object) << "Too many objects in inheritance hierarchy " + "for list property"; return -1; } - if (Q_UNLIKELY(quintptr(id) >= (quintptr(1) << quintptr(usableBits / 2) ) )) { - qmlWarning(object) << "Too many properties in object for list property"; + if (Q_UNLIKELY(quintptr(id) >= (quintptr(1) << idBits))) { + qmlWarning(object) << "Too many properties in object " + "for list property"; return -1; } - quintptr encodedIndex = (inheritanceDepth << (usableBits/2)) + id; + quintptr encodedIndex = (inheritanceDepth << idBits) + id; readPropertyAsList(id); // Initializes if necessary *static_cast<QQmlListProperty<QObject> *>(a[0]) @@ -789,123 +754,215 @@ int QQmlVMEMetaObject::metaCall(QObject *o, QMetaObject::Call c, int _id, void * list_append, list_count, list_at, list_clear, list_replace, list_removeLast); } else if (QV4::MemberData *md = propertyAndMethodStorageAsMemberData()) { + // Value type list QV4::Scope scope(engine); - QV4::ScopedValue sv(scope, *(md->data() + id)); - - // _id because this is an absolute property ID. - const QQmlPropertyData *propertyData = cache->property(_id); - - if (propertyData->isQObject()) { - if (const auto *wrap = sv->as<QV4::QObjectWrapper>()) - *reinterpret_cast<QObject **>(a[0]) = wrap->object(); - else - *reinterpret_cast<QObject **>(a[0]) = nullptr; + QV4::ScopedObject sequence(scope, *(md->data() + id)); + const void *data = sequence + ? QV4::SequencePrototype::getRawContainerPtr(sequence, propType) + : nullptr; + propType.destruct(a[0]); + propType.construct(a[0], data); + } else { + qmlWarning(object) << "Cannot find member data"; + } + } else { + switch (t) { + case QV4::CompiledData::BuiltinType::Int: + *reinterpret_cast<int *>(a[0]) = readPropertyAsInt(id); + break; + case QV4::CompiledData::BuiltinType::Bool: + *reinterpret_cast<bool *>(a[0]) = readPropertyAsBool(id); + break; + case QV4::CompiledData::BuiltinType::Real: + *reinterpret_cast<double *>(a[0]) = readPropertyAsDouble(id); + break; + case QV4::CompiledData::BuiltinType::String: + *reinterpret_cast<QString *>(a[0]) = readPropertyAsString(id); + break; + case QV4::CompiledData::BuiltinType::Url: + *reinterpret_cast<QUrl *>(a[0]) = readPropertyAsUrl(id); + break; + case QV4::CompiledData::BuiltinType::Date: + *reinterpret_cast<QDate *>(a[0]) = readPropertyAsDate(id); + break; + case QV4::CompiledData::BuiltinType::DateTime: + *reinterpret_cast<QDateTime *>(a[0]) = readPropertyAsDateTime(id); + break; + case QV4::CompiledData::BuiltinType::Rect: + *reinterpret_cast<QRectF *>(a[0]) = readPropertyAsRectF(id); + break; + case QV4::CompiledData::BuiltinType::Size: + *reinterpret_cast<QSizeF *>(a[0]) = readPropertyAsSizeF(id); + break; + case QV4::CompiledData::BuiltinType::Point: + *reinterpret_cast<QPointF *>(a[0]) = readPropertyAsPointF(id); + break; + case QV4::CompiledData::BuiltinType::Time: + *reinterpret_cast<QTime *>(a[0]) = readPropertyAsTime(id); + break; + case QV4::CompiledData::BuiltinType::Var: + if (ep) { + *reinterpret_cast<QVariant *>(a[0]) = readPropertyAsVariant(id); } else { - const QMetaType propType = propertyData->propType(); - const void *data = nullptr; - if (const QV4::VariantObject *v = sv->as<QV4::VariantObject>()) { - const QVariant &variant = v->d()->data(); - if (variant.metaType() == propType) - data = variant.constData(); + // if the context was disposed, + // we just return an invalid variant from read. + *reinterpret_cast<QVariant *>(a[0]) = QVariant(); + } + break; + case QV4::CompiledData::BuiltinType::InvalidBuiltin: + if (QV4::MemberData *md = propertyAndMethodStorageAsMemberData()) { + QV4::Scope scope(engine); + QV4::ScopedValue sv(scope, *(md->data() + id)); + + // _id because this is an absolute property ID. + const QQmlPropertyData *propertyData = cache->property(_id); + + if (propertyData->isQObject()) { + if (const auto *wrap = sv->as<QV4::QObjectWrapper>()) + *reinterpret_cast<QObject **>(a[0]) = wrap->object(); + else + *reinterpret_cast<QObject **>(a[0]) = nullptr; + } else { + const QMetaType propType = propertyData->propType(); + const void *data = nullptr; + if (const auto *v = sv->as<QV4::VariantObject>()) { + const QVariant &variant = v->d()->data(); + if (variant.metaType() == propType) + data = variant.constData(); + } + propType.destruct(a[0]); + propType.construct(a[0], data); } - propType.destruct(a[0]); - propType.construct(a[0], data); + } else { + qmlWarning(object) << "Cannot find member data"; } - } else { - qmlWarning(object) << "Cannot find member data"; } } - } else if (c == QMetaObject::WriteProperty) { bool needActivate = false; - switch (t) { - case QV4::CompiledData::BuiltinType::Int: - needActivate = *reinterpret_cast<int *>(a[0]) != readPropertyAsInt(id); - writeProperty(id, *reinterpret_cast<int *>(a[0])); - break; - case QV4::CompiledData::BuiltinType::Bool: - needActivate = *reinterpret_cast<bool *>(a[0]) != readPropertyAsBool(id); - writeProperty(id, *reinterpret_cast<bool *>(a[0])); - break; - case QV4::CompiledData::BuiltinType::Real: - needActivate = *reinterpret_cast<double *>(a[0]) != readPropertyAsDouble(id); - writeProperty(id, *reinterpret_cast<double *>(a[0])); - break; - case QV4::CompiledData::BuiltinType::String: - needActivate = *reinterpret_cast<QString *>(a[0]) != readPropertyAsString(id); - writeProperty(id, *reinterpret_cast<QString *>(a[0])); - break; - case QV4::CompiledData::BuiltinType::Url: - needActivate = *reinterpret_cast<QUrl *>(a[0]) != readPropertyAsUrl(id); - writeProperty(id, *reinterpret_cast<QUrl *>(a[0])); - break; - case QV4::CompiledData::BuiltinType::Date: - needActivate = *reinterpret_cast<QDate *>(a[0]) != readPropertyAsDate(id); - writeProperty(id, *reinterpret_cast<QDate *>(a[0])); - break; - case QV4::CompiledData::BuiltinType::DateTime: - needActivate = *reinterpret_cast<QDateTime *>(a[0]) != readPropertyAsDateTime(id); - writeProperty(id, *reinterpret_cast<QDateTime *>(a[0])); - break; - case QV4::CompiledData::BuiltinType::Rect: - needActivate = *reinterpret_cast<QRectF *>(a[0]) != readPropertyAsRectF(id); - writeProperty(id, *reinterpret_cast<QRectF *>(a[0])); - break; - case QV4::CompiledData::BuiltinType::Size: - needActivate = *reinterpret_cast<QSizeF *>(a[0]) != readPropertyAsSizeF(id); - writeProperty(id, *reinterpret_cast<QSizeF *>(a[0])); - break; - case QV4::CompiledData::BuiltinType::Point: - needActivate = *reinterpret_cast<QPointF *>(a[0]) != readPropertyAsPointF(id); - writeProperty(id, *reinterpret_cast<QPointF *>(a[0])); - break; - case QV4::CompiledData::BuiltinType::Time: - needActivate = *reinterpret_cast<QTime *>(a[0]) != readPropertyAsTime(id); - writeProperty(id, *reinterpret_cast<QTime *>(a[0])); - break; - case QV4::CompiledData::BuiltinType::Var: - if (ep) - writeProperty(id, *reinterpret_cast<QVariant *>(a[0])); - break; - case QV4::CompiledData::BuiltinType::InvalidBuiltin: - if (property.isList) { - // Writing such a property is not supported. Content is added through the list property - // methods. - } else if (QV4::MemberData *md = propertyAndMethodStorageAsMemberData()) { - QV4::Scope scope(engine); - QV4::ScopedValue sv(scope, *(md->data() + id)); - // _id because this is an absolute property ID. - const QQmlPropertyData *propertyData = cache->property(_id); + if (property.isList) { + // _id because this is an absolute property ID. + const QQmlPropertyData *propertyData = cache->property(_id); + const QMetaType propType = propertyData->propType(); - if (propertyData->isQObject()) { - QObject *arg = *reinterpret_cast<QObject **>(a[0]); - if (const QV4::QObjectWrapper *wrap = sv->as<QV4::QObjectWrapper>()) - needActivate = wrap->object() != arg; - else + if (propType.flags().testFlag(QMetaType::IsQmlList)) { + // Writing such a property is not supported. Content is added through + // the list property methods. + } else if (QV4::MemberData *md = propertyAndMethodStorageAsMemberData()) { + // Value type list + QV4::Scope scope(engine); + QV4::ScopedObject sequence(scope, *(md->data() + id)); + void *data = sequence + ? QV4::SequencePrototype::getRawContainerPtr(sequence, propType) + : nullptr; + if (data) { + if (!propType.equals(data, a[0])) { + propType.destruct(data); + propType.construct(data, a[0]); needActivate = true; - if (needActivate) - writeProperty(id, arg); + } } else { - const QMetaType propType = propertyData->propType(); - if (const QV4::VariantObject *v = sv->as<QV4::VariantObject>()) { - QVariant &variant = v->d()->data(); - if (variant.metaType() != propType) { + bool success = false; + md->set(engine, id, QV4::SequencePrototype::fromData( + engine, propType, a[0], &success)); + if (!success) { + qmlWarning(object) + << "Could not create a QML sequence object for" + << propType.name(); + } + needActivate = true; + } + } else { + qmlWarning(object) << "Cannot find member data"; + } + } else { + switch (t) { + case QV4::CompiledData::BuiltinType::Int: + needActivate = *reinterpret_cast<int *>(a[0]) != readPropertyAsInt(id); + writeProperty(id, *reinterpret_cast<int *>(a[0])); + break; + case QV4::CompiledData::BuiltinType::Bool: + needActivate = *reinterpret_cast<bool *>(a[0]) != readPropertyAsBool(id); + writeProperty(id, *reinterpret_cast<bool *>(a[0])); + break; + case QV4::CompiledData::BuiltinType::Real: + needActivate = *reinterpret_cast<double *>(a[0]) != readPropertyAsDouble(id); + writeProperty(id, *reinterpret_cast<double *>(a[0])); + break; + case QV4::CompiledData::BuiltinType::String: + needActivate = *reinterpret_cast<QString *>(a[0]) != readPropertyAsString(id); + writeProperty(id, *reinterpret_cast<QString *>(a[0])); + break; + case QV4::CompiledData::BuiltinType::Url: + needActivate = *reinterpret_cast<QUrl *>(a[0]) != readPropertyAsUrl(id); + writeProperty(id, *reinterpret_cast<QUrl *>(a[0])); + break; + case QV4::CompiledData::BuiltinType::Date: + needActivate = *reinterpret_cast<QDate *>(a[0]) != readPropertyAsDate(id); + writeProperty(id, *reinterpret_cast<QDate *>(a[0])); + break; + case QV4::CompiledData::BuiltinType::DateTime: + needActivate = *reinterpret_cast<QDateTime *>(a[0]) != readPropertyAsDateTime(id); + writeProperty(id, *reinterpret_cast<QDateTime *>(a[0])); + break; + case QV4::CompiledData::BuiltinType::Rect: + needActivate = *reinterpret_cast<QRectF *>(a[0]) != readPropertyAsRectF(id); + writeProperty(id, *reinterpret_cast<QRectF *>(a[0])); + break; + case QV4::CompiledData::BuiltinType::Size: + needActivate = *reinterpret_cast<QSizeF *>(a[0]) != readPropertyAsSizeF(id); + writeProperty(id, *reinterpret_cast<QSizeF *>(a[0])); + break; + case QV4::CompiledData::BuiltinType::Point: + needActivate = *reinterpret_cast<QPointF *>(a[0]) != readPropertyAsPointF(id); + writeProperty(id, *reinterpret_cast<QPointF *>(a[0])); + break; + case QV4::CompiledData::BuiltinType::Time: + needActivate = *reinterpret_cast<QTime *>(a[0]) != readPropertyAsTime(id); + writeProperty(id, *reinterpret_cast<QTime *>(a[0])); + break; + case QV4::CompiledData::BuiltinType::Var: + if (ep) + writeProperty(id, *reinterpret_cast<QVariant *>(a[0])); + break; + case QV4::CompiledData::BuiltinType::InvalidBuiltin: + if (QV4::MemberData *md = propertyAndMethodStorageAsMemberData()) { + QV4::Scope scope(engine); + QV4::ScopedValue sv(scope, *(md->data() + id)); + + // _id because this is an absolute property ID. + const QQmlPropertyData *propertyData = cache->property(_id); + + if (propertyData->isQObject()) { + QObject *arg = *reinterpret_cast<QObject **>(a[0]); + if (const auto *wrap = sv->as<QV4::QObjectWrapper>()) + needActivate = wrap->object() != arg; + else needActivate = true; - variant = QVariant(propType, a[0]); - } else if (!propType.equals(variant.constData(), a[0])) { + if (needActivate) + writeProperty(id, arg); + } else { + const QMetaType propType = propertyData->propType(); + if (const auto *v = sv->as<QV4::VariantObject>()) { + QVariant &variant = v->d()->data(); + if (variant.metaType() != propType) { + needActivate = true; + variant = QVariant(propType, a[0]); + } else if (!propType.equals(variant.constData(), a[0])) { + needActivate = true; + propType.destruct(variant.data()); + propType.construct(variant.data(), a[0]); + } + } else { needActivate = true; - propType.destruct(variant.data()); - propType.construct(variant.data(), a[0]); + md->set(engine, id, engine->newVariantObject( + QVariant(propType, a[0]))); } - } else { - needActivate = true; - md->set(engine, id, engine->newVariantObject( - QVariant(propType, a[0]))); } + } else { + qmlWarning(object) << "Cannot find member data"; } - } else { - qmlWarning(object) << "Cannot find member data"; } } diff --git a/src/qmltyperegistrar/qmltypescreator.cpp b/src/qmltyperegistrar/qmltypescreator.cpp index bf74fa9ca2..559b4c71d2 100644 --- a/src/qmltyperegistrar/qmltypescreator.cpp +++ b/src/qmltyperegistrar/qmltypescreator.cpp @@ -88,6 +88,12 @@ void QmlTypesCreator::writeClassProperties(const QmlTypesClassDescription &colle if (collector.elementName.isEmpty()) return; + if (!collector.sequenceValueType.isEmpty()) { + qWarning() << "Ignoring name of sequential container:" << collector.elementName; + qWarning() << "Sequential containers are anonymous. Use QML_ANONYMOUS to register them."; + return; + } + QStringList exports; QStringList metaObjects; diff --git a/src/qmlworkerscript/qv4serialize.cpp b/src/qmlworkerscript/qv4serialize.cpp index 126ebe5e41..b9b4d6aa08 100644 --- a/src/qmlworkerscript/qv4serialize.cpp +++ b/src/qmlworkerscript/qv4serialize.cpp @@ -251,7 +251,11 @@ void Serialize::serialize(QByteArray &data, const QV4::Value &v, ExecutionEngine } reserve(data, sizeof(quint32) + length * sizeof(quint32)); push(data, valueheader(WorkerSequence, length)); - serialize(data, QV4::Value::fromInt32(QV4::SequencePrototype::metaTypeForSequence(o)), engine); // sequence type + + // sequence type + serialize(data, QV4::Value::fromInt32( + QV4::SequencePrototype::metaTypeForSequence(o).id()), engine); + ScopedValue val(scope); for (uint ii = 0; ii < seqLength; ++ii) serialize(data, (val = o->get(ii)), engine); // sequence elements diff --git a/tests/auto/qml/qjsengine/tst_qjsengine.cpp b/tests/auto/qml/qjsengine/tst_qjsengine.cpp index a52084de13..f7b846e9a3 100644 --- a/tests/auto/qml/qjsengine/tst_qjsengine.cpp +++ b/tests/auto/qml/qjsengine/tst_qjsengine.cpp @@ -595,6 +595,7 @@ void tst_QJSEngine::toScriptValueQtQml_data() QTest::newRow("QList<bool>") << QVariant::fromValue(QList<bool>{true, false, true, false}); QTest::newRow("QStringList") << QVariant::fromValue(QStringList{"a", "b", "c", "d"}); QTest::newRow("QList<QUrl>") << QVariant::fromValue(QList<QUrl>{QUrl("htt://a.com"), QUrl("file:///tmp/b/"), QUrl("c.foo"), QUrl("/some/d")}); + QTest::newRow("QList<QPoint>") << QVariant::fromValue(QList<QPointF>() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)); static const QStandardItemModel model(4, 4); QTest::newRow("QModelIndexList") << QVariant::fromValue(QModelIndexList{ model.index(1, 2), model.index(2, 3), model.index(3, 1), model.index(3, 2)}); @@ -642,8 +643,6 @@ void tst_QJSEngine::toScriptValuenotroundtripped_data() QTest::newRow("QList<QObject*>") << QVariant::fromValue(QList<QObject*>() << this) << QVariant(QVariantList() << QVariant::fromValue<QObject *>(this)); QTest::newRow("QObjectList") << QVariant::fromValue(QObjectList() << this) << QVariant(QVariantList() << QVariant::fromValue<QObject *>(this)); - QTest::newRow("QList<QPoint>") << QVariant::fromValue(QList<QPointF>() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)) << QVariant(QVariantList() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)); - QTest::newRow("QVector<QPoint>") << QVariant::fromValue(QVector<QPointF>() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)) << QVariant(QVariantList() << QPointF(42.24, 24.42) << QPointF(42.24, 24.42)); QTest::newRow("VoidStar") << QVariant(QMetaType(QMetaType::VoidStar), nullptr) << QVariant(QMetaType(QMetaType::Nullptr), nullptr); } diff --git a/tests/auto/qml/qqmllanguage/data/ValueTypeListBase.qml b/tests/auto/qml/qqmllanguage/data/ValueTypeListBase.qml new file mode 100644 index 0000000000..e30990f884 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/ValueTypeListBase.qml @@ -0,0 +1,9 @@ +import QtQml +import ValueTypes + +QtObject { + property list<int> a + property list<point> b + property list<derived> x + property list<base> baseList +} diff --git a/tests/auto/qml/qqmllanguage/data/valueTypeList.qml b/tests/auto/qml/qqmllanguage/data/valueTypeList.qml new file mode 100644 index 0000000000..ed1c850d00 --- /dev/null +++ b/tests/auto/qml/qqmllanguage/data/valueTypeList.qml @@ -0,0 +1,22 @@ +import QtQml +import ValueTypes + +ValueTypeListBase { + a: [0, 2, 3, 1] + b: [Qt.point(1, 2), Qt.point(3, 4)] + property int c: a[2] + property point d: b[1] + property derived y + x: [y, y, y] + baseList: [y, y, y] + + Component.onCompleted: { + a[2] = 17 + y.increment() + } + + onObjectNameChanged: { + x[1].increment() + b[1].x = 12 + } +} diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp index 87116db4ad..fa8c2da3c8 100644 --- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp +++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp @@ -391,6 +391,7 @@ private slots: void ambiguousContainingType(); void objectAsBroken(); void customValueTypes(); + void valueTypeList(); private: QQmlEngine engine; @@ -6750,6 +6751,49 @@ void tst_qqmllanguage::customValueTypes() QCOMPARE(qvariant_cast<BaseValueType>(o->property("base")).content(), 13); } +void tst_qqmllanguage::valueTypeList() +{ + QQmlEngine engine; + QQmlComponent c(&engine, testFileUrl("valueTypeList.qml")); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + QScopedPointer<QObject> o(c.create()); + QVERIFY(!o.isNull()); + + { + QCOMPARE(o->property("c").toInt(), 17); + QCOMPARE(qvariant_cast<QPointF>(o->property("d")), QPointF(3, 4)); + QCOMPARE(qvariant_cast<DerivedValueType>(o->property("y")).content(), 29); + const QList<DerivedValueType> x = qvariant_cast<QList<DerivedValueType>>(o->property("x")); + QCOMPARE(x.length(), 3); + for (const DerivedValueType &d : x) + QCOMPARE(d.content(), 29); + + const QList<BaseValueType> baseList + = qvariant_cast<QList<BaseValueType>>(o->property("baseList")); + QCOMPARE(baseList.length(), 3); + for (const BaseValueType &b : baseList) + QCOMPARE(b.content(), 29); + } + + o->setObjectName(QStringLiteral("foo")); + { + // See QTBUG-99766 + QEXPECT_FAIL("", "Write-back for value types is still incomplete", Abort); + QCOMPARE(qvariant_cast<QPointF>(o->property("d")), QPointF(12, 4)); + QCOMPARE(qvariant_cast<DerivedValueType>(o->property("y")).content(), 30); + const QList<DerivedValueType> x = qvariant_cast<QList<DerivedValueType>>(o->property("x")); + QCOMPARE(x.length(), 3); + for (const DerivedValueType &d : x) + QCOMPARE(d.content(), 30); + + const QList<BaseValueType> baseList + = qvariant_cast<QList<BaseValueType>>(o->property("baseList")); + QCOMPARE(baseList.length(), 3); + for (const BaseValueType &b : baseList) + QCOMPARE(b.content(), 30); + } +} + QTEST_MAIN(tst_qqmllanguage) #include "tst_qqmllanguage.moc" |