// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant

#include "qqmllistmodel_p_p.h"
#include "qqmllistmodelworkeragent_p.h"

#include <private/qjsvalue_p.h>

#include <private/qqmlcustomparser_p.h>
#include <private/qqmlengine_p.h>
#include <private/qqmljsast_p.h>
#include <private/qqmljsengine_p.h>
#include <private/qqmllistwrapper_p.h>
#include <private/qqmlnotifier_p.h>
#include <private/qqmlopenmetaobject_p.h>

#include <private/qv4alloca_p.h>
#include <private/qv4dateobject_p.h>
#include <private/qv4lookup_p.h>
#include <private/qv4object_p.h>
#include <private/qv4objectiterator_p.h>
#include <private/qv4qmlcontext_p.h>
#include <private/qv4sequenceobject_p.h>
#include <private/qv4urlobject_p.h>

#include <qqmlcontext.h>
#include <qqmlinfo.h>

#include <QtCore/qdebug.h>
#include <QtCore/qstack.h>
#include <QXmlStreamReader>
#include <QtCore/qdatetime.h>
#include <QScopedValueRollback>

Q_DECLARE_METATYPE(const QV4::CompiledData::Binding*);

QT_BEGIN_NAMESPACE

// Set to 1024 as a debugging aid - easier to distinguish uids from indices of elements/models.
enum { MIN_LISTMODEL_UID = 1024 };

static QAtomicInt uidCounter(MIN_LISTMODEL_UID);

template <typename T>
static bool isMemoryUsed(const char *mem)
{
    for (size_t i=0 ; i < sizeof(T) ; ++i) {
        if (mem[i] != 0)
            return true;
    }

    return false;
}

static QString roleTypeName(ListLayout::Role::DataType t)
{
    static const QString roleTypeNames[] = {
        QStringLiteral("String"), QStringLiteral("Number"), QStringLiteral("Bool"),
        QStringLiteral("List"), QStringLiteral("QObject"), QStringLiteral("VariantMap"),
        QStringLiteral("DateTime"), QStringLiteral("Url"), QStringLiteral("Function")
    };

    if (t > ListLayout::Role::Invalid && t < ListLayout::Role::MaxDataType)
        return roleTypeNames[t];

    return QString();
}

const ListLayout::Role &ListLayout::getRoleOrCreate(const QString &key, Role::DataType type)
{
    QStringHash<Role *>::Node *node = roleHash.findNode(key);
    if (node) {
        const Role &r = *node->value;
        if (type != r.type)
            qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type));
        return r;
    }

    return createRole(key, type);
}

const ListLayout::Role &ListLayout::getRoleOrCreate(QV4::String *key, Role::DataType type)
{
    QStringHash<Role *>::Node *node = roleHash.findNode(key);
    if (node) {
        const Role &r = *node->value;
        if (type != r.type)
            qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(r.name).arg(roleTypeName(type)).arg(roleTypeName(r.type));
        return r;
    }

    QString qkey = key->toQString();

    return createRole(qkey, type);
}

const ListLayout::Role &ListLayout::createRole(const QString &key, ListLayout::Role::DataType type)
{
    const int dataSizes[] = {
        sizeof(StringOrTranslation),
        sizeof(double),
        sizeof(bool),
        sizeof(ListModel *),
        sizeof(QV4::PersistentValue),
        sizeof(QVariantMap),
        sizeof(QDateTime),
        sizeof(QUrl),
        sizeof(QJSValue)
    };
    const int dataAlignments[] = {
        alignof(StringOrTranslation),
        alignof(double),
        alignof(bool),
        alignof(ListModel *),
        alignof(QV4::PersistentValue),
        alignof(QVariantMap),
        alignof(QDateTime),
        alignof(QUrl),
        alignof(QJSValue)
    };

    Role *r = new Role;
    r->name = key;
    r->type = type;

    if (type == Role::List) {
        r->subLayout = new ListLayout;
    } else {
        r->subLayout = nullptr;
    }

    int dataSize = dataSizes[type];
    int dataAlignment = dataAlignments[type];

    int dataOffset = (currentBlockOffset + dataAlignment-1) & ~(dataAlignment-1);
    if (dataOffset + dataSize > ListElement::BLOCK_SIZE) {
        r->blockIndex = ++currentBlock;
        r->blockOffset = 0;
        currentBlockOffset = dataSize;
    } else {
        r->blockIndex = currentBlock;
        r->blockOffset = dataOffset;
        currentBlockOffset = dataOffset + dataSize;
    }

    int roleIndex = roles.size();
    r->index = roleIndex;

    roles.append(r);
    roleHash.insert(key, r);

    return *r;
}

ListLayout::ListLayout(const ListLayout *other) : currentBlock(0), currentBlockOffset(0)
{
    const int otherRolesCount = other->roles.size();
    roles.reserve(otherRolesCount);
    for (int i=0 ; i < otherRolesCount; ++i) {
        Role *role = new Role(other->roles[i]);
        roles.append(role);
        roleHash.insert(role->name, role);
    }
    currentBlockOffset = other->currentBlockOffset;
    currentBlock = other->currentBlock;
}

ListLayout::~ListLayout()
{
    qDeleteAll(roles);
}

void ListLayout::sync(ListLayout *src, ListLayout *target)
{
    int roleOffset = target->roles.size();
    int newRoleCount = src->roles.size() - roleOffset;

    for (int i=0 ; i < newRoleCount ; ++i) {
        Role *role = new Role(src->roles[roleOffset + i]);
        target->roles.append(role);
        target->roleHash.insert(role->name, role);
    }

    target->currentBlockOffset = src->currentBlockOffset;
    target->currentBlock = src->currentBlock;
}

ListLayout::Role::Role(const Role *other)
{
    name = other->name;
    type = other->type;
    blockIndex = other->blockIndex;
    blockOffset = other->blockOffset;
    index = other->index;
    if (other->subLayout)
        subLayout = new ListLayout(other->subLayout);
    else
        subLayout = nullptr;
}

ListLayout::Role::~Role()
{
    delete subLayout;
}

const ListLayout::Role *ListLayout::getRoleOrCreate(const QString &key, const QVariant &data)
{
    Role::DataType type;

    switch (data.userType()) {
        case QMetaType::Double:      type = Role::Number;      break;
        case QMetaType::Int:         type = Role::Number;      break;
        case QMetaType::Bool:        type = Role::Bool;        break;
        case QMetaType::QString:     type = Role::String;      break;
        case QMetaType::QVariantMap: type = Role::VariantMap;  break;
        case QMetaType::QDateTime:   type = Role::DateTime;    break;
        case QMetaType::QUrl:        type = Role::Url;         break;
        default:    {
            if (data.userType() == qMetaTypeId<QJSValue>() &&
                data.value<QJSValue>().isCallable()) {
                type = Role::Function;
                break;
            } else if (data.userType() == qMetaTypeId<const QV4::CompiledData::Binding*>()
                       && data.value<const QV4::CompiledData::Binding*>()->isTranslationBinding()) {
                type = Role::String;
                break;
            } else if (data.userType() >= QMetaType::User) {
                type = Role::List;
                break;
            } else {
                type = Role::Invalid;
                break;
            }
        }
    }

    if (type == Role::Invalid) {
        qmlWarning(nullptr) << "Can't create role for unsupported data type";
        return nullptr;
    }

    return &getRoleOrCreate(key, type);
}

const ListLayout::Role *ListLayout::getExistingRole(const QString &key) const
{
    Role *r = nullptr;
    QStringHash<Role *>::Node *node = roleHash.findNode(key);
    if (node)
        r = node->value;
    return r;
}

const ListLayout::Role *ListLayout::getExistingRole(QV4::String *key) const
{
    Role *r = nullptr;
    QStringHash<Role *>::Node *node = roleHash.findNode(key);
    if (node)
        r = node->value;
    return r;
}

StringOrTranslation::~StringOrTranslation()
{
    clear();
}

void StringOrTranslation::setString(const QString &s)
{
    clear();
    if (s.isEmpty())
        return;
    QString mutableString(s);
    QString::DataPointer dataPointer = mutableString.data_ptr();
    arrayData = dataPointer->d_ptr();
    stringData = dataPointer->data();
    stringSize = mutableString.size();
    if (arrayData)
        arrayData->ref();
}

void StringOrTranslation::setTranslation(const QV4::CompiledData::Binding *binding)
{
    clear();
    this->binding = binding;
}

QString StringOrTranslation::toString(const QQmlListModel *owner) const
{
    if (stringSize) {
        if (arrayData)
            arrayData->ref();
        return QString(QStringPrivate(arrayData, stringData, stringSize));
    }
    if (!owner)
        return QString();
    return owner->m_compilationUnit->bindingValueAsString(binding);
}

QString StringOrTranslation::asString() const
{
    if (!arrayData)
        return QString();
    arrayData->ref();
    return QString(QStringPrivate(arrayData, stringData, stringSize));
}

void StringOrTranslation::clear()
{
    if (arrayData && !arrayData->deref())
        QTypedArrayData<ushort>::deallocate(arrayData);
    arrayData = nullptr;
    stringData = nullptr;
    stringSize = 0;
    binding = nullptr;
}

QObject *ListModel::getOrCreateModelObject(QQmlListModel *model, int elementIndex)
{
    ListElement *e = elements[elementIndex];
    if (e->m_objectCache == nullptr) {
        void *memory = operator new(sizeof(QObject) + sizeof(QQmlData));
        void *ddataMemory = ((char *)memory) + sizeof(QObject);
        e->m_objectCache = new (memory) QObject;

        const QAbstractDeclarativeData *old = std::exchange(
            QObjectPrivate::get(e->m_objectCache)->declarativeData,
            new (ddataMemory) QQmlData(QQmlData::DoesNotOwnMemory));
        Q_ASSERT(!old); // QObject should really not manipulate QQmlData

        (void)new ModelNodeMetaObject(e->m_objectCache, model, elementIndex);
    }
    return e->m_objectCache;
}

bool ListModel::sync(ListModel *src, ListModel *target)
{
    // Sanity check

    bool hasChanges = false;

    // Build hash of elements <-> uid for each of the lists
    QHash<int, ElementSync> elementHash;
    for (int i = 0; i < target->elements.count(); ++i) {
        ListElement *e = target->elements.at(i);
        int uid = e->getUid();
        ElementSync sync;
        sync.target = e;
        sync.targetIndex = i;
        elementHash.insert(uid, sync);
    }
    for (int i = 0; i < src->elements.count(); ++i) {
        ListElement *e = src->elements.at(i);
        int uid = e->getUid();

        QHash<int, ElementSync>::iterator it = elementHash.find(uid);
        if (it == elementHash.end()) {
            ElementSync sync;
            sync.src = e;
            sync.srcIndex = i;
            elementHash.insert(uid, sync);
        } else {
            ElementSync &sync = it.value();
            sync.src = e;
            sync.srcIndex = i;
        }
    }

    QQmlListModel *targetModel = target->m_modelCache;

    // Get list of elements that are in the target but no longer in the source. These get deleted first.
    int rowsRemoved = 0;
    for (int i = 0 ; i < target->elements.count() ; ++i) {
        ListElement *element = target->elements.at(i);
        ElementSync &s = elementHash.find(element->getUid()).value();
        Q_ASSERT(s.targetIndex >= 0);
        // need to update the targetIndex, to keep it correct after removals
        s.targetIndex -= rowsRemoved;
        if (s.src == nullptr) {
            Q_ASSERT(s.targetIndex == i);
            hasChanges = true;
            if (targetModel)
                targetModel->beginRemoveRows(QModelIndex(), i, i);
            s.target->destroy(target->m_layout);
            target->elements.removeOne(s.target);
            delete s.target;
            if (targetModel)
                targetModel->endRemoveRows();
            ++rowsRemoved;
            --i;
            continue;
        }
    }

    // Sync the layouts
    ListLayout::sync(src->m_layout, target->m_layout);

    // Clear the target list, and append in correct order from the source
    target->elements.clear();
    for (int i = 0; i < src->elements.count(); ++i) {
        ListElement *srcElement = src->elements.at(i);
        ElementSync &s = elementHash.find(srcElement->getUid()).value();
        Q_ASSERT(s.srcIndex >= 0);
        ListElement *targetElement = s.target;
        if (targetElement == nullptr) {
            targetElement = new ListElement(srcElement->getUid());
        }
        s.changedRoles = ListElement::sync(srcElement, src->m_layout, targetElement, target->m_layout);
        target->elements.append(targetElement);
    }

    target->updateCacheIndices();

    // Update values stored in target meta objects
    for (int i=0 ; i < target->elements.count() ; ++i) {
        ListElement *e = target->elements[i];
        if (ModelNodeMetaObject *mo = e->objectCache())
            mo->updateValues();
    }

    // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts,
    // so the model indices can't be out of bounds
    //
    // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent
    // model indices are updated correctly
    int rowsInserted = 0;
    const int targetElementCount = target->elements.count();
    for (int i = 0 ; i < targetElementCount ; ++i) {
        ListElement *element = target->elements.at(i);
        ElementSync &s = elementHash.find(element->getUid()).value();
        Q_ASSERT(s.srcIndex >= 0);
        s.srcIndex += rowsInserted;
        if (s.srcIndex != s.targetIndex) {
            if (targetModel) {
                if (s.targetIndex == -1) {
                    targetModel->beginInsertRows(QModelIndex(), i, i);
                    targetModel->endInsertRows();
                    ++rowsInserted;
                } else {
                    bool validMove = targetModel->beginMoveRows(QModelIndex(), s.targetIndex, s.targetIndex, QModelIndex(), i);
                    Q_ASSERT(validMove);
                    targetModel->endMoveRows();
                    // fixup target indices of elements that still need to move
                    for (int j=i+1; j < targetElementCount; ++j) {
                        ListElement *eToFix = target->elements.at(j);
                        ElementSync &sToFix = elementHash.find(eToFix->getUid()).value();
                        if (i < s.targetIndex) {
                            // element was moved down
                            if (sToFix.targetIndex > s.targetIndex || sToFix.targetIndex < i)
                                continue; // unaffected by reordering
                            else
                                sToFix.targetIndex += 1;
                        } else {
                            // element was moved up
                            if (sToFix.targetIndex < s.targetIndex || sToFix.targetIndex > i)
                                continue; // unaffected by reordering
                            else
                                sToFix.targetIndex -= 1;
                        }
                    }
                }
            }
            hasChanges = true;
        }
        if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) {
            QModelIndex idx = targetModel->createIndex(i, 0);
            if (targetModel)
                targetModel->dataChanged(idx, idx, s.changedRoles);
            hasChanges = true;
        }
    }
    return hasChanges;
}

ListModel::ListModel(ListLayout *layout, QQmlListModel *modelCache) : m_layout(layout), m_modelCache(modelCache)
{
}

void ListModel::destroy()
{
    for (const auto &destroyer : remove(0, elements.count()))
        destroyer();

    m_layout = nullptr;
    if (m_modelCache && m_modelCache->m_primary == false)
        delete m_modelCache;
    m_modelCache = nullptr;
}

int ListModel::appendElement()
{
    int elementIndex = elements.count();
    newElement(elementIndex);
    return elementIndex;
}

void ListModel::insertElement(int index)
{
    newElement(index);
    updateCacheIndices(index);
}

void ListModel::move(int from, int to, int n)
{
    if (from > to) {
        // Only move forwards - flip if backwards moving
        int tfrom = from;
        int tto = to;
        from = tto;
        to = tto+n;
        n = tfrom-tto;
    }

    QPODVector<ListElement *, 4> store;
    for (int i=0 ; i < (to-from) ; ++i)
        store.append(elements[from+n+i]);
    for (int i=0 ; i < n ; ++i)
        store.append(elements[from+i]);
    for (int i=0 ; i < store.count() ; ++i)
        elements[from+i] = store[i];

    updateCacheIndices(from, to + n);
}

void ListModel::newElement(int index)
{
    ListElement *e = new ListElement;
    elements.insert(index, e);
}

void ListModel::updateCacheIndices(int start, int end)
{
    int count = elements.count();

    if (end < 0 || end > count)
        end = count;

    for (int i = start; i < end; ++i) {
        ListElement *e = elements.at(i);
        if (ModelNodeMetaObject *mo = e->objectCache())
            mo->m_elementIndex = i;
    }
}

QVariant ListModel::getProperty(int elementIndex, int roleIndex, const QQmlListModel *owner, QV4::ExecutionEngine *eng)
{
    if (roleIndex >= m_layout->roleCount())
        return QVariant();
    ListElement *e = elements[elementIndex];
    const ListLayout::Role &r = m_layout->getExistingRole(roleIndex);
    return e->getProperty(r, owner, eng);
}

ListModel *ListModel::getListProperty(int elementIndex, const ListLayout::Role &role)
{
    ListElement *e = elements[elementIndex];
    return e->getListProperty(role);
}

void ListModel::updateTranslations()
{
    for (int index = 0; index != elements.count(); ++index) {
        ListElement *e = elements[index];
        if (ModelNodeMetaObject *cache = e->objectCache()) {
            // TODO: more fine grained tracking?
            cache->updateValues();
        }
    }
}

void ListModel::set(int elementIndex, QV4::Object *object, QVector<int> *roles)
{
    ListElement *e = elements[elementIndex];

    QV4::ExecutionEngine *v4 = object->engine();
    QV4::Scope scope(v4);
    QV4::ScopedObject o(scope);

    QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
    QV4::ScopedString propertyName(scope);
    QV4::ScopedValue propertyValue(scope);
    while (1) {
        propertyName = it.nextPropertyNameAsString(propertyValue);
        if (!propertyName)
            break;

        // Check if this key exists yet
        int roleIndex = -1;

        // Add the value now
        if (const QV4::String *s = propertyValue->as<QV4::String>()) {
            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
            roleIndex = e->setStringProperty(r, s->toQString());
        } else if (propertyValue->isNumber()) {
            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
            roleIndex = e->setDoubleProperty(r, propertyValue->asDouble());
        } else if (QV4::ArrayObject *a = propertyValue->as<QV4::ArrayObject>()) {
            roleIndex = setArrayLike(&o, propertyName, e, a);
        } else if (QV4::Sequence *s = propertyValue->as<QV4::Sequence>()) {
            roleIndex = setArrayLike(&o, propertyName, e, s);
        } else if (QV4::QmlListWrapper *l = propertyValue->as<QV4::QmlListWrapper>()) {
            roleIndex = setArrayLike(&o, propertyName, e, l);
        } else if (propertyValue->isBoolean()) {
            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
            roleIndex = e->setBoolProperty(r, propertyValue->booleanValue());
        } else if (QV4::DateObject *dd = propertyValue->as<QV4::DateObject>()) {
            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime);
            QDateTime dt = dd->toQDateTime();
            roleIndex = e->setDateTimeProperty(r, dt);
        } else if (QV4::UrlObject *url = propertyValue->as<QV4::UrlObject>()) {
            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Url);
            QUrl qurl = QUrl(url->href());
            roleIndex = e->setUrlProperty(r, qurl);
        } else if (QV4::FunctionObject *f = propertyValue->as<QV4::FunctionObject>()) {
            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Function);
            QV4::ScopedFunctionObject func(scope, f);
            QJSValue jsv;
            QJSValuePrivate::setValue(&jsv, func);
            roleIndex = e->setFunctionProperty(r, jsv);
        } else if (QV4::Object *o = propertyValue->as<QV4::Object>()) {
            if (QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>()) {
                const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
                if (role.type == ListLayout::Role::QObject)
                    roleIndex = e->setQObjectProperty(role, wrapper);
            } else if (QVariant maybeUrl = QV4::ExecutionEngine::toVariant(
                           // gc will hold on to o via the scoped propertyValue; fromReturnedValue is safe
                           QV4::Value::fromReturnedValue(o->asReturnedValue()),
                           QMetaType::fromType<QUrl>(), true);
                       maybeUrl.metaType() == QMetaType::fromType<QUrl>()) {
                const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Url);
                QUrl qurl = maybeUrl.toUrl();
                roleIndex = e->setUrlProperty(r, qurl);
            } else {
                const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
                if (role.type == ListLayout::Role::VariantMap) {
                    QV4::ScopedObject obj(scope, o);
                    roleIndex = e->setVariantMapProperty(role, obj);
                }
            }
        } else if (propertyValue->isNullOrUndefined()) {
            const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
            if (r)
                e->clearProperty(*r);
        }

        if (roleIndex != -1)
            roles->append(roleIndex);
    }

    if (ModelNodeMetaObject *mo = e->objectCache())
        mo->updateValues(*roles);
}

void ListModel::set(int elementIndex, QV4::Object *object, ListModel::SetElement reason)
{
    if (!object)
        return;

    ListElement *e = elements[elementIndex];

    QV4::ExecutionEngine *v4 = object->engine();
    QV4::Scope scope(v4);

    QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly);
    QV4::ScopedString propertyName(scope);
    QV4::ScopedValue propertyValue(scope);
    QV4::ScopedObject o(scope);
    while (1) {
        propertyName = it.nextPropertyNameAsString(propertyValue);
        if (!propertyName)
            break;

        // Add the value now
        if (QV4::String *s = propertyValue->stringValue()) {
            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::String);
            if (r.type == ListLayout::Role::String)
                e->setStringPropertyFast(r, s->toQString());
        } else if (propertyValue->isNumber()) {
            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Number);
            if (r.type == ListLayout::Role::Number) {
                e->setDoublePropertyFast(r, propertyValue->asDouble());
            }
        } else if (QV4::ArrayObject *a = propertyValue->as<QV4::ArrayObject>()) {
            setArrayLikeFast(&o, propertyName, e, a);
        } else if (QV4::Sequence *s = propertyValue->as<QV4::Sequence>()) {
            setArrayLikeFast(&o, propertyName, e, s);
        } else if (QV4::QmlListWrapper *l = propertyValue->as<QV4::QmlListWrapper>()) {
            setArrayLikeFast(&o, propertyName, e, l);
        } else if (propertyValue->isBoolean()) {
            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Bool);
            if (r.type == ListLayout::Role::Bool) {
                e->setBoolPropertyFast(r, propertyValue->booleanValue());
            }
        } else if (QV4::DateObject *date = propertyValue->as<QV4::DateObject>()) {
            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::DateTime);
            if (r.type == ListLayout::Role::DateTime) {
                QDateTime dt = date->toQDateTime();
                e->setDateTimePropertyFast(r, dt);
            }
        } else if (QV4::UrlObject *url = propertyValue->as<QV4::UrlObject>()){
            const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Url);
            if (r.type == ListLayout::Role::Url) {
                QUrl qurl = QUrl(url->href()); // does what the private UrlObject->toQUrl would do
                e->setUrlPropertyFast(r, qurl);
            }
        } else if (QV4::Object *o = propertyValue->as<QV4::Object>()) {
            if (QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>()) {
                const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::QObject);
                if (r.type == ListLayout::Role::QObject)
                    e->setQObjectPropertyFast(r, wrapper);
            } else {
                QVariant maybeUrl = QV4::ExecutionEngine::toVariant(
                            // gc will hold on to o via the scoped propertyValue; fromReturnedValue is safe
                            QV4::Value::fromReturnedValue(o->asReturnedValue()),
                            QMetaType::fromType<QUrl>(), true);
                if (maybeUrl.metaType() == QMetaType::fromType<QUrl>()) {
                    const QUrl qurl = maybeUrl.toUrl();
                    const ListLayout::Role &r = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::Url);
                    if (r.type == ListLayout::Role::Url)
                        e->setUrlPropertyFast(r, qurl);
                } else {
                    const ListLayout::Role &role = m_layout->getRoleOrCreate(propertyName, ListLayout::Role::VariantMap);
                    if (role.type == ListLayout::Role::VariantMap)
                        e->setVariantMapFast(role, o);
                }
            }
        } else if (propertyValue->isNullOrUndefined()) {
            if (reason == SetElement::WasJustInserted) {
                QQmlError err;
                auto memberName = propertyName->toString(v4)->toQString();
                err.setDescription(QString::fromLatin1("%1 is %2. Adding an object with a %2 member does not create a role for it.").arg(memberName, propertyValue->isNull() ? QLatin1String("null") : QLatin1String("undefined")));
                qmlWarning(nullptr, err);
            } else {
                const ListLayout::Role *r = m_layout->getExistingRole(propertyName);
                if (r)
                    e->clearProperty(*r);
            }
        }
    }
}

QVector<std::function<void()>> ListModel::remove(int index, int count)
{
    QVector<std::function<void()>> toDestroy;
    auto layout = m_layout;
    for (int i=0 ; i < count ; ++i) {
        auto element = elements[index+i];
        toDestroy.append([element, layout](){
            element->destroy(layout);
            delete element;
        });
    }
    elements.remove(index, count);
    updateCacheIndices(index);
    return toDestroy;
}

void ListModel::insert(int elementIndex, QV4::Object *object)
{
    insertElement(elementIndex);
    set(elementIndex, object, SetElement::WasJustInserted);
}

int ListModel::append(QV4::Object *object)
{
    int elementIndex = appendElement();
    set(elementIndex, object, SetElement::WasJustInserted);
    return elementIndex;
}

int ListModel::setOrCreateProperty(int elementIndex, const QString &key, const QVariant &data)
{
    int roleIndex = -1;

    if (elementIndex >= 0 && elementIndex < elements.count()) {
        ListElement *e = elements[elementIndex];

        const ListLayout::Role *r = m_layout->getRoleOrCreate(key, data);
        if (r) {
            roleIndex = e->setVariantProperty(*r, data);

            ModelNodeMetaObject *cache = e->objectCache();

            if (roleIndex != -1 && cache)
                cache->updateValues(QVector<int>(1, roleIndex));
        }
    }

    return roleIndex;
}

int ListModel::setExistingProperty(int elementIndex, const QString &key, const QV4::Value &data, QV4::ExecutionEngine *eng)
{
    int roleIndex = -1;

    if (elementIndex >= 0 && elementIndex < elements.count()) {
        ListElement *e = elements[elementIndex];
        const ListLayout::Role *r = m_layout->getExistingRole(key);
        if (r)
            roleIndex = e->setJsProperty(*r, data, eng);
    }

    return roleIndex;
}

inline char *ListElement::getPropertyMemory(const ListLayout::Role &role)
{
    ListElement *e = this;
    int blockIndex = 0;
    while (blockIndex < role.blockIndex) {
        if (e->next == nullptr) {
            e->next = new ListElement;
            e->next->uid = uid;
        }
        e = e->next;
        ++blockIndex;
    }

    char *mem = &e->data[role.blockOffset];
    return mem;
}

ModelNodeMetaObject *ListElement::objectCache()
{
    if (!m_objectCache)
        return nullptr;
    return ModelNodeMetaObject::get(m_objectCache);
}

StringOrTranslation *ListElement::getStringProperty(const ListLayout::Role &role)
{
    char *mem = getPropertyMemory(role);
    StringOrTranslation *s = reinterpret_cast<StringOrTranslation *>(mem);
    return s;
}

QV4::QObjectWrapper *ListElement::getQObjectProperty(const ListLayout::Role &role)
{
    char *mem = getPropertyMemory(role);
    QV4::PersistentValue *g = reinterpret_cast<QV4::PersistentValue *>(mem);
    return g->as<QV4::QObjectWrapper>();
}

QVariantMap *ListElement::getVariantMapProperty(const ListLayout::Role &role)
{
    QVariantMap *map = nullptr;

    char *mem = getPropertyMemory(role);
    if (isMemoryUsed<QVariantMap>(mem))
        map = reinterpret_cast<QVariantMap *>(mem);

    return map;
}

QDateTime *ListElement::getDateTimeProperty(const ListLayout::Role &role)
{
    QDateTime *dt = nullptr;

    char *mem = getPropertyMemory(role);
    if (isMemoryUsed<QDateTime>(mem))
        dt = reinterpret_cast<QDateTime *>(mem);

    return dt;
}

QUrl *ListElement::getUrlProperty(const ListLayout::Role &role)
{
    QUrl *url = nullptr;

    char *mem = getPropertyMemory(role);
    if (isMemoryUsed<QUrl>(mem))
        url = reinterpret_cast<QUrl *>(mem);

    return url;
}

QJSValue *ListElement::getFunctionProperty(const ListLayout::Role &role)
{
    QJSValue *f = nullptr;

    char *mem = getPropertyMemory(role);
    if (isMemoryUsed<QJSValue>(mem))
        f = reinterpret_cast<QJSValue *>(mem);

    return f;
}

QV4::PersistentValue *
ListElement::getGuardProperty(const ListLayout::Role &role)
{
    char *mem = getPropertyMemory(role);

    bool existingGuard = false;
    for (size_t i = 0; i < sizeof(QV4::PersistentValue);
         ++i) {
        if (mem[i] != 0) {
            existingGuard = true;
            break;
        }
    }

    QV4::PersistentValue *g = nullptr;

    if (existingGuard)
        g = reinterpret_cast<QV4::PersistentValue *>(mem);

    return g;
}

ListModel *ListElement::getListProperty(const ListLayout::Role &role)
{
    char *mem = getPropertyMemory(role);
    ListModel **value = reinterpret_cast<ListModel **>(mem);
    return *value;
}

QVariant ListElement::getProperty(const ListLayout::Role &role, const QQmlListModel *owner, QV4::ExecutionEngine *eng)
{
    char *mem = getPropertyMemory(role);

    QVariant data;

    switch (role.type) {
        case ListLayout::Role::Number:
            {
                double *value = reinterpret_cast<double *>(mem);
                data = *value;
            }
            break;
        case ListLayout::Role::String:
            {
                StringOrTranslation *value = reinterpret_cast<StringOrTranslation *>(mem);
                if (value->isSet())
                    data = value->toString(owner);
                else
                    data = QString();
            }
            break;
        case ListLayout::Role::Bool:
            {
                bool *value = reinterpret_cast<bool *>(mem);
                data = *value;
            }
            break;
        case ListLayout::Role::List:
            {
                ListModel **value = reinterpret_cast<ListModel **>(mem);
                ListModel *model = *value;

                if (model) {
                    if (model->m_modelCache == nullptr) {
                        model->m_modelCache = new QQmlListModel(owner, model, eng);
                        QQmlEngine::setContextForObject(model->m_modelCache, QQmlEngine::contextForObject(owner));
                    }

                    QObject *object = model->m_modelCache;
                    data = QVariant::fromValue(object);
                }
            }
            break;
        case ListLayout::Role::QObject:
            {
                QV4::PersistentValue *guard = reinterpret_cast<QV4::PersistentValue *>(mem);
                data = QVariant::fromValue(guard->as<QV4::QObjectWrapper>()->object());
            }
            break;
        case ListLayout::Role::VariantMap:
            {
                if (isMemoryUsed<QVariantMap>(mem)) {
                    QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
                    data = *map;
                }
            }
            break;
        case ListLayout::Role::DateTime:
            {
                if (isMemoryUsed<QDateTime>(mem)) {
                    QDateTime *dt = reinterpret_cast<QDateTime *>(mem);
                    data = *dt;
                }
            }
            break;
        case ListLayout::Role::Url:
            {
                if (isMemoryUsed<QUrl>(mem)) {
                    QUrl *url = reinterpret_cast<QUrl *>(mem);
                    data = *url;
                }
            }
            break;
        case ListLayout::Role::Function:
            {
                if (isMemoryUsed<QJSValue>(mem)) {
                    QJSValue *func = reinterpret_cast<QJSValue *>(mem);
                    data = QVariant::fromValue(*func);
                }
            }
            break;
        default:
            break;
    }

    return data;
}

int ListElement::setStringProperty(const ListLayout::Role &role, const QString &s)
{
    int roleIndex = -1;

    if (role.type == ListLayout::Role::String) {
        char *mem = getPropertyMemory(role);
        StringOrTranslation *c = reinterpret_cast<StringOrTranslation *>(mem);
        bool changed;
        if (!c->isSet() || c->isTranslation())
            changed = true;
        else
            changed = c->asString().compare(s) != 0;
        c->setString(s);
        if (changed)
            roleIndex = role.index;
    }

    return roleIndex;
}

int ListElement::setDoubleProperty(const ListLayout::Role &role, double d)
{
    int roleIndex = -1;

    if (role.type == ListLayout::Role::Number) {
        char *mem = getPropertyMemory(role);
        double *value = reinterpret_cast<double *>(mem);
        bool changed = *value != d;
        *value = d;
        if (changed)
            roleIndex = role.index;
    }

    return roleIndex;
}

int ListElement::setBoolProperty(const ListLayout::Role &role, bool b)
{
    int roleIndex = -1;

    if (role.type == ListLayout::Role::Bool) {
        char *mem = getPropertyMemory(role);
        bool *value = reinterpret_cast<bool *>(mem);
        bool changed = *value != b;
        *value = b;
        if (changed)
            roleIndex = role.index;
    }

    return roleIndex;
}

int ListElement::setListProperty(const ListLayout::Role &role, ListModel *m)
{
    int roleIndex = -1;

    if (role.type == ListLayout::Role::List) {
        char *mem = getPropertyMemory(role);
        ListModel **value = reinterpret_cast<ListModel **>(mem);
        if (*value && *value != m) {
            (*value)->destroy();
            delete *value;
        }
        *value = m;
        roleIndex = role.index;
    }

    return roleIndex;
}

int ListElement::setQObjectProperty(const ListLayout::Role &role, QV4::QObjectWrapper *o)
{
    int roleIndex = -1;

    if (role.type == ListLayout::Role::QObject) {
        char *mem = getPropertyMemory(role);
        if (isMemoryUsed<QVariantMap>(mem))
            reinterpret_cast<QV4::PersistentValue *>(mem)->set(o->engine(), *o);
        else
            new (mem) QV4::PersistentValue(o->engine(), o);
        roleIndex = role.index;
    }

    return roleIndex;
}

int ListElement::setVariantMapProperty(const ListLayout::Role &role, QV4::Object *o)
{
    int roleIndex = -1;

    if (role.type == ListLayout::Role::VariantMap) {
        char *mem = getPropertyMemory(role);
        if (isMemoryUsed<QVariantMap>(mem)) {
            QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
            map->~QMap();
        }
        new (mem) QVariantMap(o->engine()->variantMapFromJS(o));
        roleIndex = role.index;
    }

    return roleIndex;
}

int ListElement::setVariantMapProperty(const ListLayout::Role &role, QVariantMap *m)
{
    int roleIndex = -1;

    if (role.type == ListLayout::Role::VariantMap) {
        char *mem = getPropertyMemory(role);
        if (isMemoryUsed<QVariantMap>(mem)) {
            QVariantMap *map = reinterpret_cast<QVariantMap *>(mem);
            if (m && map->isSharedWith(*m))
                return roleIndex;
            map->~QMap();
        } else if (!m) {
            return roleIndex;
        }
        if (m)
            new (mem) QVariantMap(*m);
        else
            new (mem) QVariantMap;
        roleIndex = role.index;
    }

    return roleIndex;
}

int ListElement::setDateTimeProperty(const ListLayout::Role &role, const QDateTime &dt)
{
    int roleIndex = -1;

    if (role.type == ListLayout::Role::DateTime) {
        char *mem = getPropertyMemory(role);
        if (isMemoryUsed<QDateTime>(mem)) {
            QDateTime *dt = reinterpret_cast<QDateTime *>(mem);
            dt->~QDateTime();
        }
        new (mem) QDateTime(dt);
        roleIndex = role.index;
    }

    return roleIndex;
}

int ListElement::setUrlProperty(const ListLayout::Role &role, const QUrl &url)
{
    int roleIndex = -1;

    if (role.type == ListLayout::Role::Url) {
        char *mem = getPropertyMemory(role);
        if (isMemoryUsed<QUrl>(mem)) {
            QUrl *qurl = reinterpret_cast<QUrl *>(mem);
            qurl->~QUrl();
        }
        new (mem) QUrl(url);
        roleIndex = role.index;
    }

    return roleIndex;
}

int ListElement::setFunctionProperty(const ListLayout::Role &role, const QJSValue &f)
{
    int roleIndex = -1;

    if (role.type == ListLayout::Role::Function) {
        char *mem = getPropertyMemory(role);
        if (isMemoryUsed<QJSValue>(mem)) {
            QJSValue *f = reinterpret_cast<QJSValue *>(mem);
            f->~QJSValue();
        }
        new (mem) QJSValue(f);
        roleIndex = role.index;
    }

    return roleIndex;
}

int ListElement::setTranslationProperty(const ListLayout::Role &role, const QV4::CompiledData::Binding *b)
{
    int roleIndex = -1;

    if (role.type == ListLayout::Role::String) {
        char *mem = getPropertyMemory(role);
        StringOrTranslation *s = reinterpret_cast<StringOrTranslation *>(mem);
        s->setTranslation(b);
        roleIndex = role.index;
    }

    return roleIndex;
}


void ListElement::setStringPropertyFast(const ListLayout::Role &role, const QString &s)
{
    char *mem = getPropertyMemory(role);
    reinterpret_cast<StringOrTranslation *>(mem)->setString(s);
}

void ListElement::setDoublePropertyFast(const ListLayout::Role &role, double d)
{
    char *mem = getPropertyMemory(role);
    double *value = new (mem) double;
    *value = d;
}

void ListElement::setBoolPropertyFast(const ListLayout::Role &role, bool b)
{
    char *mem = getPropertyMemory(role);
    bool *value = new (mem) bool;
    *value = b;
}

void ListElement::setQObjectPropertyFast(const ListLayout::Role &role, QV4::QObjectWrapper *o)
{
    char *mem = getPropertyMemory(role);
    new (mem) QV4::PersistentValue(o->engine(), o);
}

void ListElement::setListPropertyFast(const ListLayout::Role &role, ListModel *m)
{
    char *mem = getPropertyMemory(role);
    ListModel **value = new (mem) ListModel *;
    *value = m;
}

void ListElement::setVariantMapFast(const ListLayout::Role &role, QV4::Object *o)
{
    char *mem = getPropertyMemory(role);
    QVariantMap *map = new (mem) QVariantMap;
    *map = o->engine()->variantMapFromJS(o);
}

void ListElement::setDateTimePropertyFast(const ListLayout::Role &role, const QDateTime &dt)
{
    char *mem = getPropertyMemory(role);
    new (mem) QDateTime(dt);
}

void ListElement::setUrlPropertyFast(const ListLayout::Role &role, const QUrl &url)
{
    char *mem = getPropertyMemory(role);
    new (mem) QUrl(url);
}

void ListElement::setFunctionPropertyFast(const ListLayout::Role &role, const QJSValue &f)
{
    char *mem = getPropertyMemory(role);
    new (mem) QJSValue(f);
}

void ListElement::clearProperty(const ListLayout::Role &role)
{
    switch (role.type) {
    case ListLayout::Role::String:
        setStringProperty(role, QString());
        break;
    case ListLayout::Role::Number:
        setDoubleProperty(role, 0.0);
        break;
    case ListLayout::Role::Bool:
        setBoolProperty(role, false);
        break;
    case ListLayout::Role::List:
        setListProperty(role, nullptr);
        break;
    case ListLayout::Role::QObject:
        setQObjectProperty(role, nullptr);
        break;
    case ListLayout::Role::DateTime:
        setDateTimeProperty(role, QDateTime());
        break;
    case ListLayout::Role::Url:
        setUrlProperty(role, QUrl());
        break;
    case ListLayout::Role::VariantMap:
        setVariantMapProperty(role, (QVariantMap *)nullptr);
        break;
    case ListLayout::Role::Function:
        setFunctionProperty(role, QJSValue());
        break;
    default:
        break;
    }
}

ListElement::ListElement()
{
    m_objectCache = nullptr;
    uid = uidCounter.fetchAndAddOrdered(1);
    next = nullptr;
    memset(data, 0, sizeof(data));
}

ListElement::ListElement(int existingUid)
{
    m_objectCache = nullptr;
    uid = existingUid;
    next = nullptr;
    memset(data, 0, sizeof(data));
}

ListElement::~ListElement()
{
    delete next;
}

QVector<int> ListElement::sync(ListElement *src, ListLayout *srcLayout, ListElement *target, ListLayout *targetLayout)
{
    QVector<int> changedRoles;
    for (int i=0 ; i < srcLayout->roleCount() ; ++i) {
        const ListLayout::Role &srcRole = srcLayout->getExistingRole(i);
        const ListLayout::Role &targetRole = targetLayout->getExistingRole(i);

        int roleIndex = -1;
        switch (srcRole.type) {
            case ListLayout::Role::List:
                {
                    ListModel *srcSubModel = src->getListProperty(srcRole);
                    ListModel *targetSubModel = target->getListProperty(targetRole);

                    if (srcSubModel) {
                        if (targetSubModel == nullptr) {
                            targetSubModel = new ListModel(targetRole.subLayout, nullptr);
                            target->setListPropertyFast(targetRole, targetSubModel);
                        }
                        if (ListModel::sync(srcSubModel, targetSubModel))
                            roleIndex = targetRole.index;
                    }
                }
                break;
            case ListLayout::Role::QObject:
                {
                    QV4::QObjectWrapper *object = src->getQObjectProperty(srcRole);
                    roleIndex = target->setQObjectProperty(targetRole, object);
                }
                break;
            case ListLayout::Role::String:
            case ListLayout::Role::Number:
            case ListLayout::Role::Bool:
            case ListLayout::Role::DateTime:
            case ListLayout::Role::Function:
                {
                    QVariant v = src->getProperty(srcRole, nullptr, nullptr);
                    roleIndex = target->setVariantProperty(targetRole, v);
                }
                break;
            case ListLayout::Role::VariantMap:
                {
                    QVariantMap *map = src->getVariantMapProperty(srcRole);
                    roleIndex = target->setVariantMapProperty(targetRole, map);
                }
                break;
            default:
                break;
        }
        if (roleIndex >= 0)
            changedRoles << roleIndex;
    }

    return changedRoles;
}

void ListElement::destroy(ListLayout *layout)
{
    if (layout) {
        for (int i=0 ; i < layout->roleCount() ; ++i) {
            const ListLayout::Role &r = layout->getExistingRole(i);

            switch (r.type) {
                case ListLayout::Role::String:
                    {
                        StringOrTranslation *string = getStringProperty(r);
                        if (string)
                            string->~StringOrTranslation();
                    }
                    break;
                case ListLayout::Role::List:
                    {
                        ListModel *model = getListProperty(r);
                        if (model) {
                            model->destroy();
                            delete model;
                        }
                    }
                    break;
                case ListLayout::Role::QObject:
                    {
                        if (QV4::PersistentValue *guard = getGuardProperty(r))
                            guard->~PersistentValue();
                    }
                    break;
                case ListLayout::Role::VariantMap:
                    {
                        QVariantMap *map = getVariantMapProperty(r);
                        if (map)
                            map->~QMap();
                    }
                    break;
                case ListLayout::Role::DateTime:
                    {
                        QDateTime *dt = getDateTimeProperty(r);
                        if (dt)
                            dt->~QDateTime();
                    }
                    break;
                case ListLayout::Role::Url:
                    {
                        QUrl *url = getUrlProperty(r);
                        if (url)
                            url->~QUrl();
                        break;
                    }
                case ListLayout::Role::Function:
                    {
                        QJSValue *f = getFunctionProperty(r);
                        if (f)
                            f->~QJSValue();
                    }
                    break;
                default:
                    // other types don't need explicit cleanup.
                    break;
            }
        }

        if (m_objectCache) {
            m_objectCache->~QObject();
            operator delete(m_objectCache);
        }
    }

    if (next)
        next->destroy(nullptr);
    uid = -1;
}

int ListElement::setVariantProperty(const ListLayout::Role &role, const QVariant &d)
{
    int roleIndex = -1;

    switch (role.type) {
        case ListLayout::Role::Number:
            roleIndex = setDoubleProperty(role, d.toDouble());
            break;
        case ListLayout::Role::String:
            if (d.userType() == qMetaTypeId<const QV4::CompiledData::Binding *>())
                roleIndex = setTranslationProperty(role, d.value<const QV4::CompiledData::Binding*>());
            else
                roleIndex = setStringProperty(role, d.toString());
            break;
        case ListLayout::Role::Bool:
            roleIndex = setBoolProperty(role, d.toBool());
            break;
        case ListLayout::Role::List:
            roleIndex = setListProperty(role, d.value<ListModel *>());
            break;
        case ListLayout::Role::VariantMap: {
                QVariantMap map = d.toMap();
                roleIndex = setVariantMapProperty(role, &map);
            }
            break;
        case ListLayout::Role::DateTime:
            roleIndex = setDateTimeProperty(role, d.toDateTime());
            break;
        case ListLayout::Role::Url:
            roleIndex = setUrlProperty(role, d.toUrl());
            break;
        case ListLayout::Role::Function:
            roleIndex = setFunctionProperty(role, d.value<QJSValue>());
            break;
        default:
            break;
    }

    return roleIndex;
}

int ListElement::setJsProperty(const ListLayout::Role &role, const QV4::Value &d, QV4::ExecutionEngine *eng)
{
    // Check if this key exists yet
    int roleIndex = -1;

    QV4::Scope scope(eng);

    // Add the value now
    if (d.isString()) {
        QString qstr = d.toQString();
        roleIndex = setStringProperty(role, qstr);
    } else if (d.isNumber()) {
        roleIndex = setDoubleProperty(role, d.asDouble());
    } else if (d.as<QV4::ArrayObject>()) {
        QV4::ScopedArrayObject a(scope, d);
        if (role.type == ListLayout::Role::List) {
            QV4::Scope scope(a->engine());
            QV4::ScopedObject o(scope);

            ListModel *subModel = new ListModel(role.subLayout, nullptr);
            int arrayLength = a->getLength();
            for (int j=0 ; j < arrayLength ; ++j) {
                o = a->get(j);
                subModel->append(o);
            }
            roleIndex = setListProperty(role, subModel);
        } else {
            qmlWarning(nullptr) << QStringLiteral("Can't assign to existing role '%1' of different type [%2 -> %3]").arg(role.name).arg(roleTypeName(role.type)).arg(roleTypeName(ListLayout::Role::List));
        }
    } else if (d.isBoolean()) {
        roleIndex = setBoolProperty(role, d.booleanValue());
    } else if (d.as<QV4::DateObject>()) {
        QV4::Scoped<QV4::DateObject> dd(scope, d);
        QDateTime dt = dd->toQDateTime();
        roleIndex = setDateTimeProperty(role, dt);
    } else if (d.as<QV4::UrlObject>()) {
        QV4::Scoped<QV4::UrlObject> url(scope, d);
        QUrl qurl = QUrl(url->href());
        roleIndex = setUrlProperty(role, qurl);
    } else if (d.as<QV4::FunctionObject>()) {
        QV4::ScopedFunctionObject f(scope, d);
        QJSValue jsv;
        QJSValuePrivate::setValue(&jsv, f);
        roleIndex = setFunctionProperty(role, jsv);
    } else if (d.isObject()) {
        QV4::ScopedObject o(scope, d);
        QV4::QObjectWrapper *wrapper = o->as<QV4::QObjectWrapper>();
        if (role.type == ListLayout::Role::QObject && wrapper) {
            roleIndex = setQObjectProperty(role, wrapper);
        } else if (role.type == ListLayout::Role::VariantMap) {
            roleIndex = setVariantMapProperty(role, o);
        } else if (role.type == ListLayout::Role::Url) {
            QVariant maybeUrl = QV4::ExecutionEngine::toVariant(
                        // gc will hold on to o via the scoped propertyValue; fromReturnedValue is safe
                        QV4::Value::fromReturnedValue(o.asReturnedValue()),
                        QMetaType::fromType<QUrl>(), true);
            if (maybeUrl.metaType() == QMetaType::fromType<QUrl>()) {
                roleIndex = setUrlProperty(role, maybeUrl.toUrl());
            }
        }
    } else if (d.isNullOrUndefined()) {
        clearProperty(role);
    }

    return roleIndex;
}

ModelNodeMetaObject::ModelNodeMetaObject(QObject *object, QQmlListModel *model, int elementIndex)
: QQmlOpenMetaObject(object), m_enabled(false), m_model(model), m_elementIndex(elementIndex), m_initialized(false)
{}

void ModelNodeMetaObject::initialize()
{
    const int roleCount = m_model->m_listModel->roleCount();
    QVector<QByteArray> properties;
    properties.reserve(roleCount);
    for (int i = 0 ; i < roleCount ; ++i) {
        const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
        QByteArray name = role.name.toUtf8();
        properties << name;
    }
    type()->createProperties(properties);
    updateValues();
    m_enabled = true;
}

ModelNodeMetaObject::~ModelNodeMetaObject()
{
}

#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0)
const QMetaObject *ModelNodeMetaObject::toDynamicMetaObject(QObject *object) const
#else
QMetaObject *ModelNodeMetaObject::toDynamicMetaObject(QObject *object)
#endif
{
    if (!m_initialized) {
        m_initialized = true;
        const_cast<ModelNodeMetaObject *>(this)->initialize();
    }
    return QQmlOpenMetaObject::toDynamicMetaObject(object);
}

ModelNodeMetaObject *ModelNodeMetaObject::get(QObject *obj)
{
    QObjectPrivate *op = QObjectPrivate::get(obj);
    return static_cast<ModelNodeMetaObject*>(op->metaObject);
}

void ModelNodeMetaObject::updateValues()
{
    const int roleCount = m_model->m_listModel->roleCount();
    if (!m_initialized) {
        if (roleCount) {
            Q_ALLOCA_VAR(int, changedRoles, roleCount * sizeof(int));
            for (int i = 0; i < roleCount; ++i)
                changedRoles[i] = i;
            emitDirectNotifies(changedRoles, roleCount);
        }
        return;
    }
    for (int i=0 ; i < roleCount ; ++i) {
        const ListLayout::Role &role = m_model->m_listModel->getExistingRole(i);
        QByteArray name = role.name.toUtf8();
        const QVariant &data = m_model->data(m_elementIndex, i);
        setValue(name, data, role.type == ListLayout::Role::List);
    }
}

void ModelNodeMetaObject::updateValues(const QVector<int> &roles)
{
    if (!m_initialized) {
        emitDirectNotifies(roles.constData(), roles.size());
        return;
    }
    int roleCount = roles.size();
    for (int i=0 ; i < roleCount ; ++i) {
        int roleIndex = roles.at(i);
        const ListLayout::Role &role = m_model->m_listModel->getExistingRole(roleIndex);
        QByteArray name = role.name.toUtf8();
        const QVariant &data = m_model->data(m_elementIndex, roleIndex);
        setValue(name, data, role.type == ListLayout::Role::List);
    }
}

void ModelNodeMetaObject::propertyWritten(int index)
{
    if (!m_enabled)
        return;

    QString propName = QString::fromUtf8(name(index));
    const QVariant value = this->value(index);

    QV4::Scope scope(m_model->engine());
    QV4::ScopedValue v(scope, scope.engine->fromVariant(value));

    int roleIndex = m_model->m_listModel->setExistingProperty(m_elementIndex, propName, v, scope.engine);
    if (roleIndex != -1)
        m_model->emitItemsChanged(m_elementIndex, 1, QVector<int>(1, roleIndex));
}

// Does the emission of the notifiers when we haven't created the meta-object yet
void ModelNodeMetaObject::emitDirectNotifies(const int *changedRoles, int roleCount)
{
    Q_ASSERT(!m_initialized);
    QQmlData *ddata = QQmlData::get(object(), /*create*/false);
    if (!ddata)
        return;
    // There's nothing to emit if we're a list model in a worker thread.
    if (!qmlEngine(m_model))
        return;
    for (int i = 0; i < roleCount; ++i) {
        const int changedRole = changedRoles[i];
        QQmlNotifier::notify(ddata, changedRole);
    }
}

namespace QV4 {

bool ModelObject::virtualPut(Managed *m, PropertyKey id, const Value &value, Value *receiver)
{
    if (!id.isString())
        return Object::virtualPut(m, id, value, receiver);
    QString propName = id.toQString();

    ModelObject *that = static_cast<ModelObject*>(m);

    ExecutionEngine *eng = that->engine();
    const int elementIndex = that->d()->elementIndex();
    if (QQmlListModel *model = that->d()->m_model) {
        const int roleIndex
                = model->listModel()->setExistingProperty(elementIndex, propName, value, eng);
        if (roleIndex != -1)
            model->emitItemsChanged(elementIndex, 1, QVector<int>(1, roleIndex));
    }

    ModelNodeMetaObject *mo = ModelNodeMetaObject::get(that->object());
    if (mo->initialized())
        mo->emitPropertyNotification(propName.toUtf8());
    return true;
}

ReturnedValue ModelObject::virtualGet(const Managed *m, PropertyKey id, const Value *receiver, bool *hasProperty)
{
    if (!id.isString())
        return QObjectWrapper::virtualGet(m, id, receiver, hasProperty);

    const ModelObject *that = static_cast<const ModelObject*>(m);
    Scope scope(that);
    ScopedString name(scope, id.asStringOrSymbol());
    QQmlListModel *model = that->d()->m_model;
    if (!model)
        return QObjectWrapper::virtualGet(m, id, receiver, hasProperty);

    const ListLayout::Role *role = model->listModel()->getExistingRole(name);
    if (!role)
        return QObjectWrapper::virtualGet(m, id, receiver, hasProperty);
    if (hasProperty)
        *hasProperty = true;

    if (QQmlEngine *qmlEngine = that->engine()->qmlEngine()) {
        QQmlEnginePrivate *ep = QQmlEnginePrivate::get(qmlEngine);
        if (ep && ep->propertyCapture)
            ep->propertyCapture->captureProperty(that->object(), -1, role->index, /*doNotify=*/ false);
    }

    const int elementIndex = that->d()->elementIndex();
    QVariant value = model->data(elementIndex, role->index);
    return that->engine()->fromVariant(value);
}

ReturnedValue ModelObject::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup)
{
    lookup->call = Lookup::Call::GetterQObjectPropertyFallback;
    return lookup->getter(engine, *object);
}

struct ModelObjectOwnPropertyKeyIterator : ObjectOwnPropertyKeyIterator
{
    int roleNameIndex = 0;
    ~ModelObjectOwnPropertyKeyIterator() override = default;
    PropertyKey next(const Object *o, Property *pd = nullptr, PropertyAttributes *attrs = nullptr) override;

};

PropertyKey ModelObjectOwnPropertyKeyIterator::next(const Object *o, Property *pd, PropertyAttributes *attrs)
{
    const ModelObject *that = static_cast<const ModelObject *>(o);

    ExecutionEngine *v4 = that->engine();

    QQmlListModel *model = that->d()->m_model;
    ListModel *listModel = model ? model->listModel() : nullptr;
    if (listModel && roleNameIndex < listModel->roleCount()) {
        Scope scope(that->engine());
        const ListLayout::Role &role = listModel->getExistingRole(roleNameIndex);
        ++roleNameIndex;
        ScopedString roleName(scope, v4->newString(role.name));
        if (attrs)
            *attrs = QV4::Attr_Data;
        if (pd) {

            QVariant value = model->data(that->d()->elementIndex(), role.index);
            if (auto recursiveListModel = qvariant_cast<QQmlListModel*>(value)) {
                auto size = recursiveListModel->count();
                auto array = ScopedArrayObject{scope, v4->newArrayObject(size)};
                QV4::ScopedValue val(scope);
                for (auto i = 0; i < size; i++) {
                    val = QJSValuePrivate::convertToReturnedValue(
                            v4, recursiveListModel->get(i));
                    array->arrayPut(i, val);
                }
                pd->value = array;
            } else {
                pd->value = v4->fromVariant(value);
            }
        }
        return roleName->toPropertyKey();
    }

    // Fall back to QV4::Object as opposed to QV4::QObjectWrapper otherwise it will add
    // unnecessary entries that relate to the roles used. These just create extra work
    // later on as they will just be ignored.
    return ObjectOwnPropertyKeyIterator::next(o, pd, attrs);
}

OwnPropertyKeyIterator *ModelObject::virtualOwnPropertyKeys(const Object *m, Value *target)
{
    *target = *m;
    return new ModelObjectOwnPropertyKeyIterator;
}

DEFINE_OBJECT_VTABLE(ModelObject);

} // namespace QV4

DynamicRoleModelNode::DynamicRoleModelNode(QQmlListModel *owner, int uid) : m_owner(owner), m_uid(uid), m_meta(new DynamicRoleModelNodeMetaObject(this))
{
    setNodeUpdatesEnabled(true);
}

DynamicRoleModelNode *DynamicRoleModelNode::create(const QVariantMap &obj, QQmlListModel *owner)
{
    DynamicRoleModelNode *object = new DynamicRoleModelNode(owner, uidCounter.fetchAndAddOrdered(1));
    QVector<int> roles;
    object->updateValues(obj, roles);
    return object;
}

QVector<int> DynamicRoleModelNode::sync(DynamicRoleModelNode *src, DynamicRoleModelNode *target)
{
    QVector<int> changedRoles;
    for (int i = 0; i < src->m_meta->count(); ++i) {
        const QByteArray &name = src->m_meta->name(i);
        QVariant value = src->m_meta->value(i);

        QQmlListModel *srcModel = qobject_cast<QQmlListModel *>(value.value<QObject *>());
        QQmlListModel *targetModel = qobject_cast<QQmlListModel *>(target->m_meta->value(i).value<QObject *>());

        bool modelHasChanges = false;
        if (srcModel) {
            if (targetModel == nullptr)
                targetModel = QQmlListModel::createWithOwner(target->m_owner);

            modelHasChanges = QQmlListModel::sync(srcModel, targetModel);

            QObject *targetModelObject = targetModel;
            value = QVariant::fromValue(targetModelObject);
        } else if (targetModel) {
            delete targetModel;
        }

        if (target->setValue(name, value) || modelHasChanges)
            changedRoles << target->m_owner->m_roles.indexOf(QString::fromUtf8(name));
    }
    return changedRoles;
}

void DynamicRoleModelNode::updateValues(const QVariantMap &object, QVector<int> &roles)
{
    for (auto it = object.cbegin(), end = object.cend(); it != end; ++it) {
        const QString &key = it.key();

        int roleIndex = m_owner->m_roles.indexOf(key);
        if (roleIndex == -1) {
            roleIndex = m_owner->m_roles.size();
            m_owner->m_roles.append(key);
        }

        QVariant value = it.value();

        // A JS array/object is translated into a (hierarchical) QQmlListModel,
        // so translate to a variant map/list first with toVariant().
        if (value.userType() == qMetaTypeId<QJSValue>())
            value = value.value<QJSValue>().toVariant();

        if (value.userType() == QMetaType::QVariantList) {
            QQmlListModel *subModel = QQmlListModel::createWithOwner(m_owner);

            QVariantList subArray = value.toList();
            QVariantList::const_iterator subIt = subArray.cbegin();
            QVariantList::const_iterator subEnd = subArray.cend();
            while (subIt != subEnd) {
                const QVariantMap &subObject = subIt->toMap();
                subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
                ++subIt;
            }

            QObject *subModelObject = subModel;
            value = QVariant::fromValue(subModelObject);
        }

        const QByteArray &keyUtf8 = key.toUtf8();

        QQmlListModel *existingModel = qobject_cast<QQmlListModel *>(m_meta->value(keyUtf8).value<QObject *>());
        delete existingModel;

        if (m_meta->setValue(keyUtf8, value))
            roles << roleIndex;
    }
}

DynamicRoleModelNodeMetaObject::DynamicRoleModelNodeMetaObject(DynamicRoleModelNode *object)
    : QQmlOpenMetaObject(object), m_enabled(false), m_owner(object)
{
}

DynamicRoleModelNodeMetaObject::~DynamicRoleModelNodeMetaObject()
{
    for (int i=0 ; i < count() ; ++i) {
        QQmlListModel *subModel = qobject_cast<QQmlListModel *>(value(i).value<QObject *>());
        delete subModel;
    }
}

void DynamicRoleModelNodeMetaObject::propertyWrite(int index)
{
    if (!m_enabled)
        return;

    QVariant v = value(index);
    QQmlListModel *model = qobject_cast<QQmlListModel *>(v.value<QObject *>());
    delete model;
}

void DynamicRoleModelNodeMetaObject::propertyWritten(int index)
{
    if (!m_enabled)
        return;

    QQmlListModel *parentModel = m_owner->m_owner;

    QVariant v = value(index);

    // A JS array/object is translated into a (hierarchical) QQmlListModel,
    // so translate to a variant map/list first with toVariant().
    if (v.userType() == qMetaTypeId<QJSValue>())
        v= v.value<QJSValue>().toVariant();

    if (v.userType() == QMetaType::QVariantList) {
        QQmlListModel *subModel = QQmlListModel::createWithOwner(parentModel);

        QVariantList subArray = v.toList();
        QVariantList::const_iterator subIt = subArray.cbegin();
        QVariantList::const_iterator subEnd = subArray.cend();
        while (subIt != subEnd) {
            const QVariantMap &subObject = subIt->toMap();
            subModel->m_modelObjects.append(DynamicRoleModelNode::create(subObject, subModel));
            ++subIt;
        }

        QObject *subModelObject = subModel;
        v = QVariant::fromValue(subModelObject);

        setValue(index, v);
    }

    int elementIndex = parentModel->m_modelObjects.indexOf(m_owner);
    if (elementIndex != -1) {
        int roleIndex = parentModel->m_roles.indexOf(QString::fromLatin1(name(index).constData()));
        if (roleIndex != -1)
            parentModel->emitItemsChanged(elementIndex, 1, QVector<int>(1, roleIndex));
    }
}

/*!
    \qmltype ListModel
    \nativetype QQmlListModel
    //! \inherits AbstractListModel
    \inqmlmodule QtQml.Models
    \ingroup qtquick-models
    \brief Defines a free-form list data source.

    The ListModel is a simple container of ListElement definitions, each
    containing data roles. The contents can be defined dynamically, or
    explicitly in QML.

    The number of elements in the model can be obtained from its \l count property.
    A number of familiar methods are also provided to manipulate the contents of the
    model, including append(), insert(), move(), remove() and set(). These methods
    accept dictionaries as their arguments; these are translated to ListElement objects
    by the model.

    Elements can be manipulated via the model using the setProperty() method, which
    allows the roles of the specified element to be set and changed.

    ListModel inherits from \l{QAbstractListModel} and provides its \l{Q_INVOKABLE}
    methods. You can, for example use \l{QAbstractItemModel::index} to retrieve a
    \l{QModelIndex} for a row and column.

    \section1 Example Usage

    The following example shows a ListModel containing three elements, with the roles
    "name" and "cost".

    \div {class="float-right"}
    \inlineimage listmodel.png
    \enddiv

    \snippet qml/listmodel/listmodel.qml 0

    Roles (properties) in each element must begin with a lower-case letter and
    should be common to all elements in a model. The ListElement documentation
    provides more guidelines for how elements should be defined.

    Since the example model contains an \c id property, it can be referenced
    by views, such as the ListView in this example:

    \snippet qml/listmodel/listmodel-simple.qml 0
    \dots 8
    \snippet qml/listmodel/listmodel-simple.qml 1

    It is possible for roles to contain list data.  In the following example we
    create a list of fruit attributes:

    \snippet qml/listmodel/listmodel-nested.qml model

    The delegate displays all the fruit attributes:

    \div {class="float-right"}
    \inlineimage listmodel-nested.png
    \enddiv

    \snippet qml/listmodel/listmodel-nested.qml delegate

    \section1 Modifying List Models

    The content of a ListModel may be created and modified using the clear(),
    append(), set(), insert() and setProperty() methods.  For example:

    \snippet qml/listmodel/listmodel-modify.qml delegate

    Note that when creating content dynamically the set of available properties
    cannot be changed once set. Whatever properties are first added to the model
    are the only permitted properties in the model.

    \section1 Using Threaded List Models with WorkerScript

    ListModel can be used together with WorkerScript to access a list model
    from multiple threads. This is useful if list modifications are
    synchronous and take some time: the list operations can be moved to a
    different thread to avoid blocking of the main GUI thread.

    Here is an example that uses WorkerScript to periodically append the
    current time to a list model:

    \snippet qml/listmodel/WorkerScript.qml 0

    The included file, \tt dataloader.mjs, looks like this:

    \snippet qml/listmodel/dataloader.mjs 0

    The timer in the main example sends messages to the worker script by calling
    \l WorkerScript::sendMessage(). When this message is received,
    \c WorkerScript.onMessage() is invoked in \c dataloader.mjs,
    which appends the current time to the list model.

    Note the call to sync() from the external thread.
    You must call sync() or else the changes made to the list from that
    thread will not be reflected in the list model in the main thread.

    \sa {qml-data-models}{Data Models}, {Qt Qml}
*/

QQmlListModel::QQmlListModel(QObject *parent)
: QAbstractListModel(parent)
{
    m_mainThread = true;
    m_primary = true;
    m_agent = nullptr;
    m_dynamicRoles = false;

    m_layout = new ListLayout;
    m_listModel = new ListModel(m_layout, this);

    m_engine = nullptr;
}

QQmlListModel::QQmlListModel(const QQmlListModel *owner, ListModel *data, QV4::ExecutionEngine *engine, QObject *parent)
: QAbstractListModel(parent)
{
    m_mainThread = owner->m_mainThread;
    m_primary = false;
    m_agent = owner->m_agent;

    Q_ASSERT(owner->m_dynamicRoles == false);
    m_dynamicRoles = false;
    m_layout = nullptr;
    m_listModel = data;

    m_engine = engine;
    m_compilationUnit = owner->m_compilationUnit;
}

QQmlListModel::QQmlListModel(QQmlListModel *orig, QQmlListModelWorkerAgent *agent)
: QAbstractListModel(agent)
{
    m_mainThread = false;
    m_primary = true;
    m_agent = agent;
    m_dynamicRoles = orig->m_dynamicRoles;

    if (ListLayout *layout = orig->m_layout)
        m_layout = new ListLayout(layout);
    else
        m_layout = new ListLayout;

    m_listModel = new ListModel(m_layout, this);

    if (m_dynamicRoles)
        sync(orig, this);
    else
        ListModel::sync(orig->m_listModel, m_listModel);

    m_engine = nullptr;
    m_compilationUnit = orig->m_compilationUnit;
}

QQmlListModel::~QQmlListModel()
{
    qDeleteAll(m_modelObjects);

    if (m_primary) {
        m_listModel->destroy();
        delete m_listModel;

        if (m_mainThread && m_agent)
            m_agent->modelDestroyed();
    }

    if (m_mainThread && m_agent)
        m_agent->release();

    m_listModel = nullptr;

    delete m_layout;
    m_layout = nullptr;
}

QQmlListModel *QQmlListModel::createWithOwner(QQmlListModel *newOwner)
{
    QQmlListModel *model = new QQmlListModel;

    model->m_mainThread = newOwner->m_mainThread;
    model->m_engine = newOwner->m_engine;
    model->m_agent = newOwner->m_agent;
    model->m_dynamicRoles = newOwner->m_dynamicRoles;

    if (model->m_mainThread && model->m_agent)
        model->m_agent->addref();

    QQmlEngine::setContextForObject(model, QQmlEngine::contextForObject(newOwner));

    return model;
}

QV4::ExecutionEngine *QQmlListModel::engine() const
{
    if (m_engine == nullptr) {
        m_engine  = qmlEngine(this)->handle();
    }

    return m_engine;
}

bool QQmlListModel::sync(QQmlListModel *src, QQmlListModel *target)
{
    Q_ASSERT(src->m_dynamicRoles && target->m_dynamicRoles);

    bool hasChanges = false;

    target->m_roles = src->m_roles;

    // Build hash of elements <-> uid for each of the lists
    QHash<int, ElementSync> elementHash;
    for (int i = 0 ; i < target->m_modelObjects.size(); ++i) {
        DynamicRoleModelNode *e = target->m_modelObjects.at(i);
        int uid = e->getUid();
        ElementSync sync;
        sync.target = e;
        sync.targetIndex = i;
        elementHash.insert(uid, sync);
    }
    for (int i = 0 ; i < src->m_modelObjects.size(); ++i) {
        DynamicRoleModelNode *e = src->m_modelObjects.at(i);
        int uid = e->getUid();

        QHash<int, ElementSync>::iterator it = elementHash.find(uid);
        if (it == elementHash.end()) {
            ElementSync sync;
            sync.src = e;
            sync.srcIndex = i;
            elementHash.insert(uid, sync);
        } else {
            ElementSync &sync = it.value();
            sync.src = e;
            sync.srcIndex = i;
        }
    }

    // Get list of elements that are in the target but no longer in the source. These get deleted first.
    int rowsRemoved = 0;
    for (int i = 0 ; i < target->m_modelObjects.size() ; ++i) {
        DynamicRoleModelNode *element = target->m_modelObjects.at(i);
        ElementSync &s = elementHash.find(element->getUid()).value();
        Q_ASSERT(s.targetIndex >= 0);
        // need to update the targetIndex, to keep it correct after removals
        s.targetIndex -= rowsRemoved;
        if (s.src == nullptr) {
            Q_ASSERT(s.targetIndex == i);
            hasChanges = true;
            target->beginRemoveRows(QModelIndex(), i, i);
            target->m_modelObjects.remove(i, 1);
            target->endRemoveRows();
            delete s.target;
            ++rowsRemoved;
            --i;
            continue;
        }
    }

    // Clear the target list, and append in correct order from the source
    target->m_modelObjects.clear();
    for (int i = 0 ; i < src->m_modelObjects.size() ; ++i) {
        DynamicRoleModelNode *element = src->m_modelObjects.at(i);
        ElementSync &s = elementHash.find(element->getUid()).value();
        Q_ASSERT(s.srcIndex >= 0);
        DynamicRoleModelNode *targetElement = s.target;
        if (targetElement == nullptr) {
            targetElement = new DynamicRoleModelNode(target, element->getUid());
        }
        s.changedRoles = DynamicRoleModelNode::sync(element, targetElement);
        target->m_modelObjects.append(targetElement);
    }

    // now emit the change notifications required. This can be safely done, as we're only emitting changes, moves and inserts,
    // so the model indices can't be out of bounds
    //
    // to ensure things are kept in the correct order, emit inserts and moves first. This shouls ensure all persistent
    // model indices are updated correctly
    int rowsInserted = 0;
    for (int i = 0 ; i < target->m_modelObjects.size() ; ++i) {
        DynamicRoleModelNode *element = target->m_modelObjects.at(i);
        ElementSync &s = elementHash.find(element->getUid()).value();
        Q_ASSERT(s.srcIndex >= 0);
        s.srcIndex += rowsInserted;
        if (s.srcIndex != s.targetIndex) {
            if (s.targetIndex == -1) {
                target->beginInsertRows(QModelIndex(), i, i);
                target->endInsertRows();
            } else {
                target->beginMoveRows(QModelIndex(), i, i, QModelIndex(), s.srcIndex);
                target->endMoveRows();
            }
            hasChanges = true;
            ++rowsInserted;
        }
        if (s.targetIndex != -1 && !s.changedRoles.isEmpty()) {
            QModelIndex idx = target->createIndex(i, 0);
            emit target->dataChanged(idx, idx, s.changedRoles);
            hasChanges = true;
        }
    }
    return hasChanges;
}

void QQmlListModel::emitItemsChanged(int index, int count, const QVector<int> &roles)
{
    if (count <= 0)
        return;

    if (m_mainThread)
        emit dataChanged(createIndex(index, 0), createIndex(index + count - 1, 0), roles);;
}

void QQmlListModel::emitItemsAboutToBeInserted(int index, int count)
{
    Q_ASSERT(index >= 0 && count >= 0);
    if (m_mainThread)
        beginInsertRows(QModelIndex(), index, index + count - 1);
}

void QQmlListModel::emitItemsInserted()
{
    if (m_mainThread) {
        endInsertRows();
        emit countChanged();
    }
}

QQmlListModelWorkerAgent *QQmlListModel::agent()
{
    if (m_agent)
        return m_agent;

    m_agent = new QQmlListModelWorkerAgent(this);
    return m_agent;
}

QModelIndex QQmlListModel::index(int row, int column, const QModelIndex &parent) const
{
    return row >= 0 && row < count() && column == 0 && !parent.isValid()
            ? createIndex(row, column)
            : QModelIndex();
}

int QQmlListModel::rowCount(const QModelIndex &parent) const
{
    return !parent.isValid() ? count() : 0;
}

QVariant QQmlListModel::data(const QModelIndex &index, int role) const
{
    return data(index.row(), role);
}

bool QQmlListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    const int row = index.row();
    if (row >= count() || row < 0)
        return false;

    if (m_dynamicRoles) {
        const QByteArray property = m_roles.at(role).toUtf8();
        if (m_modelObjects[row]->setValue(property, value)) {
            emitItemsChanged(row, 1, QVector<int>(1, role));
            return true;
        }
    } else {
        const ListLayout::Role &r = m_listModel->getExistingRole(role);
        const int roleIndex = m_listModel->setOrCreateProperty(row, r.name, value);
        if (roleIndex != -1) {
            emitItemsChanged(row, 1, QVector<int>(1, role));
            return true;
        }
    }

    return false;
}

QVariant QQmlListModel::data(int index, int role) const
{
    QVariant v;

    if (index >= count() || index < 0)
        return v;

    if (m_dynamicRoles)
        v = m_modelObjects[index]->getValue(m_roles[role]);
    else
        v = m_listModel->getProperty(index, role, this, engine());

    return v;
}

QHash<int, QByteArray> QQmlListModel::roleNames() const
{
    QHash<int, QByteArray> roleNames;

    if (m_dynamicRoles) {
        for (int i = 0 ; i < m_roles.size() ; ++i)
            roleNames.insert(i, m_roles.at(i).toUtf8());
    } else {
        for (int i = 0 ; i < m_listModel->roleCount() ; ++i) {
            const ListLayout::Role &r = m_listModel->getExistingRole(i);
            roleNames.insert(i, r.name.toUtf8());
        }
    }

    return roleNames;
}

/*!
    \qmlproperty bool ListModel::dynamicRoles

    By default, the type of a role is fixed the first time
    the role is used. For example, if you create a role called
    "data" and assign a number to it, you can no longer assign
    a string to the "data" role. However, when the dynamicRoles
    property is enabled, the type of a given role is not fixed
    and can be different between elements.

    The dynamicRoles property must be set before any data is
    added to the ListModel, and must be set from the main
    thread.

    A ListModel that has data statically defined (via the
    ListElement QML syntax) cannot have the dynamicRoles
    property enabled.

    There is a significant performance cost to using a
    ListModel with dynamic roles enabled. The cost varies
    from platform to platform but is typically somewhere
    between 4-6x slower than using static role types.

    Due to the performance cost of using dynamic roles,
    they are disabled by default.
*/
void QQmlListModel::setDynamicRoles(bool enableDynamicRoles)
{
    if (m_mainThread && m_agent == nullptr) {
        if (enableDynamicRoles) {
            if (m_layout && m_layout->roleCount())
                qmlWarning(this) << tr("unable to enable dynamic roles as this model is not empty");
            else
                m_dynamicRoles = true;
        } else {
            if (m_roles.size()) {
                qmlWarning(this) << tr("unable to enable static roles as this model is not empty");
            } else {
                m_dynamicRoles = false;
            }
        }
    } else {
        qmlWarning(this) << tr("dynamic role setting must be made from the main thread, before any worker scripts are created");
    }
}

/*!
    \qmlproperty int ListModel::count
    The number of data entries in the model.
*/
int QQmlListModel::count() const
{
    return m_dynamicRoles ? m_modelObjects.size() : m_listModel->elementCount();
}

/*!
    \qmlmethod ListModel::clear()

    Deletes all content from the model. In particular this invalidates all objects you may have
    retrieved using \l get().

    \sa append(), remove(), get()
*/
void QQmlListModel::clear()
{
    removeElements(0, count());
}

/*!
    \qmlmethod ListModel::remove(int index, int count = 1)

    Deletes \a count number of items at \a index from the model.

    \sa clear()
*/
void QQmlListModel::remove(QQmlV4FunctionPtr args)
{
    int argLength = args->length();

    if (argLength == 1 || argLength == 2) {
        QV4::Scope scope(args->v4engine());
        int index = QV4::ScopedValue(scope, (*args)[0])->toInt32();
        int removeCount = (argLength == 2 ? QV4::ScopedValue(scope, (*args)[1])->toInt32() : 1);

        if (index < 0 || index+removeCount > count() || removeCount <= 0) {
            qmlWarning(this) << tr("remove: indices [%1 - %2] out of range [0 - %3]").arg(index).arg(index+removeCount).arg(count());
            return;
        }

        removeElements(index, removeCount);
    } else {
        qmlWarning(this) << tr("remove: incorrect number of arguments");
    }
}

void QQmlListModel::removeElements(int index, int removeCount)
{
    Q_ASSERT(index >= 0 && removeCount >= 0);

    if (!removeCount)
        return;

    if (m_mainThread)
        beginRemoveRows(QModelIndex(), index, index + removeCount - 1);

    QVector<std::function<void()>> toDestroy;
    if (m_dynamicRoles) {
        for (int i=0 ; i < removeCount ; ++i) {
            auto modelObject = m_modelObjects[index+i];
            toDestroy.append([modelObject](){
                delete modelObject;
            });
        }
        m_modelObjects.remove(index, removeCount);
    } else {
        toDestroy = m_listModel->remove(index, removeCount);
    }

    if (m_mainThread) {
        endRemoveRows();
        emit countChanged();
    }
    for (const auto &destroyer : toDestroy)
        destroyer();
}

void QQmlListModel::updateTranslations()
{
    // assumption: it is impossible to have retranslatable strings in a
    // dynamic list model, as they would already have "decayed" to strings
    // when they were inserted
    if (m_dynamicRoles)
        return;
    Q_ASSERT(m_listModel);

    QList<int> roles;
    for (int i = 0, end = m_listModel->roleCount(); i != end; ++i) {
        if (m_listModel->getExistingRole(i).type == ListLayout::Role::String)
            roles.append(i);
    }

    if (!roles.isEmpty())
        emitItemsChanged(0, rowCount(QModelIndex()), roles);

    m_listModel->updateTranslations();
}

/*!
    \qmlmethod ListModel::insert(int index, jsobject dict)

    Adds a new item to the list model at position \a index, with the
    values in \a dict.

    \code
        fruitModel.insert(2, {"cost": 5.95, "name":"Pizza"})
    \endcode

    The \a index must be to an existing item in the list, or one past
    the end of the list (equivalent to append).

    \sa set(), append()
*/

void QQmlListModel::insert(QQmlV4FunctionPtr args)
{
    if (args->length() == 2) {
        QV4::Scope scope(args->v4engine());
        QV4::ScopedValue arg0(scope, (*args)[0]);
        int index = arg0->toInt32();

        if (index < 0 || index > count()) {
            qmlWarning(this) << tr("insert: index %1 out of range").arg(index);
            return;
        }

        QV4::ScopedObject argObject(scope, (*args)[1]);
        QV4::ScopedArrayObject objectArray(scope, (*args)[1]);
        if (objectArray) {
            QV4::ScopedObject argObject(scope);

            int objectArrayLength = objectArray->getLength();
            emitItemsAboutToBeInserted(index, objectArrayLength);
            for (int i=0 ; i < objectArrayLength ; ++i) {
                argObject = objectArray->get(i);

                if (m_dynamicRoles) {
                    m_modelObjects.insert(index+i, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
                } else {
                    m_listModel->insert(index+i, argObject);
                }
            }
            emitItemsInserted();
        } else if (argObject) {
            emitItemsAboutToBeInserted(index, 1);

            if (m_dynamicRoles) {
                m_modelObjects.insert(index, DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
            } else {
                m_listModel->insert(index, argObject);
            }

            emitItemsInserted();
        } else {
            qmlWarning(this) << tr("insert: value is not an object");
        }
    } else {
        qmlWarning(this) << tr("insert: value is not an object");
    }
}

/*!
    \qmlmethod ListModel::move(int from, int to, int n)

    Moves \a n items \a from one position \a to another.

    The from and to ranges must exist; for example, to move the first 3 items
    to the end of the list:

    \code
        fruitModel.move(0, fruitModel.count - 3, 3)
    \endcode

    \sa append()
*/
void QQmlListModel::move(int from, int to, int n)
{
    if (n == 0 || from == to)
        return;
    if (!canMove(from, to, n)) {
        qmlWarning(this) << tr("move: out of range");
        return;
    }

    if (m_mainThread)
        beginMoveRows(QModelIndex(), from, from + n - 1, QModelIndex(), to > from ? to + n : to);

    if (m_dynamicRoles) {

        int realFrom = from;
        int realTo = to;
        int realN = n;

        if (from > to) {
            // Only move forwards - flip if backwards moving
            int tfrom = from;
            int tto = to;
            realFrom = tto;
            realTo = tto+n;
            realN = tfrom-tto;
        }

        QPODVector<DynamicRoleModelNode *, 4> store;
        for (int i=0 ; i < (realTo-realFrom) ; ++i)
            store.append(m_modelObjects[realFrom+realN+i]);
        for (int i=0 ; i < realN ; ++i)
            store.append(m_modelObjects[realFrom+i]);
        for (int i=0 ; i < store.count() ; ++i)
            m_modelObjects[realFrom+i] = store[i];

    } else {
        m_listModel->move(from, to, n);
    }

    if (m_mainThread)
        endMoveRows();
}

/*!
    \qmlmethod ListModel::append(jsobject dict)

    Adds a new item to the end of the list model, with the
    values in \a dict.

    \code
        fruitModel.append({"cost": 5.95, "name":"Pizza"})
    \endcode

    \sa set(), remove()
*/
void QQmlListModel::append(QQmlV4FunctionPtr args)
{
    if (args->length() == 1) {
        QV4::Scope scope(args->v4engine());
        QV4::ScopedObject argObject(scope, (*args)[0]);
        QV4::ScopedArrayObject objectArray(scope, (*args)[0]);

        if (objectArray) {
            QV4::ScopedObject argObject(scope);

            int objectArrayLength = objectArray->getLength();
            if (objectArrayLength > 0) {
                int index = count();
                emitItemsAboutToBeInserted(index, objectArrayLength);

                for (int i=0 ; i < objectArrayLength ; ++i) {
                    argObject = objectArray->get(i);

                    if (m_dynamicRoles) {
                        m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
                    } else {
                        m_listModel->append(argObject);
                    }
                }

                emitItemsInserted();
            }
        } else if (argObject) {
            int index;

            if (m_dynamicRoles) {
                index = m_modelObjects.size();
                emitItemsAboutToBeInserted(index, 1);
                m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(argObject), this));
            } else {
                index = m_listModel->elementCount();
                emitItemsAboutToBeInserted(index, 1);
                m_listModel->append(argObject);
            }

            emitItemsInserted();
        } else {
            qmlWarning(this) << tr("append: value is not an object");
        }
    } else {
        qmlWarning(this) << tr("append: value is not an object");
    }
}

/*!
    \qmlmethod object ListModel::get(int index)

    Returns the item at \a index in the list model. This allows the item
    data to be accessed or modified from JavaScript:

    \code
    Component.onCompleted: {
        fruitModel.append({"cost": 5.95, "name":"Jackfruit"});
        console.log(fruitModel.get(0).cost);
        fruitModel.get(0).cost = 10.95;
    }
    \endcode

    The \a index must be an element in the list.

    Note that properties of the returned object that are themselves objects
    will also be models, and this get() method is used to access elements:

    \code
        fruitModel.append(..., "attributes":
            [{"name":"spikes","value":"7mm"},
             {"name":"color","value":"green"}]);
        fruitModel.get(0).attributes.get(1).value; // == "green"
    \endcode

    \warning The returned object is not guaranteed to remain valid. It
    should not be used in \l{Property Binding}{property bindings} or for
    storing data across modifications of its origin ListModel.

    \sa append(), clear()
*/
QJSValue QQmlListModel::get(int index) const
{
    QV4::Scope scope(engine());
    QV4::ScopedValue result(scope, QV4::Value::undefinedValue());

    if (index >= 0 && index < count()) {

        if (m_dynamicRoles) {
            DynamicRoleModelNode *object = m_modelObjects[index];
            result = QV4::QObjectWrapper::wrap(scope.engine, object);
        } else {
            QObject *object = m_listModel->getOrCreateModelObject(const_cast<QQmlListModel *>(this), index);
            QQmlData *ddata = QQmlData::get(object);
            if (ddata->jsWrapper.isNullOrUndefined()) {
                result = scope.engine->memoryManager->allocate<QV4::ModelObject>(object, const_cast<QQmlListModel *>(this));
                // Keep track of the QObjectWrapper in persistent value storage
                ddata->jsWrapper.set(scope.engine, result);
            } else {
                result = ddata->jsWrapper.value();
            }
        }
    }

    return QJSValuePrivate::fromReturnedValue(result->asReturnedValue());
}

/*!
    \qmlmethod ListModel::set(int index, jsobject dict)

    Changes the item at \a index in the list model with the
    values in \a dict. Properties not appearing in \a dict
    are left unchanged.

    \code
        fruitModel.set(3, {"cost": 5.95, "name":"Pizza"})
    \endcode

    If \a index is equal to count() then a new item is appended to the
    list. Otherwise, \a index must be an element in the list.

    \sa append()
*/
void QQmlListModel::set(int index, const QJSValue &value)
{
    QV4::Scope scope(engine());
    QV4::ScopedObject object(scope, QJSValuePrivate::asReturnedValue(&value));

    if (!object) {
        qmlWarning(this) << tr("set: value is not an object");
        return;
    }
    if (index > count() || index < 0) {
        qmlWarning(this) << tr("set: index %1 out of range").arg(index);
        return;
    }


    if (index == count()) {
        emitItemsAboutToBeInserted(index, 1);

        if (m_dynamicRoles) {
            m_modelObjects.append(DynamicRoleModelNode::create(scope.engine->variantMapFromJS(object), this));
        } else {
            m_listModel->insert(index, object);
        }

        emitItemsInserted();
    } else {

        QVector<int> roles;

        if (m_dynamicRoles) {
            m_modelObjects[index]->updateValues(scope.engine->variantMapFromJS(object), roles);
        } else {
            m_listModel->set(index, object, &roles);
        }

        if (roles.size())
            emitItemsChanged(index, 1, roles);
    }
}

/*!
    \qmlmethod ListModel::setProperty(int index, string property, variant value)

    Changes the \a property of the item at \a index in the list model to \a value.

    \code
        fruitModel.setProperty(3, "cost", 5.95)
    \endcode

    The \a index must be an element in the list.

    \sa append()
*/
void QQmlListModel::setProperty(int index, const QString& property, const QVariant& value)
{
    if (count() == 0 || index >= count() || index < 0) {
        qmlWarning(this) << tr("set: index %1 out of range").arg(index);
        return;
    }

    if (m_dynamicRoles) {
        int roleIndex = m_roles.indexOf(property);
        if (roleIndex == -1) {
            roleIndex = m_roles.size();
            m_roles.append(property);
        }
        if (m_modelObjects[index]->setValue(property.toUtf8(), value))
            emitItemsChanged(index, 1, QVector<int>(1, roleIndex));
    } else {
        int roleIndex = m_listModel->setOrCreateProperty(index, property, value);
        if (roleIndex != -1)
            emitItemsChanged(index, 1, QVector<int>(1, roleIndex));
    }
}

/*!
    \qmlmethod ListModel::sync()

    Writes any unsaved changes to the list model after it has been modified
    from a worker script.
*/
void QQmlListModel::sync()
{
    // This is just a dummy method to make it look like sync() exists in
    // ListModel (and not just QQmlListModelWorkerAgent) and to let
    // us document sync().
    qmlWarning(this) << "List sync() can only be called from a WorkerScript";
}

bool QQmlListModelParser::verifyProperty(
        const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
        const QV4::CompiledData::Binding *binding)
{
    if (binding->type() >= QV4::CompiledData::Binding::Type_Object) {
        const quint32 targetObjectIndex = binding->value.objectIndex;
        const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex);
        QString objName = compilationUnit->stringAt(target->inheritedTypeNameIndex);
        if (objName != listElementTypeName) {
            const QMetaObject *mo = resolveType(objName);
            if (mo != &QQmlListElement::staticMetaObject) {
                error(target, QQmlListModel::tr("ListElement: cannot contain nested elements"));
                return false;
            }
            listElementTypeName = objName; // cache right name for next time
        }

        if (!compilationUnit->stringAt(target->idNameIndex).isEmpty()) {
            error(target->locationOfIdProperty, QQmlListModel::tr("ListElement: cannot use reserved \"id\" property"));
            return false;
        }

        const QV4::CompiledData::Binding *binding = target->bindingTable();
        for (quint32 i = 0; i < target->nBindings; ++i, ++binding) {
            QString propName = compilationUnit->stringAt(binding->propertyNameIndex);
            if (propName.isEmpty()) {
                error(binding, QQmlListModel::tr("ListElement: cannot contain nested elements"));
                return false;
            }
            if (!verifyProperty(compilationUnit, binding))
                return false;
        }
    } else if (binding->type() == QV4::CompiledData::Binding::Type_Script) {
        QString scriptStr = compilationUnit->bindingValueAsScriptString(binding);
        if (!binding->isFunctionExpression() && !definesEmptyList(scriptStr)) {
            bool ok;
            evaluateEnum(scriptStr, &ok);
            if (!ok) {
                error(binding, QQmlListModel::tr("ListElement: cannot use script for property value"));
                return false;
            }
        }
    }

    return true;
}

bool QQmlListModelParser::applyProperty(
        const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
        const QV4::CompiledData::Binding *binding, ListModel *model, QQmlListModel *owner,
        int outterElementIndex)
{
    const QString elementName = compilationUnit->stringAt(binding->propertyNameIndex);

    bool roleSet = false;
    const QV4::CompiledData::Binding::Type bindingType = binding->type();
    if (bindingType >= QV4::CompiledData::Binding::Type_Object) {
        const quint32 targetObjectIndex = binding->value.objectIndex;
        const QV4::CompiledData::Object *target = compilationUnit->objectAt(targetObjectIndex);

        ListModel *subModel = nullptr;
        if (outterElementIndex == -1) {
            subModel = model;
        } else {
            const ListLayout::Role &role = model->getOrCreateListRole(elementName);
            if (role.type == ListLayout::Role::List) {
                subModel = model->getListProperty(outterElementIndex, role);
                if (subModel == nullptr) {
                    subModel = new ListModel(role.subLayout, nullptr);
                    QVariant vModel = QVariant::fromValue(subModel);
                    model->setOrCreateProperty(outterElementIndex, elementName, vModel);
                }
            }
        }

        int elementIndex = subModel ? subModel->appendElement() : -1;

        const QV4::CompiledData::Binding *subBinding = target->bindingTable();
        for (quint32 i = 0; i < target->nBindings; ++i, ++subBinding) {
            roleSet |= applyProperty(compilationUnit, subBinding, subModel, owner, elementIndex);
        }

    } else {
        QVariant value;

        const bool isTranslationBinding = binding->isTranslationBinding();
        if (isTranslationBinding) {
            value = QVariant::fromValue<const QV4::CompiledData::Binding*>(binding);
        } else if (binding->evaluatesToString()) {
            value = compilationUnit->bindingValueAsString(binding);
        } else if (bindingType == QV4::CompiledData::Binding::Type_Number) {
            value = compilationUnit->bindingValueAsNumber(binding);
        } else if (bindingType == QV4::CompiledData::Binding::Type_Boolean) {
            value = binding->valueAsBoolean();
        } else if (bindingType == QV4::CompiledData::Binding::Type_Null) {
            value = QVariant::fromValue(nullptr);
        } else if (bindingType == QV4::CompiledData::Binding::Type_Script) {
            QString scriptStr = compilationUnit->bindingValueAsScriptString(binding);
            if (definesEmptyList(scriptStr)) {
                const ListLayout::Role &role = model->getOrCreateListRole(elementName);
                ListModel *emptyModel = new ListModel(role.subLayout, nullptr);
                value = QVariant::fromValue(emptyModel);
            } else if (binding->isFunctionExpression()) {
                QQmlBinding::Identifier id = binding->value.compiledScriptIndex;
                Q_ASSERT(id != QQmlBinding::Invalid);

                auto v4 = compilationUnit->engine;
                QV4::Scope scope(v4);

                if (model->m_modelCache == nullptr) {
                    model->m_modelCache = new QQmlListModel(owner, model, v4);
                    QQmlEngine::setContextForObject(
                            model->m_modelCache, QQmlEngine::contextForObject(owner));
                }

                // for now we do not provide a context object; data from the ListElement must be passed to the function
                QV4::ScopedContext context(scope, QV4::QmlContext::create(v4->rootContext(), QQmlContextData::get(qmlContext(model->m_modelCache)), nullptr));
                QV4::ScopedFunctionObject function(scope, QV4::FunctionObject::createScriptFunction(context, compilationUnit->runtimeFunctions[id]));

                QJSValue v;
                QV4::ScopedValue result(scope, function->call(v4->globalObject, nullptr, 0));
                if (v4->hasException)
                    v4->catchException();
                else
                    QJSValuePrivate::setValue(&v, result);
                value.setValue(v);
            } else {
                bool ok;
                value = evaluateEnum(scriptStr, &ok);
            }
        } else {
            Q_UNREACHABLE();
        }

        if (!model)
            return roleSet;
        model->setOrCreateProperty(outterElementIndex, elementName, value);
        auto listModel = model->m_modelCache;
        if (isTranslationBinding && listModel) {
            if (!listModel->translationChangeHandler) {
                auto ep = QQmlEnginePrivate::get(compilationUnit->engine);
                model->m_modelCache->translationChangeHandler = std::make_unique<QPropertyNotifier>(
                            ep->translationLanguage.addNotifier([listModel](){ listModel->updateTranslations(); }));
            }
        }
        roleSet = true;
    }
    return roleSet;
}

void QQmlListModelParser::verifyBindings(
        const QQmlRefPointer<QV4::CompiledData::CompilationUnit> &compilationUnit,
        const QList<const QV4::CompiledData::Binding *> &bindings)
{
    listElementTypeName = QString(); // unknown

    for (const QV4::CompiledData::Binding *binding : bindings) {
        QString propName = compilationUnit->stringAt(binding->propertyNameIndex);
        if (!propName.isEmpty()) { // isn't default property
            error(binding, QQmlListModel::tr("ListModel: undefined property '%1'").arg(propName));
            return;
        }
        if (!verifyProperty(compilationUnit, binding))
            return;
    }
}

void QQmlListModelParser::applyBindings(
        QObject *obj, const QQmlRefPointer<QV4::ExecutableCompilationUnit> &compilationUnit,
        const QList<const QV4::CompiledData::Binding *> &bindings)
{
    QQmlListModel *rv = static_cast<QQmlListModel *>(obj);

    rv->m_engine = qmlEngine(rv)->handle();
    rv->m_compilationUnit = compilationUnit;

    bool setRoles = false;

    for (const QV4::CompiledData::Binding *binding : bindings) {
        if (binding->type() != QV4::CompiledData::Binding::Type_Object)
            continue;
        setRoles |= applyProperty(
                compilationUnit, binding, rv->m_listModel, rv, /*outter element index*/-1);
    }

    if (setRoles == false)
        qmlWarning(obj) << "All ListElement declarations are empty, no roles can be created unless dynamicRoles is set.";
}

bool QQmlListModelParser::definesEmptyList(const QString &s)
{
    if (s.startsWith(QLatin1Char('[')) && s.endsWith(QLatin1Char(']'))) {
        for (int i=1; i<s.size()-1; i++) {
            if (!s[i].isSpace())
                return false;
        }
        return true;
    }
    return false;
}


/*!
    \qmltype ListElement
    \nativetype QQmlListElement
    \inqmlmodule QtQml.Models
    \brief Defines a data item in a ListModel.
    \ingroup qtquick-models

    List elements are defined inside ListModel definitions, and represent items in a
    list that will be displayed using ListView or \l Repeater items.

    List elements are defined like other QML elements except that they contain
    a collection of \e role definitions instead of properties. Using the same
    syntax as property definitions, roles both define how the data is accessed
    and include the data itself.

    The names used for roles must begin with a lower-case letter and should be
    common to all elements in a given model. Values must be simple constants; either
    strings (quoted and optionally within a call to
    \l [QML] {Qt::} {QT_TR_NOOP()}, boolean values (true, false), numbers, or
    enumeration values (such as AlignText.AlignHCenter).

    Beginning with Qt 5.11 ListElement also allows assigning a function declaration to
    a role. This allows the definition of ListElements with callable actions.

    \section1 Referencing Roles

    The role names are used by delegates to obtain data from list elements.
    Each role name is accessible in the delegate's scope, and refers to the
    corresponding role in the current element. Where a role name would be
    ambiguous to use, it can be accessed via the \l{ListView::}{model}
    property (e.g., \c{model.cost} instead of \c{cost}).

    \section1 Example Usage

    The following model defines a series of list elements, each of which
    contain "name" and "cost" roles and their associated values.

    \snippet qml/listmodel/listelements.qml model

    The delegate obtains the name and cost for each element by simply referring
    to \c name and \c cost:

    \snippet qml/listmodel/listelements.qml view

    \sa ListModel
*/

QT_END_NAMESPACE

#include "moc_qqmllistmodel_p_p.cpp"

#include "moc_qqmllistmodel_p.cpp"
