QtQml: Integrate sequences with registration macros

You get to write QML_SEQUENTIAL_CONTAINER(value_type) now, and
qmltyperegistrar will generate a sensible registration call from that.
A registration might look like this:

struct MyStringListForeign
{
    Q_GADGET
    QML_ANONYMOUS
    QML_SEQUENTIAL_CONTAINER(QString)
    QML_FOREIGN(MyStringList)
    QML_ADDED_IN_VERSION(3, 1)
};

It's unfortunate that we need to use a metaobject to transfer all of
this information, but there is no other sensible way.

Transform the containers defined in qv4sequenceobject.cpp to use the new
style, and move them out of the builtins, into QtQml. Recognize that
only one of them was ever tested, and add tests for the rest.

Task-number: QTBUG-82443
Change-Id: I3a30f9e27266bb575eea26c5daf5dad1ec461cc5
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Ulf Hermann 2020-11-06 15:13:27 +01:00
parent 40c0cbda77
commit f0908255c9
17 changed files with 297 additions and 68 deletions

View File

@ -709,8 +709,6 @@ ExecutionEngine::ExecutionEngine(QJSEngine *jsEngine)
static_cast<VariantPrototype *>(variantPrototype())->init();
#if QT_CONFIG(qml_sequence_object)
static const bool registered = QV4::SequencePrototype::registerDefaultTypes();
Q_UNUSED(registered);
sequencePrototype()->cast<SequencePrototype>()->init();
#endif

View File

@ -37,7 +37,6 @@
**
****************************************************************************/
#include <QtQml/qqml.h>
#include <QtCore/qsequentialiterable.h>
#include "qv4sequenceobject_p.h"
@ -51,10 +50,6 @@
#include "qv4objectiterator_p.h"
#include <private/qqmlmetatype_p.h>
#include <private/qqmltype_p_p.h>
#if QT_CONFIG(qml_itemmodel)
#include <private/qqmlmodelindexvaluetype_p.h>
#include <QtCore/qabstractitemmodel.h>
#endif
#include <algorithm>
@ -581,34 +576,6 @@ namespace QV4 {
DEFINE_OBJECT_VTABLE(QV4Sequence);
}
template<typename SequenceType>
void registerSequenceType()
{
qRegisterMetaType<SequenceType>();
qmlRegisterAnonymousSequentialContainer<SequenceType>("QML", 1);
}
bool SequencePrototype::registerDefaultTypes()
{
registerSequenceType<std::vector<int>>();
registerSequenceType<std::vector<qreal>>();
registerSequenceType<std::vector<bool>>();
registerSequenceType<QList<int>>();
registerSequenceType<QList<qreal>>();
registerSequenceType<QList<bool>>();
registerSequenceType<QStringList>();
registerSequenceType<std::vector<QString>>();
registerSequenceType<QList<QUrl>>();
registerSequenceType<std::vector<QUrl>>();
#if QT_CONFIG(qml_itemmodel)
registerSequenceType<QModelIndexList>();
registerSequenceType<std::vector<QModelIndex>>();
registerSequenceType<QItemSelection>();
#endif
return true;
}
void SequencePrototype::init()
{
defineDefaultProperty(QStringLiteral("sort"), method_sort, 1);

View File

@ -53,12 +53,18 @@
#include <QtCore/qglobal.h>
#include <QtCore/qvariant.h>
#include <QtQml/qqml.h>
#include "qv4value_p.h"
#include "qv4object_p.h"
#include "qv4context_p.h"
#include "qv4string_p.h"
#if QT_CONFIG(qml_itemmodel)
#include <private/qqmlmodelindexvaluetype_p.h>
#include <QtCore/qabstractitemmodel.h>
#endif
QT_REQUIRE_CONFIG(qml_sequence_object);
QT_BEGIN_NAMESPACE
@ -68,7 +74,6 @@ namespace QV4 {
struct Q_QML_PRIVATE_EXPORT SequencePrototype : public QV4::Object
{
V4_PROTOTYPE(arrayPrototype)
static bool registerDefaultTypes();
void init();
static ReturnedValue method_valueOf(const FunctionObject *, const Value *thisObject, const Value *argv, int argc);
@ -84,6 +89,45 @@ struct Q_QML_PRIVATE_EXPORT SequencePrototype : public QV4::Object
}
#define QT_DECLARE_SEQUENTIAL_CONTAINER(LOCAL, FOREIGN, VALUE) \
struct LOCAL \
{ \
Q_GADGET \
QML_ANONYMOUS \
QML_SEQUENTIAL_CONTAINER(VALUE) \
QML_FOREIGN(FOREIGN) \
QML_ADDED_IN_VERSION(2, 0) \
}
// We use the original QT_COORD_TYPE name because that will match up with relevant other
// types in plugins.qmltypes (if you use either float or double, that is; otherwise you're
// on your own).
#ifdef QT_COORD_TYPE
QT_DECLARE_SEQUENTIAL_CONTAINER(QStdRealVectorForeign, std::vector<qreal>, QT_COORD_TYPE);
QT_DECLARE_SEQUENTIAL_CONTAINER(QRealListForeign, QList<qreal>, QT_COORD_TYPE);
#else
QT_DECLARE_SEQUENTIAL_CONTAINER(QRealStdVectorForeign, std::vector<qreal>, double);
QT_DECLARE_SEQUENTIAL_CONTAINER(QRealListForeign, QList<qreal>, double);
#endif
QT_DECLARE_SEQUENTIAL_CONTAINER(QIntStdVectorForeign, std::vector<int>, int);
QT_DECLARE_SEQUENTIAL_CONTAINER(QBoolStdVectorForeign, std::vector<bool>, bool);
QT_DECLARE_SEQUENTIAL_CONTAINER(QStringStdVectorForeign, std::vector<QString>, QString);
QT_DECLARE_SEQUENTIAL_CONTAINER(QUrlStdVectorForeign, std::vector<QUrl>, QUrl);
QT_DECLARE_SEQUENTIAL_CONTAINER(QIntListForeign, QList<int>, int);
QT_DECLARE_SEQUENTIAL_CONTAINER(QBoolListForeign, QList<bool>, bool);
QT_DECLARE_SEQUENTIAL_CONTAINER(QStringListForeign, QStringList, QString);
QT_DECLARE_SEQUENTIAL_CONTAINER(QUrlListForeign, QList<QUrl>, QUrl);
#if QT_CONFIG(qml_itemmodel)
QT_DECLARE_SEQUENTIAL_CONTAINER(QModelIndexListForeign, QModelIndexList, QModelIndex);
QT_DECLARE_SEQUENTIAL_CONTAINER(QModelIndexStdVectorForeign, std::vector<QModelIndex>, QModelIndex);
QT_DECLARE_SEQUENTIAL_CONTAINER(QItemSelectionForeign, QItemSelection, QItemSelectionRange);
#endif
#undef QT_DECLARE_SEQUENTIAL_CONTAINER
QT_END_NAMESPACE
#endif // QV4SEQUENCEWRAPPER_P_H

View File

@ -421,6 +421,48 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data)
}
break;
}
case SequentialContainerAndRevisionsRegistration: {
const RegisterSequentialContainerAndRevisions &type
= *reinterpret_cast<RegisterSequentialContainerAndRevisions *>(data);
const char *elementName = classElementName(type.classInfoMetaObject);
RegisterSequentialContainer revisionRegistration = {
0,
type.uri,
type.version,
elementName,
type.typeId,
type.metaSequence,
QTypeRevision()
};
const QTypeRevision added = revisionClassInfo(
type.classInfoMetaObject, "QML.AddedInVersion",
QTypeRevision::fromMinorVersion(0));
const QTypeRevision removed = revisionClassInfo(
type.classInfoMetaObject, "QML.RemovedInVersion");
QVector<QTypeRevision> revisions = { added };
uniqueRevisions(&revisions, type.version, added);
for (QTypeRevision revision : qAsConst(revisions)) {
if (revision < added)
continue;
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)
type.qmlTypeIds->append(id);
}
break;
}
case TypeRegistration:
dtype = QQmlMetaType::registerType(*reinterpret_cast<RegisterType *>(data));
break;
@ -472,6 +514,7 @@ void QQmlPrivate::qmlunregister(RegistrationType type, quintptr data)
break;
case TypeAndRevisionsRegistration:
case SingletonAndRevisionsRegistration:
case SequentialContainerAndRevisionsRegistration:
// Currently unnecessary. We'd need a special data structure to hold
// URI + majorVersion and then we'd iterate the minor versions, look up the
// associated QQmlType objects by uri/elementName/major/minor and qmlunregister

View File

@ -94,6 +94,14 @@
template<typename T, typename... Args> \
friend void QML_REGISTER_TYPES_AND_REVISIONS(const char *uri, int versionMajor, QList<int> *);
#define QML_SEQUENTIAL_CONTAINER(VALUE_TYPE) \
Q_CLASSINFO("QML.Sequence", #VALUE_TYPE) \
using QmlSequenceValueType = VALUE_TYPE; \
enum class QmlIsSequence {yes = true}; \
template<typename, typename> friend struct QML_PRIVATE_NAMESPACE::QmlSequence; \
template<typename T, typename... Args> \
friend void QML_REGISTER_TYPES_AND_REVISIONS(const char *uri, int versionMajor, QList<int> *);
#define QML_ADDED_IN_MINOR_VERSION(VERSION) \
Q_CLASSINFO("QML.AddedInVersion", Q_REVISION(VERSION))
@ -792,17 +800,18 @@ inline int qmlRegisterAnonymousSequentialContainer(const char *uri, int versionM
QTypeRevision::fromMajorVersion(versionMajor),
nullptr,
QMetaType::fromType<Container>(),
QMetaSequence::fromContainer<Container>()
QMetaSequence::fromContainer<Container>(),
QTypeRevision::zero()
};
return QQmlPrivate::qmlregister(QQmlPrivate::SequentialContainerRegistration, &type);
}
template<class T, class Resolved, class Extended, bool Singleton, bool Interface>
template<class T, class Resolved, class Extended, bool Singleton, bool Interface, bool Sequence>
struct QmlTypeAndRevisionsRegistration;
template<class T, class Resolved, class Extended>
struct QmlTypeAndRevisionsRegistration<T, Resolved, Extended, false, false> {
struct QmlTypeAndRevisionsRegistration<T, Resolved, Extended, false, false, false> {
static void registerTypeAndRevisions(const char *uri, int versionMajor, QList<int> *qmlTypeIds,
const QMetaObject *extension)
{
@ -812,8 +821,19 @@ struct QmlTypeAndRevisionsRegistration<T, Resolved, Extended, false, false> {
}
};
template<class T, class Resolved>
struct QmlTypeAndRevisionsRegistration<T, Resolved, void, false, false, true> {
static void registerTypeAndRevisions(const char *uri, int versionMajor, QList<int> *qmlTypeIds,
const QMetaObject *)
{
QQmlPrivate::qmlRegisterSequenceAndRevisions<Resolved>(
uri, versionMajor, QQmlPrivate::StaticMetaObject<T>::staticMetaObject(),
qmlTypeIds);
}
};
template<class T, class Resolved, class Extended>
struct QmlTypeAndRevisionsRegistration<T, Resolved, Extended, true, false> {
struct QmlTypeAndRevisionsRegistration<T, Resolved, Extended, true, false, false> {
static void registerTypeAndRevisions(const char *uri, int versionMajor, QList<int> *qmlTypeIds,
const QMetaObject *extension)
{
@ -824,7 +844,7 @@ struct QmlTypeAndRevisionsRegistration<T, Resolved, Extended, true, false> {
};
template<class T, class Resolved>
struct QmlTypeAndRevisionsRegistration<T, Resolved, void, false, true> {
struct QmlTypeAndRevisionsRegistration<T, Resolved, void, false, true, false> {
static void registerTypeAndRevisions(const char *uri, int versionMajor, QList<int> *qmlTypeIds,
const QMetaObject *)
{
@ -845,7 +865,8 @@ void qmlRegisterTypesAndRevisions(const char *uri, int versionMajor, QList<int>
T, typename QQmlPrivate::QmlResolved<T>::Type,
typename QQmlPrivate::QmlExtended<T>::Type,
QQmlPrivate::QmlSingleton<T>::Value,
QQmlPrivate::QmlInterface<T>::Value>
QQmlPrivate::QmlInterface<T>::Value,
QQmlPrivate::QmlSequence<T>::Value>
::registerTypeAndRevisions(uri, versionMajor, qmlTypeIds,
QQmlPrivate::QmlExtendedNamespace<T>::metaObject());
qmlRegisterTypesAndRevisions<Args...>(uri, versionMajor, qmlTypeIds);

View File

@ -667,7 +667,8 @@ QQmlType QQmlMetaType::registerSequentialContainer(
QQmlMetaTypeDataPtr data;
if (!checkRegistration(QQmlType::SequentialContainerType, data, container.uri, QString(),
const QString typeName = QString::fromUtf8(container.typeName);
if (!checkRegistration(QQmlType::SequentialContainerType, data, container.uri, typeName,
container.version, {})) {
return QQmlType();
}
@ -675,8 +676,9 @@ QQmlType QQmlMetaType::registerSequentialContainer(
QQmlTypePrivate *priv = new QQmlTypePrivate(QQmlType::SequentialContainerType);
data->registerType(priv);
priv->setName(QString::fromUtf8(container.uri), QString());
priv->setName(QString::fromUtf8(container.uri), typeName);
priv->version = container.version;
priv->revision = container.revision;
priv->typeId = container.typeId;
*priv->extraData.ld = container.metaSequence;

View File

@ -574,6 +574,19 @@ namespace QQmlPrivate
const char *typeName;
QMetaType typeId;
QMetaSequence metaSequence;
QTypeRevision revision;
};
struct RegisterSequentialContainerAndRevisions {
int structVersion;
const char *uri;
QTypeRevision version;
const QMetaObject *classInfoMetaObject;
QMetaType typeId;
QMetaSequence metaSequence;
QVector<int> *qmlTypeIds;
};
struct AOTCompiledFunction {
@ -605,6 +618,7 @@ namespace QQmlPrivate
TypeAndRevisionsRegistration = 7,
SingletonAndRevisionsRegistration = 8,
SequentialContainerRegistration = 9,
SequentialContainerAndRevisionsRegistration = 10,
};
int Q_QML_EXPORT qmlregister(RegistrationType, void *);
@ -716,6 +730,20 @@ namespace QQmlPrivate
static constexpr bool Value = bool(T::QmlIsSingleton::yes);
};
template<class T, class = std::void_t<>>
struct QmlSequence
{
static constexpr bool Value = false;
};
template<class T>
struct QmlSequence<T, std::void_t<typename T::QmlIsSequence>>
{
Q_STATIC_ASSERT((std::is_same_v<typename T::QmlSequenceValueType,
typename QmlResolved<T>::Type::value_type>));
static constexpr bool Value = bool(T::QmlIsSequence::yes);
};
template<class T, class = std::void_t<>>
struct QmlInterface
{
@ -823,6 +851,24 @@ namespace QQmlPrivate
qmlregister(TypeAndRevisionsRegistration, &type);
}
template<typename T>
void qmlRegisterSequenceAndRevisions(const char *uri, int versionMajor,
const QMetaObject *classInfoMetaObject,
QVector<int> *qmlTypeIds)
{
RegisterSequentialContainerAndRevisions type = {
0,
uri,
QTypeRevision::fromMajorVersion(versionMajor),
classInfoMetaObject,
QMetaType::fromType<T>(),
QMetaSequence::fromContainer<T>(),
qmlTypeIds
};
qmlregister(SequentialContainerAndRevisionsRegistration, &type);
}
template<>
void Q_QML_EXPORT qmlRegisterTypeAndRevisions<QQmlTypeNotAvailable, void>(
const char *uri, int versionMajor, const QMetaObject *classInfoMetaObject,

View File

@ -152,6 +152,7 @@ void QQmlJSScope::resolveTypes(const QHash<QString, QQmlJSScope::ConstPtr> &cont
m_baseType = findType(m_baseTypeName);
m_attachedType = findType(m_attachedTypeName);
m_valueType = findType(m_valueTypeName);
for (auto it = m_properties.begin(), end = m_properties.end(); it != end; ++it)
it->setType(findType(it->typeName()));

View File

@ -101,7 +101,8 @@ public:
enum class AccessSemantics {
Reference,
Value,
None
None,
Sequence
};
enum Flag {
@ -200,6 +201,10 @@ public:
void setAttachedTypeName(const QString &name) { m_attachedTypeName = name; }
QQmlJSScope::ConstPtr attachedType() const { return m_attachedType; }
QString valueTypeName() const { return m_valueTypeName; }
void setValueTypeName(const QString &name) { m_valueTypeName = name; }
QQmlJSScope::ConstPtr valueType() const { return m_valueType; }
bool isSingleton() const { return m_flags & Singleton; }
bool isCreatable() const { return m_flags & Creatable; }
bool isComposite() const { return m_flags & Composite; }
@ -258,6 +263,9 @@ private:
QString m_attachedTypeName;
QQmlJSScope::WeakConstPtr m_attachedType;
QString m_valueTypeName;
QQmlJSScope::WeakConstPtr m_valueType;
Flags m_flags;
AccessSemantics m_semantics = AccessSemantics::Reference;

View File

@ -224,6 +224,8 @@ void QQmlJSTypeDescriptionReader::readComponent(UiObjectDefinition *ast)
readMetaObjectRevisions(script, scope);
} else if (name == QLatin1String("attachedType")) {
scope->setAttachedTypeName(readStringBinding(script));
} else if (name == QLatin1String("valueType")) {
scope->setValueTypeName(readStringBinding(script));
} else if (name == QLatin1String("isSingleton")) {
scope->setIsSingleton(readBoolBinding(script));
} else if (name == QLatin1String("isCreatable")) {
@ -238,6 +240,8 @@ void QQmlJSTypeDescriptionReader::readComponent(UiObjectDefinition *ast)
scope->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
} else if (semantics == QLatin1String("none")) {
scope->setAccessSemantics(QQmlJSScope::AccessSemantics::None);
} else if (semantics == QLatin1String("sequence")) {
scope->setAccessSemantics(QQmlJSScope::AccessSemantics::Sequence);
} else {
addWarning(script->firstSourceLocation(),
tr("Unknown access semantics \"%1\".").arg(semantics));
@ -245,7 +249,7 @@ void QQmlJSTypeDescriptionReader::readComponent(UiObjectDefinition *ast)
} else {
addWarning(script->firstSourceLocation(),
tr("Expected only name, prototype, defaultProperty, attachedType, "
"exports, isSingleton, isCreatable, isComposite and "
"valueType, exports, isSingleton, isCreatable, isComposite and "
"exportMetaObjectRevisions script bindings, not \"%1\".").arg(name));
}
} else {

View File

@ -153,6 +153,7 @@ QVector<QJsonObject> MetaTypesJsonProcessor::foreignRelatedTypes() const
const QLatin1String qmlNamePrefix("QML.");
const QLatin1String qmlForeignName("QML.Foreign");
const QLatin1String qmlAttachedName("QML.Attached");
const QLatin1String qmlSequenceName("QML.Sequence");
const QLatin1String valueKey("value");
const QLatin1String superClassesKey("superClasses");
const QLatin1String accessKey("access");
@ -214,9 +215,10 @@ QVector<QJsonObject> MetaTypesJsonProcessor::foreignRelatedTypes() const
const auto classInfos = classDef.value(classInfosKey).toArray();
for (const QJsonValue classInfo : classInfos) {
const QJsonObject obj = classInfo.toObject();
if (obj.value(nameKey).toString() == qmlAttachedName) {
const QString objNameValue = obj.value(nameKey).toString();
if (objNameValue == qmlAttachedName || objNameValue == qmlSequenceName) {
addType(obj.value(valueKey).toString());
} else if (obj.value(nameKey).toString() == qmlForeignName) {
} else if (objNameValue == qmlForeignName) {
const QString foreignClassName = obj.value(valueKey).toString();
if (const QJsonObject *other = QmlTypesClassDescription::findType(
m_foreignTypes, foreignClassName)) {
@ -230,7 +232,8 @@ QVector<QJsonObject> MetaTypesJsonProcessor::foreignRelatedTypes() const
const auto otherClassInfos = other->value(classInfosKey).toArray();
for (const QJsonValue otherClassInfo : otherClassInfos) {
const QJsonObject obj = otherClassInfo.toObject();
if (obj.value(nameKey).toString() == qmlAttachedName) {
const QString objNameValue = obj.value(nameKey).toString();
if (objNameValue == qmlAttachedName || objNameValue == qmlSequenceName) {
addType(obj.value(valueKey).toString());
break;
}

View File

@ -99,7 +99,11 @@ void QmlTypesClassDescription::collect(const QJsonObject *classDef,
} else if (name == QLatin1String("QML.Creatable")) {
isCreatable = (value != QLatin1String("false"));
} else if (name == QLatin1String("QML.Attached")) {
collectAttached(value, types, foreign, defaultRevision);
attachedType = value;
collectRelated(value, types, foreign, defaultRevision);
} else if (name == QLatin1String("QML.Sequence")) {
sequenceValueType = value;
collectRelated(value, types, foreign, defaultRevision);
} else if (name == QLatin1String("QML.Singleton")) {
if (value == QLatin1String("true"))
isSingleton = true;
@ -112,10 +116,15 @@ void QmlTypesClassDescription::collect(const QJsonObject *classDef,
const QJsonObject obj = classInfo.toObject();
const QString foreignName = obj[QLatin1String("name")].toString();
const QString foreignValue = obj[QLatin1String("value")].toString();
if (defaultProp.isEmpty() && foreignName == QLatin1String("DefaultProperty"))
if (defaultProp.isEmpty() && foreignName == QLatin1String("DefaultProperty")) {
defaultProp = foreignValue;
else if (foreignName == QLatin1String("QML.Attached"))
collectAttached(foreignValue, types, foreign, defaultRevision);
} else if (foreignName == QLatin1String("QML.Attached")) {
attachedType = foreignValue;
collectRelated(foreignValue, types, foreign, defaultRevision);
} else if (foreignName == QLatin1String("QML.Sequence")) {
sequenceValueType = foreignValue;
collectRelated(foreignValue, types, foreign, defaultRevision);
}
}
} else {
// The foreign type does not have a meta object: We only override the name.
@ -176,7 +185,10 @@ void QmlTypesClassDescription::collect(const QJsonObject *classDef,
if (className.isEmpty() && mode == TopLevel)
className = classDef->value(QLatin1String("qualifiedClassName")).toString();
if (classDef->value(QLatin1String("object")).toBool()) {
if (!sequenceValueType.isEmpty()) {
isCreatable = false;
accessSemantics = QLatin1String("sequence");
} else if (classDef->value(QLatin1String("object")).toBool()) {
accessSemantics = QLatin1String("reference");
} else {
isCreatable = false;
@ -186,14 +198,13 @@ void QmlTypesClassDescription::collect(const QJsonObject *classDef,
}
}
void QmlTypesClassDescription::collectAttached(const QString &attached,
void QmlTypesClassDescription::collectRelated(const QString &related,
const QVector<QJsonObject> &types,
const QVector<QJsonObject> &foreign,
QTypeRevision defaultRevision)
{
attachedType = attached;
if (const QJsonObject *other = findType(types, attachedType))
if (const QJsonObject *other = findType(types, related))
collect(other, types, foreign, AttachedType, defaultRevision);
else if (const QJsonObject *other = findType(foreign, attachedType))
else if (const QJsonObject *other = findType(foreign, related))
collect(other, types, foreign, AttachedType, defaultRevision);
}

View File

@ -44,6 +44,7 @@ struct QmlTypesClassDescription
QString defaultProp;
QString superClass;
QString attachedType;
QString sequenceValueType;
QString accessSemantics;
QList<QTypeRevision> revisions;
QTypeRevision addedInRevision;
@ -61,8 +62,8 @@ struct QmlTypesClassDescription
void collect(const QJsonObject *classDef, const QVector<QJsonObject> &types,
const QVector<QJsonObject> &foreign, CollectMode mode,
QTypeRevision defaultRevision);
void collectAttached(const QString &attached, const QVector<QJsonObject> &types,
const QVector<QJsonObject> &foreign, QTypeRevision defaultRevision);
void collectRelated(const QString &related, const QVector<QJsonObject> &types,
const QVector<QJsonObject> &foreign, QTypeRevision defaultRevision);
static const QJsonObject *findType(const QVector<QJsonObject> &types, const QString &name);
};

View File

@ -58,6 +58,9 @@ void QmlTypesCreator::writeClassProperties(const QmlTypesClassDescription &colle
if (!collector.superClass.isEmpty())
m_qml.writeScriptBinding(QLatin1String("prototype"), enquote(collector.superClass));
if (!collector.sequenceValueType.isEmpty())
m_qml.writeScriptBinding(QLatin1String("valueType"), enquote(collector.sequenceValueType));
if (collector.elementName.isEmpty())
return;

View File

@ -41,6 +41,8 @@
#include <private/qv4alloca_p.h>
#include <private/qjsvalue_p.h>
#include <QScopeGuard>
#include <QUrl>
#include <QModelIndex>
#ifdef Q_CC_MSVC
#define NO_INLINE __declspec(noinline)
@ -66,8 +68,10 @@ private slots:
void newArray();
void newArray_HooliganTask218092();
void newArray_HooliganTask233836();
void toScriptValue_data();
void toScriptValue();
void toScriptValueBuiltin_data();
void toScriptValueBuiltin();
void toScriptValueQtQml_data();
void toScriptValueQtQml();
void toScriptValuenotroundtripped_data();
void toScriptValuenotroundtripped();
void newVariant();
@ -512,7 +516,7 @@ void tst_QJSEngine::newArray_HooliganTask233836()
}
}
void tst_QJSEngine::toScriptValue_data()
void tst_QJSEngine::toScriptValueBuiltin_data()
{
QTest::addColumn<QVariant>("input");
@ -544,8 +548,6 @@ void tst_QJSEngine::toScriptValue_data()
vm.clear(); vm.insert("point1", QPointF(42.24, 24.42)); vm.insert("point2", QPointF(42.24, 24.42));
QTest::newRow("qvariantmap_point") << QVariant(vm);
QTest::newRow("qvariant") << QVariant(QVariant(42));
QTest::newRow("QList<int>") << QVariant::fromValue(QList<int>() << 1 << 2 << 3 << 4);
QTest::newRow("QVector<int>") << QVariant::fromValue(QVector<int>() << 1 << 2 << 3 << 4);
QTest::newRow("QList<QString>") << QVariant::fromValue(QVector<QString>() << "1" << "2" << "3" << "4");
QTest::newRow("QStringList") << QVariant::fromValue(QStringList() << "1" << "2" << "3" << "4");
QTest::newRow("QMap<QString, QString>") << QVariant::fromValue(QMap<QString, QString>{{ "1", "2" }, { "3", "4" }});
@ -554,7 +556,7 @@ void tst_QJSEngine::toScriptValue_data()
QTest::newRow("QHash<QString, QPointF>") << QVariant::fromValue(QHash<QString, QPointF>{{ "1", { 42.24, 24.42 } }, { "3", { 24.42, 42.24 } }});
}
void tst_QJSEngine::toScriptValue()
void tst_QJSEngine::toScriptValueBuiltin()
{
QFETCH(QVariant, input);
@ -570,6 +572,62 @@ void tst_QJSEngine::toScriptValue()
QCOMPARE(input, output);
}
void tst_QJSEngine::toScriptValueQtQml_data()
{
QTest::addColumn<QVariant>("input");
QTest::newRow("std::vector<qreal>") << QVariant::fromValue(std::vector<qreal>{.1, .2, .3, .4});
QTest::newRow("QList<qreal>") << QVariant::fromValue(QList<qreal>{.1, .2, .3, .4});
QTest::newRow("std::vector<int>") << QVariant::fromValue(std::vector<int>{1, 2, 3, 4});
QTest::newRow("std::vector<bool>") << QVariant::fromValue(std::vector<bool>{true, false, true, false});
QTest::newRow("std::vector<QString>") << QVariant::fromValue(std::vector<QString>{"a", "b", "c", "d"});
QTest::newRow("std::vector<QUrl>") << QVariant::fromValue(std::vector<QUrl>{QUrl("htt://a.com"), QUrl("file:///tmp/b/"), QUrl("c.foo"), QUrl("/some/d")});
QTest::newRow("QList<int>") << QVariant::fromValue(QList<int>{1, 2, 3, 4});
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")});
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("std::vector<QModelIndex>") << QVariant::fromValue(std::vector<QModelIndex>{ model.index(1, 2), model.index(2, 3), model.index(3, 1), model.index(3, 2)});
// QVariant wants to implicitly convert this to a QList<QItemSelectionRange>. Prevent that by
// keeping the instance on the stack, and explicitly instantiating the template below.
QItemSelection selection;
// It doesn't have an initializer list ctor ...
selection << QItemSelectionRange(model.index(1, 2), model.index(2, 3))
<< QItemSelectionRange(model.index(3, 1), model.index(3, 2))
<< QItemSelectionRange(model.index(2, 2), model.index(3, 3))
<< QItemSelectionRange(model.index(1, 1), model.index(2, 2));
QTest::newRow("QItemSelection") << QVariant::fromValue<QItemSelection>(selection);
}
void tst_QJSEngine::toScriptValueQtQml()
{
QFETCH(QVariant, input);
// Import QtQml, to enable the sequential containers defined there.
QQmlEngine engine;
QQmlComponent c(&engine);
c.setData("import QtQml\nQtObject{}", QUrl());
QScopedPointer<QObject> obj(c.create());
QVERIFY(!obj.isNull());
QJSValue outputJS = engine.toScriptValue(input);
QVariant output = engine.fromScriptValue<QVariant>(outputJS);
if (input.metaType().id() == QMetaType::QChar) {
if (!input.convert(QMetaType(QMetaType::QString)))
QFAIL("cannot convert to the original value");
} else if (!output.convert(input.metaType()) && input.isValid())
QFAIL("cannot convert to the original value");
QCOMPARE(input, output);
}
void tst_QJSEngine::toScriptValuenotroundtripped_data()
{
QTest::addColumn<QVariant>("input");

View File

@ -33,6 +33,8 @@
#include <QtWidgets/QPushButton>
#include <QtCore/qthread.h>
#include <QtQml/qqmlengine.h>
#include <QtQml/qqmlcomponent.h>
#include <memory>
@ -2250,8 +2252,8 @@ void tst_QJSValue::strictlyEquals()
{
QJSValue var1 = eng.toScriptValue(QVariant(QStringList() << "a"));
QJSValue var2 = eng.toScriptValue(QVariant(QStringList() << "a"));
QVERIFY(!var1.isArray());
QVERIFY(!var2.isArray());
QVERIFY(var1.isArray());
QVERIFY(var2.isArray());
QVERIFY(!var1.strictlyEquals(var2));
}
{
@ -2269,6 +2271,22 @@ void tst_QJSValue::strictlyEquals()
QJSValue var2 = eng.toScriptValue(QVariant(QPoint(3, 4)));
QVERIFY(!var1.strictlyEquals(var2));
}
{
// Import QtQml to trigger the registration of QStringList, which makes it a sequence
// type, rather than a generic JS array.
QQmlEngine qmlEngine;
QQmlComponent c(&qmlEngine);
c.setData("import QtQml\nQtObject {}", QUrl());
QScopedPointer<QObject> obj(c.create());
QVERIFY(!obj.isNull());
QJSValue var1 = qmlEngine.toScriptValue(QVariant(QStringList() << "a"));
QJSValue var2 = qmlEngine.toScriptValue(QVariant(QStringList() << "a"));
QVERIFY(!var1.isArray());
QVERIFY(!var2.isArray());
QVERIFY(!var1.strictlyEquals(var2));
}
}
Q_DECLARE_METATYPE(int*)

View File

@ -1,3 +1,4 @@
import QtQml
import Test 1.0
ItemModelsTest {