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>
This commit is contained in:
Ulf Hermann 2022-01-07 11:01:56 +01:00
parent 8c4c0605b0
commit b0fc028cb5
26 changed files with 605 additions and 355 deletions

View File

@ -307,7 +307,7 @@ property is only invoked when the property is reassigned to a different object v
\ingroup qmlvaluetypes \ingroup qmlvaluetypes
\brief a list of QML objects. \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: 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, Values can be dynamically added to the list by using the \c push method,
as if it were a JavaScript Array as if it were a JavaScript Array
A \c list can only store QML objects, and cannot contain any A \c list can store QML objects or \l{QML Value Types}{value type} values.
\l {QML Value Types}{value type} values. (To store value types within a
list, use the \l var type instead.)
When integrating with C++, note that any QQmlListProperty value When integrating with C++, note that any QQmlListProperty value
\l{qtqml-cppintegration-data.html}{passed into QML from C++} is automatically \l{qtqml-cppintegration-data.html}{passed into QML from C++} is automatically
converted into a \c list value, and vice-versa. 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 \section1 Using the list Type
@ -367,13 +367,17 @@ property is only invoked when the property is reassigned to a different object v
} }
\endqml \endqml
Objects in a list can be replaced with the \c{[]} operator, just like Objects and values in a list can be replaced with the \c{[]} operator, just
entries of JavaScript arrays. You can also use \c{push()} to append entries, like entries of JavaScript arrays. You can also use \c{push()} to append
or you can set the \c length property of the list to truncate or extend it. entries, or you can set the \c length property of the list to truncate or
You can not automatically extend the list by assigning to an index currently extend it. You can not automatically extend the list by assigning to an
out of range, though. Furthermore, if you insert \c null values into the index currently out of range, though. Furthermore, if you insert \c null
list, those are converted to \c nullptr entries in the underlying values into a list of objects, those are converted to \c nullptr entries in
QQmlListProperty. 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. This value type is provided by the QML language.

View File

@ -131,11 +131,12 @@ static ReturnedValue loadProperty(ExecutionEngine *v4, QObject *object,
Q_ASSERT(!property.isFunction()); Q_ASSERT(!property.isFunction());
Scope scope(v4); Scope scope(v4);
const QMetaType propMetaType = property.propType();
if (property.isQObject()) { if (property.isQObject()) {
QObject *rv = nullptr; QObject *rv = nullptr;
property.readProperty(object, &rv); property.readProperty(object, &rv);
ReturnedValue ret = QObjectWrapper::wrap(v4, rv); ReturnedValue ret = QObjectWrapper::wrap(v4, rv);
if (property.propType().flags().testFlag(QMetaType::IsConst)) { if (propMetaType.flags().testFlag(QMetaType::IsConst)) {
ScopedValue v(scope, ret); ScopedValue v(scope, ret);
if (auto obj = v->as<Object>()) { if (auto obj = v->as<Object>()) {
obj->setInternalClass(obj->internalClass()->cryopreserved()); obj->setInternalClass(obj->internalClass()->cryopreserved());
@ -145,10 +146,9 @@ static ReturnedValue loadProperty(ExecutionEngine *v4, QObject *object,
return ret; return ret;
} }
if (property.isQList()) if (property.isQList() && propMetaType.flags().testFlag(QMetaType::IsQmlList))
return QmlListWrapper::create(v4, object, property.coreIndex(), property.propType()); return QmlListWrapper::create(v4, object, property.coreIndex(), propMetaType);
const QMetaType propMetaType = property.propType();
switch (property.isEnum() ? QMetaType::Int : propMetaType.id()) { switch (property.isEnum() ? QMetaType::Int : propMetaType.id()) {
case QMetaType::Int: { case QMetaType::Int: {
int v = 0; int v = 0;
@ -205,23 +205,23 @@ static ReturnedValue loadProperty(ExecutionEngine *v4, QObject *object,
if (QQmlMetaType::isValueType(propMetaType)) { if (QQmlMetaType::isValueType(propMetaType)) {
if (const QMetaObject *valueTypeMetaObject = QQmlMetaType::metaObjectForValueType(propMetaType)) if (const QMetaObject *valueTypeMetaObject = QQmlMetaType::metaObjectForValueType(propMetaType))
return QQmlValueTypeWrapper::create(v4, object, property.coreIndex(), valueTypeMetaObject, 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()) { if (!propMetaType.isValid()) {
QMetaProperty p = object->metaObject()->property(property.coreIndex()); QMetaProperty p = object->metaObject()->property(property.coreIndex());
qWarning("QMetaProperty::read: Unable to handle unregistered datatype '%s' for property " qWarning("QMetaProperty::read: Unable to handle unregistered datatype '%s' for property "
"'%s::%s'", p.typeName(), object->metaObject()->className(), p.name()); "'%s::%s'", p.typeName(), object->metaObject()->className(), p.name());
return Encode::undefined(); return Encode::undefined();
} else { } else {
QVariant v(property.propType(), (void *)nullptr); QVariant v(propMetaType);
property.readProperty(object, v.data()); property.readProperty(object, v.data());
return scope.engine->fromVariant(v); 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 (auto sequenceMetaType = SequencePrototype::metaTypeForSequence(obj); sequenceMetaType.isValid()) {
if (sequenceMetaType == conversionType) if (sequenceMetaType == conversionMetaType)
return 1; return 1;
else else
return 10; return 10;
@ -1888,7 +1888,8 @@ bool CallArgument::fromContainerValue(const Value &value, M CallArgument::*membe
{ {
const Object *object = value.as<Object>(); const Object *object = value.as<Object>();
if (object && object->isListType()) { 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; (this->*member) = ptr;
return true; return true;
} }

View File

@ -81,7 +81,7 @@ struct QV4Sequence : Object {
void init(const QQmlType &qmlType, const void *container); void init(const QQmlType &qmlType, const void *container);
void init(QObject *object, int propertyIndex, const QQmlType &qmlType, bool readOnly); void init(QObject *object, int propertyIndex, const QQmlType &qmlType, bool readOnly);
void destroy() { void destroy() {
typePrivate->typeId.destroy(container); typePrivate->listId.destroy(container);
QQmlType::derefHandle(typePrivate); QQmlType::derefHandle(typePrivate);
object.destroy(); object.destroy();
Object::destroy(); Object::destroy();
@ -110,31 +110,41 @@ struct QV4Sequence : public QV4::Object
V4_NEEDS_DESTROY V4_NEEDS_DESTROY
public: 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 qsizetype size() const
{ {
const auto *p = d(); const auto *p = d();
return meta(p)->size(p->container); return metaSequence(p)->size(p->container);
} }
QVariant at(int index) const QVariant at(int index) const
{ {
const auto *p = d(); const auto *p = d();
const auto *m = meta(p); QVariant result(valueMetaType(p));
QVariant result(m->valueMetaType()); metaSequence(p)->valueAtIndex(p->container, index, result.data());
m->valueAtIndex(p->container, index, result.data());
return result; return result;
} }
void append(const QVariant &item) void append(const QVariant &item)
{ {
const auto *p = d(); const auto *p = d();
const auto *m = meta(p); const auto *m = metaSequence(p);
if (item.metaType() == m->valueMetaType()) { const QMetaType v = valueMetaType(p);
if (item.metaType() == v) {
m->addValueAtEnd(p->container, item.constData()); m->addValueAtEnd(p->container, item.constData());
} else { } else {
QVariant converted = item; QVariant converted = item;
if (!converted.convert(m->valueMetaType())) if (!converted.convert(v))
converted = QVariant(m->valueMetaType()); converted = QVariant(v);
m->addValueAtEnd(p->container, converted.constData()); m->addValueAtEnd(p->container, converted.constData());
} }
} }
@ -142,13 +152,14 @@ public:
void replace(int index, const QVariant &item) void replace(int index, const QVariant &item)
{ {
const auto *p = d(); const auto *p = d();
const auto *m = meta(p); const auto *m = metaSequence(p);
if (item.metaType() == m->valueMetaType()) { const QMetaType v = valueMetaType(p);
if (item.metaType() == v) {
m->setValueAtIndex(p->container, index, item.constData()); m->setValueAtIndex(p->container, index, item.constData());
} else { } else {
QVariant converted = item; QVariant converted = item;
if (!converted.convert(m->valueMetaType())) if (!converted.convert(v))
converted = QVariant(m->valueMetaType()); converted = QVariant(v);
m->setValueAtIndex(p->container, index, converted.constData()); m->setValueAtIndex(p->container, index, converted.constData());
} }
} }
@ -157,9 +168,9 @@ public:
void sort(const Compare &compare) void sort(const Compare &compare)
{ {
const auto *p = d(); 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()) { if (iterable.canRandomAccessIterate()) {
std::sort(QSequentialIterable::RandomAccessIterator(iterable.mutableBegin()), std::sort(QSequentialIterable::RandomAccessIterator(iterable.mutableBegin()),
QSequentialIterable::RandomAccessIterator(iterable.mutableEnd()), QSequentialIterable::RandomAccessIterator(iterable.mutableEnd()),
@ -176,7 +187,7 @@ public:
void removeLast(int num) void removeLast(int num)
{ {
const auto *p = d(); const auto *p = d();
const auto *m = meta(p); const auto *m = metaSequence(p);
if (m->canEraseRangeAtIterator() && m->hasRandomAccessIterator() && num > 1) { if (m->canEraseRangeAtIterator() && m->hasRandomAccessIterator() && num > 1) {
void *i = m->end(p->container); void *i = m->end(p->container);
@ -194,7 +205,7 @@ public:
QVariant toVariant() QVariant toVariant()
{ {
const auto *p = d(); const auto *p = d();
return QVariant(p->typePrivate->typeId, p->container); return QVariant(p->typePrivate->listId, p->container);
} }
// ### Qt 7 use qsizetype instead. // ### Qt 7 use qsizetype instead.
@ -249,8 +260,8 @@ public:
} }
quint32 count = quint32(size()); quint32 count = quint32(size());
const QMetaType valueMetaType = meta(d())->valueMetaType(); const QMetaType valueType = valueMetaType(d());
const QVariant element = engine()->toVariant(value, valueMetaType, false); const QVariant element = engine()->toVariant(value, valueType, false);
if (index == count) { if (index == count) {
append(element); append(element);
@ -260,7 +271,7 @@ public:
/* according to ECMA262r3 we need to insert */ /* according to ECMA262r3 we need to insert */
/* the value at the given index, increasing length to index+1. */ /* the value at the given index, increasing length to index+1. */
while (index > count++) while (index > count++)
append(QVariant(valueMetaType)); append(QVariant(valueType));
append(element); append(element);
} }
@ -471,7 +482,7 @@ void Heap::QV4Sequence::init(const QQmlType &qmlType, const void *container)
typePrivate = qmlType.priv(); typePrivate = qmlType.priv();
QQmlType::refHandle(typePrivate); QQmlType::refHandle(typePrivate);
this->container = QMetaType(typePrivate->typeId).create(container); this->container = typePrivate->listId.create(container);
propertyIndex = -1; propertyIndex = -1;
isReference = false; isReference = false;
isReadOnly = false; isReadOnly = false;
@ -490,7 +501,7 @@ void Heap::QV4Sequence::init(QObject *object, int propertyIndex, const QQmlType
Q_ASSERT(qmlType.isSequentialContainer()); Q_ASSERT(qmlType.isSequentialContainer());
typePrivate = qmlType.priv(); typePrivate = qmlType.priv();
QQmlType::refHandle(typePrivate); QQmlType::refHandle(typePrivate);
container = QMetaType(typePrivate->typeId).create(); container = QMetaType(typePrivate->listId).create();
this->propertyIndex = propertyIndex; this->propertyIndex = propertyIndex;
isReference = true; isReference = true;
this->isReadOnly = readOnly; this->isReadOnly = readOnly;
@ -571,7 +582,6 @@ static QV4::ReturnedValue method_set_length(const FunctionObject *f, const Value
RETURN_UNDEFINED(); RETURN_UNDEFINED();
} }
void SequencePrototype::init() void SequencePrototype::init()
{ {
defineDefaultProperty(QStringLiteral("sort"), method_sort, 1); 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) // (as well as object ptr + property index for updated-read and write-back)
// and so access/mutate avoids variant conversion. // and so access/mutate avoids variant conversion.
const QQmlType qmlType = QQmlMetaType::qmlType(sequenceType); const QQmlType qmlType = QQmlMetaType::qmlListType(sequenceType);
if (qmlType.isSequentialContainer()) { if (qmlType.isSequentialContainer()) {
*succeeded = true; *succeeded = true;
QV4::ScopedObject obj(scope, engine->memoryManager->allocate<QV4Sequence>( 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 // Access and mutation is extremely fast since it will not need to modify any
// QObject property. // QObject property.
const QQmlType qmlType = QQmlMetaType::qmlType(type); const QQmlType qmlType = QQmlMetaType::qmlListType(type);
if (qmlType.isSequentialContainer()) { if (qmlType.isSequentialContainer()) {
*succeeded = true; *succeeded = true;
QV4::ScopedObject obj(scope, engine->memoryManager->allocate<QV4Sequence>(qmlType, data)); 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::Scope scope(array.as<Object>()->engine());
QV4::ScopedArrayObject a(scope, array); QV4::ScopedArrayObject a(scope, array);
const QQmlType type = QQmlMetaType::qmlType(typeHint); const QQmlType type = QQmlMetaType::qmlListType(typeHint);
if (type.isSequentialContainer()) { if (type.isSequentialContainer()) {
const QMetaSequence *meta = type.priv()->extraData.ld; const QQmlTypePrivate *priv = type.priv();
const QMetaType containerMetaType(type.priv()->typeId); const QMetaSequence *meta = priv->extraData.ld;
const QMetaType containerMetaType(priv->listId);
QVariant result(containerMetaType); QVariant result(containerMetaType);
quint32 length = a->getLength(); quint32 length = a->getLength();
QV4::ScopedValue v(scope); QV4::ScopedValue v(scope);
for (quint32 i = 0; i < length; ++i) { 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); 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); variant = QVariant(valueMetaType);
}
meta->addValueAtEnd(result.data(), variant.constData()); meta->addValueAtEnd(result.data(), variant.constData());
} }
return result; return result;
@ -687,20 +704,20 @@ QVariant SequencePrototype::toVariant(const QV4::Value &array, QMetaType typeHin
return QVariant(); 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 (auto *s = object->as<QV4Sequence>()) {
if (s->d()->typePrivate->typeId.id() == typeHint) if (s->d()->typePrivate->listId == typeHint)
return s->getRawContainerPtr(); return s->getRawContainerPtr();
} }
return nullptr; return nullptr;
} }
int SequencePrototype::metaTypeForSequence(const QV4::Object *object) QMetaType SequencePrototype::metaTypeForSequence(const QV4::Object *object)
{ {
if (auto *s = object->as<QV4Sequence>()) if (auto *s = object->as<QV4Sequence>())
return s->d()->typePrivate->typeId.id(); return s->d()->typePrivate->listId;
return -1; return QMetaType();
} }
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -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 fromVariant(QV4::ExecutionEngine *engine, const QVariant &v, bool *succeeded);
static ReturnedValue fromData(QV4::ExecutionEngine *engine, QMetaType type, const void *data, 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(Object *object);
static QVariant toVariant(const Value &array, QMetaType typeHint, bool *succeeded); 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);
}; };
} }

View File

@ -479,7 +479,7 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data)
noCreateReason = QLatin1String("Type cannot be created in QML."); noCreateReason = QLatin1String("Type cannot be created in QML.");
} }
RegisterType revisionRegistration = { RegisterType typeRevision = {
1, 1,
type.typeId, type.typeId,
type.listId, type.listId,
@ -504,6 +504,16 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data)
type.structVersion > 0 ? type.finalizerCast : -1 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( const QTypeRevision added = revisionClassInfo(
type.classInfoMetaObject, "QML.AddedInVersion", type.classInfoMetaObject, "QML.AddedInVersion",
QTypeRevision::fromMinorVersion(0)); QTypeRevision::fromMinorVersion(0));
@ -524,19 +534,28 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data)
break; break;
// When removed, we still add revisions, but anonymous ones // When removed, we still add revisions, but anonymous ones
if (removed.isValid() && !(revision < removed)) { if (removed.isValid() && !(revision < removed)) {
revisionRegistration.elementName = nullptr; typeRevision.elementName = nullptr;
revisionRegistration.create = nullptr; typeRevision.create = nullptr;
} else { } else {
revisionRegistration.elementName = elementName; typeRevision.elementName = elementName;
revisionRegistration.create = creatable ? type.create : nullptr; typeRevision.create = creatable ? type.create : nullptr;
revisionRegistration.userdata = type.userdata; typeRevision.userdata = type.userdata;
} }
assignVersions(&revisionRegistration, revision, type.version); assignVersions(&typeRevision, revision, type.version);
revisionRegistration.customParser = type.customParserFactory(); typeRevision.customParser = type.customParserFactory();
const int id = qmlregister(TypeRegistration, &revisionRegistration); const int id = qmlregister(TypeRegistration, &typeRevision);
if (type.qmlTypeIds) if (type.qmlTypeIds)
type.qmlTypeIds->append(id); 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; break;
} }
@ -594,12 +613,11 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data)
case SequentialContainerAndRevisionsRegistration: { case SequentialContainerAndRevisionsRegistration: {
const RegisterSequentialContainerAndRevisions &type const RegisterSequentialContainerAndRevisions &type
= *reinterpret_cast<RegisterSequentialContainerAndRevisions *>(data); = *reinterpret_cast<RegisterSequentialContainerAndRevisions *>(data);
const char *elementName = classElementName(type.classInfoMetaObject);
RegisterSequentialContainer revisionRegistration = { RegisterSequentialContainer revisionRegistration = {
0, 0,
type.uri, type.uri,
type.version, type.version,
elementName, nullptr,
type.typeId, type.typeId,
type.metaSequence, type.metaSequence,
QTypeRevision() QTypeRevision()
@ -608,10 +626,8 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data)
const QTypeRevision added = revisionClassInfo( const QTypeRevision added = revisionClassInfo(
type.classInfoMetaObject, "QML.AddedInVersion", type.classInfoMetaObject, "QML.AddedInVersion",
QTypeRevision::fromMinorVersion(0)); QTypeRevision::fromMinorVersion(0));
const QTypeRevision removed = revisionClassInfo( QList<QTypeRevision> revisions = revisionClassInfos(
type.classInfoMetaObject, "QML.RemovedInVersion"); type.classInfoMetaObject, "QML.ExtraVersion");
QList<QTypeRevision> revisions = revisionClassInfos(type.classInfoMetaObject,
"QML.ExtraVersion");
revisions.append(added); revisions.append(added);
uniqueRevisions(&revisions, type.version, 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()) if (revision.hasMajorVersion() && revision.majorVersion() > type.version.majorVersion())
break; 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); assignVersions(&revisionRegistration, revision, type.version);
const int id = qmlregister(SequentialContainerRegistration, &revisionRegistration); const int id = qmlregister(SequentialContainerRegistration, &revisionRegistration);
if (type.qmlTypeIds) if (type.qmlTypeIds)
@ -697,40 +707,42 @@ void QQmlPrivate::qmlunregister(RegistrationType type, quintptr data)
namespace QQmlPrivate { namespace QQmlPrivate {
template<> template<>
void qmlRegisterTypeAndRevisions<QQmlTypeNotAvailable, void>(const char *uri, int versionMajor, void qmlRegisterTypeAndRevisions<QQmlTypeNotAvailable, void>(
const QMetaObject *classInfoMetaObject, const char *uri, int versionMajor, const QMetaObject *classInfoMetaObject,
QVector<int> *qmlTypeIds, QVector<int> *qmlTypeIds, const QMetaObject *extension, bool)
const QMetaObject *extension, bool)
{ {
using T = QQmlTypeNotAvailable; using T = QQmlTypeNotAvailable;
RegisterTypeAndRevisions type = { 1, RegisterTypeAndRevisions type = {
QMetaType::fromType<T *>(), 3,
QMetaType::fromType<QQmlListProperty<T>>(), QmlMetaType<T>::self(),
0, QmlMetaType<T>::list(),
nullptr, 0,
nullptr, nullptr,
nullptr, nullptr,
nullptr,
uri, uri,
QTypeRevision::fromMajorVersion(versionMajor), QTypeRevision::fromMajorVersion(versionMajor),
&QQmlTypeNotAvailable::staticMetaObject, &QQmlTypeNotAvailable::staticMetaObject,
classInfoMetaObject, classInfoMetaObject,
attachedPropertiesFunc<T>(), attachedPropertiesFunc<T>(),
attachedPropertiesMetaObject<T>(), attachedPropertiesMetaObject<T>(),
StaticCastSelector<T, QQmlParserStatus>::cast(), StaticCastSelector<T, QQmlParserStatus>::cast(),
StaticCastSelector<T, QQmlPropertyValueSource>::cast(), StaticCastSelector<T, QQmlPropertyValueSource>::cast(),
StaticCastSelector<T, QQmlPropertyValueInterceptor>::cast(), StaticCastSelector<T, QQmlPropertyValueInterceptor>::cast(),
nullptr, nullptr,
extension, extension,
qmlCreateCustomParser<T>, qmlCreateCustomParser<T>,
qmlTypeIds, qmlTypeIds,
QQmlPrivate::StaticCastSelector<T, QQmlFinalizerHook>::cast(), QQmlPrivate::StaticCastSelector<T, QQmlFinalizerHook>::cast(),
false }; false,
QmlMetaType<T>::sequence(),
};
qmlregister(TypeAndRevisionsRegistration, &type); qmlregister(TypeAndRevisionsRegistration, &type);
} }

View File

@ -843,35 +843,37 @@ inline void qmlRegisterNamespaceAndRevisions(const QMetaObject *metaObject,
const QMetaObject *classInfoMetaObject, const QMetaObject *classInfoMetaObject,
const QMetaObject *extensionMetaObject) const QMetaObject *extensionMetaObject)
{ {
QQmlPrivate::RegisterTypeAndRevisions type = { 1, QQmlPrivate::RegisterTypeAndRevisions type = {
QMetaType(), 3,
QMetaType(), QMetaType(),
0, QMetaType(),
nullptr, 0,
nullptr, nullptr,
nullptr, nullptr,
nullptr,
uri, uri,
QTypeRevision::fromMajorVersion(versionMajor), QTypeRevision::fromMajorVersion(versionMajor),
metaObject, metaObject,
(classInfoMetaObject ? classInfoMetaObject (classInfoMetaObject ? classInfoMetaObject : metaObject),
: metaObject),
nullptr, nullptr,
nullptr, nullptr,
-1, -1,
-1, -1,
-1, -1,
nullptr, nullptr,
extensionMetaObject, extensionMetaObject,
&qmlCreateCustomParser<void>, &qmlCreateCustomParser<void>,
qmlTypeIds, qmlTypeIds,
-1, -1,
false }; false,
QMetaSequence()
};
qmlregister(QQmlPrivate::TypeAndRevisionsRegistration, &type); qmlregister(QQmlPrivate::TypeAndRevisionsRegistration, &type);
} }

View File

@ -530,7 +530,10 @@ Q_NEVER_INLINE bool QQmlBinding::slowWrite(const QQmlPropertyData &core,
if (isUndefined) { if (isUndefined) {
} else if (core.isQList()) { } 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()) { } else if (result.isNull() && core.isQObject()) {
value = QVariant::fromValue((QObject *)nullptr); value = QVariant::fromValue((QObject *)nullptr);
} else if (core.propType() == QMetaType::fromType<QList<QUrl>>()) { } else if (core.propType() == QMetaType::fromType<QList<QUrl>>()) {

View File

@ -82,7 +82,7 @@ public:
{ {
if (!m_elementType) { if (!m_elementType) {
m_elementType = QQmlMetaType::rawMetaObjectForType( m_elementType = QQmlMetaType::rawMetaObjectForType(
QQmlMetaType::listType(propertyType)).metaObject(); QQmlMetaType::listValueType(propertyType)).metaObject();
} }
return m_elementType; return m_elementType;

View File

@ -475,12 +475,15 @@ static void addTypeToData(QQmlTypePrivate *type, QQmlMetaTypeData *data)
if (type->baseMetaObject) if (type->baseMetaObject)
data->metaObjectToType.insert(type->baseMetaObject, type); data->metaObjectToType.insert(type->baseMetaObject, type);
if (type->typeId.isValid()) { if (type->regType == QQmlType::SequentialContainerType) {
data->idToType.insert(type->typeId.id(), type); 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()) { if (type->listId.flags().testFlag(QMetaType::IsQmlList))
data->idToType.insert(type->listId.id(), type); data->idToType.insert(type->listId.id(), type);
} }
if (!type->module.isEmpty()) { if (!type->module.isEmpty()) {
@ -637,8 +640,7 @@ QQmlType QQmlMetaType::registerSequentialContainer(
QQmlMetaTypeDataPtr data; QQmlMetaTypeDataPtr data;
const QString typeName = QString::fromUtf8(container.typeName); if (!checkRegistration(QQmlType::SequentialContainerType, data, container.uri, QString(),
if (!checkRegistration(QQmlType::SequentialContainerType, data, container.uri, typeName,
container.version, {})) { container.version, {})) {
return QQmlType(); return QQmlType();
} }
@ -646,10 +648,11 @@ QQmlType QQmlMetaType::registerSequentialContainer(
QQmlTypePrivate *priv = new QQmlTypePrivate(QQmlType::SequentialContainerType); QQmlTypePrivate *priv = new QQmlTypePrivate(QQmlType::SequentialContainerType);
data->registerType(priv); data->registerType(priv);
priv->setName(QString::fromUtf8(container.uri), typeName); priv->setName(QString::fromUtf8(container.uri), QString());
priv->version = container.version; priv->version = container.version;
priv->revision = container.revision; priv->revision = container.revision;
priv->typeId = container.typeId; priv->typeId = container.metaSequence.valueMetaType();
priv->listId = container.typeId;
*priv->extraData.ld = container.metaSequence; *priv->extraData.ld = container.metaSequence;
addTypeToData(priv, data); 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. Returns the item type for a list of type \a id.
*/ */
QMetaType QQmlMetaType::listType(QMetaType metaType) QMetaType QQmlMetaType::listValueType(QMetaType metaType)
{ {
if (!isList(metaType)) if (isList(metaType)) {
return QMetaType {}; const auto iface = metaType.iface();
const auto iface = metaType.iface(); if (iface->metaObjectFn == &dynamicQmlListMarker)
if (iface->metaObjectFn == &dynamicQmlListMarker) return QMetaType(static_cast<const QQmlListMetaTypeInterface *>(iface)->valueType);
return QMetaType(static_cast<const QQmlListMetaTypeInterface *>(iface)->valueType); } else if (metaType.flags() & QMetaType::PointerToQObject) {
return QMetaType();
}
QQmlMetaTypeDataPtr data; QQmlMetaTypeDataPtr data;
QQmlTypePrivate *type = data->idToType.value(metaType.id()); QQmlTypePrivate *type = data->idToType.value(metaType.id());
if (type && type->listId == metaType) if (type && type->listId == metaType)
@ -1258,6 +1264,13 @@ QQmlType QQmlMetaType::qmlType(QMetaType metaType)
return (type && type->typeId == metaType) ? QQmlType(type) : QQmlType(); 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 Returns the type (if any) that corresponds to the given \a url in the set of
composite types added through file imports. composite types added through file imports.

View File

@ -172,6 +172,8 @@ public:
static QQmlType qmlTypeById(int qmlTypeId); static QQmlType qmlTypeById(int qmlTypeId);
static QQmlType qmlType(QMetaType metaType); static QQmlType qmlType(QMetaType metaType);
static QQmlType qmlListType(QMetaType metaType);
static QQmlType qmlType(const QUrl &unNormalizedUrl, bool includeNonFileImports = false); static QQmlType qmlType(const QUrl &unNormalizedUrl, bool includeNonFileImports = false);
static QQmlRefPointer<QQmlPropertyCache> propertyCache( static QQmlRefPointer<QQmlPropertyCache> propertyCache(
@ -198,7 +200,7 @@ public:
static QObject *toQObject(const QVariant &, bool *ok = nullptr); static QObject *toQObject(const QVariant &, bool *ok = nullptr);
static QMetaType listType(QMetaType type); static QMetaType listValueType(QMetaType type);
static QQmlAttachedPropertiesFunc attachedPropertiesFunc(QQmlEnginePrivate *, static QQmlAttachedPropertiesFunc attachedPropertiesFunc(QQmlEnginePrivate *,
const QMetaObject *); const QMetaObject *);
static bool isInterface(QMetaType type); static bool isInterface(QMetaType type);

View File

@ -259,7 +259,7 @@ void QQmlObjectCreator::populateDeferred(QObject *instance, int deferredIndex,
const QQmlPropertyData &property = qmlProperty->core; const QQmlPropertyData &property = qmlProperty->core;
if (property.isQList()) { if (property.propType().flags().testFlag(QMetaType::IsQmlList)) {
void *argv[1] = { (void*)&_currentList }; void *argv[1] = { (void*)&_currentList };
QMetaObject::metacall(_qobject, QMetaObject::ReadProperty, property.coreIndex(), argv); QMetaObject::metacall(_qobject, QMetaObject::ReadProperty, property.coreIndex(), argv);
} else if (_currentList.object) { } else if (_currentList.object) {
@ -693,7 +693,7 @@ void QQmlObjectCreator::setupBindings(BindingSetupFlags mode)
continue; continue;
} }
if (property && property->isQList()) { if (property && property->propType().flags().testFlag(QMetaType::IsQmlList)) {
if (property->coreIndex() != currentListPropertyIndex) { if (property->coreIndex() != currentListPropertyIndex) {
void *argv[1] = { (void*)&_currentList }; void *argv[1] = { (void*)&_currentList };
QMetaObject::metacall(_qobject, QMetaObject::ReadProperty, property->coreIndex(), argv); QMetaObject::metacall(_qobject, QMetaObject::ReadProperty, property->coreIndex(), argv);
@ -1081,12 +1081,12 @@ bool QQmlObjectCreator::setPropertyBinding(const QQmlPropertyData *bindingProper
argv[0] = &value; argv[0] = &value;
QMetaObject::metacall(_qobject, QMetaObject::WriteProperty, bindingProperty->coreIndex(), argv); QMetaObject::metacall(_qobject, QMetaObject::WriteProperty, bindingProperty->coreIndex(), argv);
} }
} else if (bindingProperty->isQList()) { } else if (bindingProperty->propType().flags().testFlag(QMetaType::IsQmlList)) {
Q_ASSERT(_currentList.object); Q_ASSERT(_currentList.object);
void *itemToAdd = createdSubObject; void *itemToAdd = createdSubObject;
QMetaType listItemType = QQmlMetaType::listType(bindingProperty->propType()); QMetaType listItemType = QQmlMetaType::listValueType(bindingProperty->propType());
if (listItemType.isValid()) { if (listItemType.isValid()) {
const char *iid = QQmlMetaType::interfaceIId(listItemType); const char *iid = QQmlMetaType::interfaceIId(listItemType);
if (iid) if (iid)

View File

@ -513,6 +513,7 @@ namespace QQmlPrivate
int finalizerCast; int finalizerCast;
bool forceAnonymous; bool forceAnonymous;
QMetaSequence listMetaSequence;
}; };
struct RegisterInterface { struct RegisterInterface {
@ -590,7 +591,11 @@ namespace QQmlPrivate
int structVersion; int structVersion;
const char *uri; const char *uri;
QTypeRevision version; QTypeRevision version;
// ### Qt7: Remove typeName. It's ignored because the only valid name is "list",
// and that's automatic.
const char *typeName; const char *typeName;
QMetaType typeId; QMetaType typeId;
QMetaSequence metaSequence; QMetaSequence metaSequence;
QTypeRevision revision; QTypeRevision revision;
@ -931,7 +936,15 @@ namespace QQmlPrivate
if constexpr (std::is_base_of_v<QObject, T>) if constexpr (std::is_base_of_v<QObject, T>)
return QMetaType::fromType<QQmlListProperty<T>>(); return QMetaType::fromType<QQmlListProperty<T>>();
else 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); static_assert(std::is_base_of_v<QObject, T> || !QQmlTypeInfo<T>::hasAttachedProperties);
RegisterTypeAndRevisions type = { RegisterTypeAndRevisions type = {
2, 3,
QmlMetaType<T>::self(), QmlMetaType<T>::self(),
QmlMetaType<T>::list(), QmlMetaType<T>::list(),
int(sizeof(T)), int(sizeof(T)),
@ -1001,7 +1014,8 @@ namespace QQmlPrivate
qmlTypeIds, qmlTypeIds,
StaticCastSelector<T, QQmlFinalizerHook>::cast(), StaticCastSelector<T, QQmlFinalizerHook>::cast(),
forceAnonymous forceAnonymous,
QmlMetaType<T>::sequence(),
}; };
// Initialize the extension so that we can find it by name or ID. // Initialize the extension so that we can find it by name or ID.

View File

@ -1462,43 +1462,54 @@ bool QQmlPropertyPrivate::write(
: urlSequence(value); : urlSequence(value);
return property.writeProperty(object, &urlSeq, flags); return property.writeProperty(object, &urlSeq, flags);
} else if (property.isQList()) { } else if (property.isQList()) {
QQmlMetaObject listType; if (propertyMetaType.flags() & QMetaType::IsQmlList) {
QMetaType listValueType = QQmlMetaType::listValueType(propertyMetaType);
QQmlMetaObject valueMetaObject = QQmlMetaType::rawMetaObjectForType(listValueType);
if (valueMetaObject.isNull())
return false;
listType = QQmlMetaType::rawMetaObjectForType(QQmlMetaType::listType(propertyMetaType)); QQmlListProperty<void> prop;
if (listType.isNull()) property.readProperty(object, &prop);
return false;
QQmlListProperty<void> prop; if (!prop.clear)
property.readProperty(object, &prop); return false;
if (!prop.clear) prop.clear(&prop);
return false;
prop.clear(&prop); if (variantMetaType == QMetaType::fromType<QQmlListReference>()) {
QQmlListReference qdlr = value.value<QQmlListReference>();
if (variantMetaType == QMetaType::fromType<QQmlListReference>()) { for (qsizetype ii = 0; ii < qdlr.count(); ++ii) {
QQmlListReference qdlr = value.value<QQmlListReference>(); 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 < qdlr.count(); ++ii) { for (qsizetype ii = 0; ii < list.count(); ++ii) {
QObject *o = qdlr.at(ii); QObject *o = list.at(ii);
if (o && !QQmlMetaObject::canConvert(o, listType)) if (o && !QQmlMetaObject::canConvert(o, valueMetaObject))
o = nullptr; o = nullptr;
prop.append(&prop, o); prop.append(&prop, o);
} }
} else if (variantMetaType == QMetaType::fromType<QList<QObject *>>()) { } else {
const QList<QObject *> &list = qvariant_cast<QList<QObject *> >(value); QObject *o = QQmlMetaType::toQObject(value);
if (o && !QQmlMetaObject::canConvert(o, valueMetaObject))
for (qsizetype ii = 0; ii < list.count(); ++ii) {
QObject *o = list.at(ii);
if (o && !QQmlMetaObject::canConvert(o, listType))
o = nullptr; o = nullptr;
prop.append(&prop, o); prop.append(&prop, o);
} }
} else if (variantMetaType == propertyMetaType) {
QVariant v = value;
property.writeProperty(object, v.data(), flags);
} else { } else {
QObject *o = QQmlMetaType::toQObject(value); QVariant list(propertyMetaType);
if (o && !QQmlMetaObject::canConvert(o, listType)) const QQmlType type = QQmlMetaType::qmlType(propertyMetaType);
o = nullptr; const QMetaSequence sequence = type.listMetaSequence();
prop.append(&prop, o); if (sequence.canAddValue())
sequence.addValue(list.data(), value.data());
property.writeProperty(object, list.data(), flags);
} }
} else if (variantMetaType == QMetaType::fromType<QJSValue>()) { } else if (variantMetaType == QMetaType::fromType<QJSValue>()) {
QJSValue jsValue = qvariant_cast<QJSValue>(value); QJSValue jsValue = qvariant_cast<QJSValue>(value);

View File

@ -66,6 +66,26 @@ QMetaType QQmlPropertyCacheCreatorBase::metaTypeForPropertyType(QV4::CompiledDat
return QMetaType {}; 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) QByteArray QQmlPropertyCacheCreatorBase::createClassNameTypeByUrl(const QUrl &url)
{ {
const QString path = url.path(); const QString path = url.path();

View File

@ -102,6 +102,7 @@ public:
static QAtomicInt classIndexCounter; static QAtomicInt classIndexCounter;
static QMetaType metaTypeForPropertyType(QV4::CompiledData::BuiltinType type); static QMetaType metaTypeForPropertyType(QV4::CompiledData::BuiltinType type);
static QMetaType listTypeForPropertyType(QV4::CompiledData::BuiltinType type);
static QByteArray createClassNameTypeByUrl(const QUrl &url); static QByteArray createClassNameTypeByUrl(const QUrl &url);
@ -650,11 +651,15 @@ inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject(int
const QV4::CompiledData::BuiltinType type = p->builtinType(); 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; propertyFlags.type = QQmlPropertyData::Flags::VarPropertyType;
if (type != QV4::CompiledData::BuiltinType::InvalidBuiltin) { if (type != QV4::CompiledData::BuiltinType::InvalidBuiltin) {
propertyType = metaTypeForPropertyType(type); propertyType = p->isList
? listTypeForPropertyType(type)
: metaTypeForPropertyType(type);
} else { } else {
Q_ASSERT(!p->isBuiltinType); Q_ASSERT(!p->isBuiltinType);
@ -698,12 +703,11 @@ inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject(int
propertyType = typeIds.id; propertyType = typeIds.id;
} }
} else { } else {
if (p->isList) { if (p->isList)
propertyType = qmltype.qListTypeId(); propertyType = qmltype.qListTypeId();
} else { else
propertyType = qmltype.typeId(); propertyType = qmltype.typeId();
propertyTypeVersion = qmltype.version(); propertyTypeVersion = qmltype.version();
}
} }
if (p->isList) if (p->isList)
@ -714,7 +718,7 @@ inline QQmlError QQmlPropertyCacheCreator<ObjectContainer>::createMetaObject(int
propertyFlags.type = QQmlPropertyData::Flags::ValueType; propertyFlags.type = QQmlPropertyData::Flags::ValueType;
} }
if (!p->isReadOnly && !p->isList) if (!p->isReadOnly && !propertyType.flags().testFlag(QMetaType::IsQmlList))
propertyFlags.setIsWritable(true); propertyFlags.setIsWritable(true);

View File

@ -745,7 +745,7 @@ QQmlError QQmlPropertyValidator::validateObjectBinding(QQmlPropertyData *propert
// We can convert everything to QVariant :) // We can convert everything to QVariant :)
return noError; return noError;
} else if (property->isQList()) { } else if (property->isQList()) {
const QMetaType listType = QQmlMetaType::listType(property->propType()); const QMetaType listType = QQmlMetaType::listValueType(property->propType());
if (!QQmlMetaType::isInterface(listType)) { if (!QQmlMetaType::isInterface(listType)) {
QQmlRefPointer<QQmlPropertyCache> source = propertyCaches.at(binding->value.objectIndex); QQmlRefPointer<QQmlPropertyCache> source = propertyCaches.at(binding->value.objectIndex);
if (!canCoerce(listType, source)) { if (!canCoerce(listType, source)) {

View File

@ -642,6 +642,11 @@ QMetaType QQmlType::qListTypeId() const
return d ? d->listId : QMetaType{}; return d ? d->listId : QMetaType{};
} }
QMetaSequence QQmlType::listMetaSequence() const
{
return isSequentialContainer() ? *d->extraData.ld : QMetaSequence();
}
const QMetaObject *QQmlType::metaObject() const const QMetaObject *QQmlType::metaObject() const
{ {
if (!d) if (!d)

View File

@ -132,6 +132,7 @@ public:
QMetaType typeId() const; QMetaType typeId() const;
QMetaType qListTypeId() const; QMetaType qListTypeId() const;
QMetaSequence listMetaSequence() const;
const QMetaObject *metaObject() const; const QMetaObject *metaObject() const;
const QMetaObject *baseMetaObject() const; const QMetaObject *baseMetaObject() const;

View File

@ -492,7 +492,7 @@ bool QQmlEnumTypeResolver::resolveEnumBindings()
const QString propertyName = stringAt(binding->propertyNameIndex); const QString propertyName = stringAt(binding->propertyNameIndex);
bool notInRevision = false; bool notInRevision = false;
QQmlPropertyData *pd = resolver.property(propertyName, &notInRevision); QQmlPropertyData *pd = resolver.property(propertyName, &notInRevision);
if (!pd) if (!pd || pd->isQList())
continue; continue;
if (!pd->isEnum() && pd->propType().id() != QMetaType::Int) if (!pd->isEnum() && pd->propType().id() != QMetaType::Int)

View File

@ -58,6 +58,7 @@
#include <private/qv4scopedvalue_p.h> #include <private/qv4scopedvalue_p.h>
#include <private/qv4jscall_p.h> #include <private/qv4jscall_p.h>
#include <private/qv4qobjectwrapper_p.h> #include <private/qv4qobjectwrapper_p.h>
#include <private/qv4sequenceobject_p.h>
#include <private/qqmlpropertycachecreator_p.h> #include <private/qqmlpropertycachecreator_p.h>
#include <private/qqmlpropertycachemethodarguments_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()); : QQmlEnginePrivate::get(ctxt->engine());
if (c == QMetaObject::ReadProperty) { if (c == QMetaObject::ReadProperty) {
switch (t) { if (property.isList) {
case QV4::CompiledData::BuiltinType::Int: // _id because this is an absolute property ID.
*reinterpret_cast<int *>(a[0]) = readPropertyAsInt(id); const QQmlPropertyData *propertyData = cache->property(_id);
break; const QMetaType propType = propertyData->propType();
case QV4::CompiledData::BuiltinType::Bool:
*reinterpret_cast<bool *>(a[0]) = readPropertyAsBool(id); if (propType.flags().testFlag(QMetaType::IsQmlList)) {
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) {
// when reading from the list, we need to find the correct MetaObject, // when reading from the list, we need to find the correct MetaObject,
// namely this. However, obejct->metaObject might point to any MetaObject // namely this. However, obejct->metaObject might point to any
// down the inheritance hierarchy, so we need to store how far we have // MetaObject down the inheritance hierarchy, so we need to store how
// to go down // far we have to go down
// To do this, we encode the hierarchy depth together with the id of the // 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 // property in a single quintptr, with the first half storing the depth
// and the second half storing the property id // 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(); mo = mo->parentVMEMetaObject();
++inheritanceDepth; ++inheritanceDepth;
} }
constexpr quintptr usableBits = sizeof(quintptr) * CHAR_BIT; constexpr quintptr idBits = sizeof(quintptr) * CHAR_BIT / 2u;
if (Q_UNLIKELY(inheritanceDepth >= (quintptr(1) << quintptr(usableBits / 2u) ) )) { if (Q_UNLIKELY(inheritanceDepth >= (quintptr(1) << idBits))) {
qmlWarning(object) << "Too many objects in inheritance hierarchy for list property"; qmlWarning(object) << "Too many objects in inheritance hierarchy "
"for list property";
return -1; return -1;
} }
if (Q_UNLIKELY(quintptr(id) >= (quintptr(1) << quintptr(usableBits / 2) ) )) { if (Q_UNLIKELY(quintptr(id) >= (quintptr(1) << idBits))) {
qmlWarning(object) << "Too many properties in object for list property"; qmlWarning(object) << "Too many properties in object "
"for list property";
return -1; return -1;
} }
quintptr encodedIndex = (inheritanceDepth << (usableBits/2)) + id; quintptr encodedIndex = (inheritanceDepth << idBits) + id;
readPropertyAsList(id); // Initializes if necessary readPropertyAsList(id); // Initializes if necessary
*static_cast<QQmlListProperty<QObject> *>(a[0]) *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_append, list_count, list_at,
list_clear, list_replace, list_removeLast); list_clear, list_replace, list_removeLast);
} else if (QV4::MemberData *md = propertyAndMethodStorageAsMemberData()) { } else if (QV4::MemberData *md = propertyAndMethodStorageAsMemberData()) {
// Value type list
QV4::Scope scope(engine); QV4::Scope scope(engine);
QV4::ScopedValue sv(scope, *(md->data() + id)); QV4::ScopedObject sequence(scope, *(md->data() + id));
const void *data = sequence
// _id because this is an absolute property ID. ? QV4::SequencePrototype::getRawContainerPtr(sequence, propType)
const QQmlPropertyData *propertyData = cache->property(_id); : nullptr;
propType.destruct(a[0]);
if (propertyData->isQObject()) { propType.construct(a[0], data);
if (const auto *wrap = sv->as<QV4::QObjectWrapper>()) } else {
*reinterpret_cast<QObject **>(a[0]) = wrap->object(); qmlWarning(object) << "Cannot find member data";
else }
*reinterpret_cast<QObject **>(a[0]) = nullptr; } 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 { } else {
const QMetaType propType = propertyData->propType(); // if the context was disposed,
const void *data = nullptr; // we just return an invalid variant from read.
if (const QV4::VariantObject *v = sv->as<QV4::VariantObject>()) { *reinterpret_cast<QVariant *>(a[0]) = QVariant();
const QVariant &variant = v->d()->data(); }
if (variant.metaType() == propType) break;
data = variant.constData(); 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]); } else {
propType.construct(a[0], data); qmlWarning(object) << "Cannot find member data";
}
}
}
} else if (c == QMetaObject::WriteProperty) {
bool needActivate = false;
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)) {
// 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;
}
} else {
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 { } else {
qmlWarning(object) << "Cannot find member data"; 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));
} else if (c == QMetaObject::WriteProperty) { // _id because this is an absolute property ID.
bool needActivate = false; const QQmlPropertyData *propertyData = cache->property(_id);
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. if (propertyData->isQObject()) {
const QQmlPropertyData *propertyData = cache->property(_id); QObject *arg = *reinterpret_cast<QObject **>(a[0]);
if (const auto *wrap = sv->as<QV4::QObjectWrapper>())
if (propertyData->isQObject()) { needActivate = wrap->object() != arg;
QObject *arg = *reinterpret_cast<QObject **>(a[0]); else
if (const QV4::QObjectWrapper *wrap = sv->as<QV4::QObjectWrapper>())
needActivate = wrap->object() != arg;
else
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) {
needActivate = true; needActivate = true;
variant = QVariant(propType, a[0]); if (needActivate)
} else if (!propType.equals(variant.constData(), a[0])) { writeProperty(id, arg);
needActivate = true;
propType.destruct(variant.data());
propType.construct(variant.data(), a[0]);
}
} else { } else {
needActivate = true; const QMetaType propType = propertyData->propType();
md->set(engine, id, engine->newVariantObject( if (const auto *v = sv->as<QV4::VariantObject>()) {
QVariant(propType, a[0]))); 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;
md->set(engine, id, engine->newVariantObject(
QVariant(propType, a[0])));
}
} }
} else {
qmlWarning(object) << "Cannot find member data";
} }
} else {
qmlWarning(object) << "Cannot find member data";
} }
} }

View File

@ -88,6 +88,12 @@ void QmlTypesCreator::writeClassProperties(const QmlTypesClassDescription &colle
if (collector.elementName.isEmpty()) if (collector.elementName.isEmpty())
return; 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 exports;
QStringList metaObjects; QStringList metaObjects;

View File

@ -251,7 +251,11 @@ void Serialize::serialize(QByteArray &data, const QV4::Value &v, ExecutionEngine
} }
reserve(data, sizeof(quint32) + length * sizeof(quint32)); reserve(data, sizeof(quint32) + length * sizeof(quint32));
push(data, valueheader(WorkerSequence, length)); 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); ScopedValue val(scope);
for (uint ii = 0; ii < seqLength; ++ii) for (uint ii = 0; ii < seqLength; ++ii)
serialize(data, (val = o->get(ii)), engine); // sequence elements serialize(data, (val = o->get(ii)), engine); // sequence elements

View File

@ -595,6 +595,7 @@ void tst_QJSEngine::toScriptValueQtQml_data()
QTest::newRow("QList<bool>") << QVariant::fromValue(QList<bool>{true, false, true, false}); QTest::newRow("QList<bool>") << QVariant::fromValue(QList<bool>{true, false, true, false});
QTest::newRow("QStringList") << QVariant::fromValue(QStringList{"a", "b", "c", "d"}); 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<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); 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)}); 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("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("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); QTest::newRow("VoidStar") << QVariant(QMetaType(QMetaType::VoidStar), nullptr) << QVariant(QMetaType(QMetaType::Nullptr), nullptr);
} }

View File

@ -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
}

View File

@ -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
}
}

View File

@ -391,6 +391,7 @@ private slots:
void ambiguousContainingType(); void ambiguousContainingType();
void objectAsBroken(); void objectAsBroken();
void customValueTypes(); void customValueTypes();
void valueTypeList();
private: private:
QQmlEngine engine; QQmlEngine engine;
@ -6750,6 +6751,49 @@ void tst_qqmllanguage::customValueTypes()
QCOMPARE(qvariant_cast<BaseValueType>(o->property("base")).content(), 13); 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) QTEST_MAIN(tst_qqmllanguage)
#include "tst_qqmllanguage.moc" #include "tst_qqmllanguage.moc"