// 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 "qqmltypecompiler_p.h"

#include <private/qqmlobjectcreator_p.h>
#include <private/qqmlcustomparser_p.h>
#include <private/qqmlvmemetaobject_p.h>
#include <private/qqmlcomponent_p.h>
#include <private/qqmlpropertyresolver_p.h>
#include <private/qqmlcomponentandaliasresolver_p.h>
#include <private/qqmlsignalnames_p.h>

#define COMPILE_EXCEPTION(token, desc) \
    { \
        recordError((token)->location, desc); \
        return false; \
    }

QT_BEGIN_NAMESPACE

DEFINE_BOOL_CONFIG_OPTION(
        disableInternalDeferredProperties, QML_DISABLE_INTERNAL_DEFERRED_PROPERTIES);

Q_LOGGING_CATEGORY(lcQmlTypeCompiler, "qt.qml.typecompiler");

QQmlTypeCompiler::QQmlTypeCompiler(
        QQmlTypeLoader *typeLoader, QQmlTypeData *typeData, QmlIR::Document *parsedQML,
        QV4::CompiledData::ResolvedTypeReferenceMap *resolvedTypeCache,
        const QV4::CompiledData::DependentTypesHasher &dependencyHasher)
    : resolvedTypes(resolvedTypeCache)
    , loader(typeLoader)
    , dependencyHasher(dependencyHasher)
    , document(parsedQML)
    , typeData(typeData)
{
}

QQmlRefPointer<QV4::CompiledData::CompilationUnit> QQmlTypeCompiler::compile()
{
    // Build property caches and VME meta object data

    for (auto it = resolvedTypes->constBegin(), end = resolvedTypes->constEnd();
         it != end; ++it) {
        QQmlCustomParser *customParser = (*it)->type().customParser();
        if (customParser)
            customParsers.insert(it.key(), customParser);
    }

    QQmlPendingGroupPropertyBindings pendingGroupPropertyBindings;


    {
        QQmlPropertyCacheCreator<QQmlTypeCompiler> propertyCacheBuilder(
                &m_propertyCaches, &pendingGroupPropertyBindings,
                loader, this, imports(), typeData->typeClassName());
        QQmlError cycleError = propertyCacheBuilder.verifyNoICCycle();
        if (cycleError.isValid()) {
            recordError(cycleError);
            return nullptr;
        }
        QQmlPropertyCacheCreatorBase::IncrementalResult result;
        do {
            result = propertyCacheBuilder.buildMetaObjectsIncrementally();
            const QQmlError &error = result.error;
            if (error.isValid()) {
                recordError(error);
                return nullptr;
            } else {
                // Resolve component boundaries and aliases

                QQmlComponentAndAliasResolver resolver(this, &m_propertyCaches);
                if (QQmlError error = resolver.resolve(result.processedRoot); error.isValid()) {
                    recordError(error);
                    return nullptr;
                }
                pendingGroupPropertyBindings.resolveMissingPropertyCaches(&m_propertyCaches);
                pendingGroupPropertyBindings.clear(); // anything that can be processed is now processed
            }
        } while (result.canResume);
    }

    {
        QQmlDefaultPropertyMerger merger(this);
        merger.mergeDefaultProperties();
    }

    {
        SignalHandlerResolver converter(this);
        if (!converter.resolveSignalHandlerExpressions())
            return nullptr;
    }

    {
        QQmlEnumTypeResolver enumResolver(this);
        if (!enumResolver.resolveEnumBindings())
            return nullptr;
    }

    {
        QQmlCustomParserScriptIndexer cpi(this);
        cpi.annotateBindingsWithScriptStrings();
    }

    {
        QQmlAliasAnnotator annotator(this);
        annotator.annotateBindingsToAliases();
    }

    {
        QQmlDeferredAndCustomParserBindingScanner deferredAndCustomParserBindingScanner(this);
        if (!deferredAndCustomParserBindingScanner.scanObject())
            return nullptr;
    }

    if (!document->javaScriptCompilationUnit || !document->javaScriptCompilationUnit->unitData()) {
        // Compile JS binding expressions and signal handlers if necessary
        {
            // We can compile script strings ahead of time, but they must be compiled
            // without type optimizations as their scope is always entirely dynamic.
            QQmlScriptStringScanner sss(this);
            sss.scan();
        }

        Q_ASSERT(document->jsModule.fileName == typeData->urlString());
        Q_ASSERT(document->jsModule.finalUrl == typeData->finalUrlString());
        QmlIR::JSCodeGen v4CodeGenerator(document);
        for (QmlIR::Object *object : std::as_const(document->objects)) {
            if (!v4CodeGenerator.generateRuntimeFunctions(object)) {
                Q_ASSERT(v4CodeGenerator.hasError());
                recordError(v4CodeGenerator.error());
                return nullptr;
            }
        }
        document->javaScriptCompilationUnit = v4CodeGenerator.generateCompilationUnit(/*generated unit data*/false);
    }

    // Generate QML compiled type data structures

    QmlIR::QmlUnitGenerator qmlGenerator;
    qmlGenerator.generate(*document, dependencyHasher);

    if (!errors.isEmpty())
        return nullptr;

    return std::move(document->javaScriptCompilationUnit);
}

void QQmlTypeCompiler::recordError(const QV4::CompiledData::Location &location, const QString &description)
{
    QQmlError error;
    error.setLine(qmlConvertSourceCoordinate<quint32, int>(location.line()));
    error.setColumn(qmlConvertSourceCoordinate<quint32, int>(location.column()));
    error.setDescription(description);
    error.setUrl(url());
    errors << error;
}

void QQmlTypeCompiler::recordError(const QQmlJS::DiagnosticMessage &message)
{
    QQmlError error;
    error.setDescription(message.message);
    error.setLine(qmlConvertSourceCoordinate<quint32, int>(message.loc.startLine));
    error.setColumn(qmlConvertSourceCoordinate<quint32, int>(message.loc.startColumn));
    error.setUrl(url());
    errors << error;
}

void QQmlTypeCompiler::recordError(const QQmlError &e)
{
    QQmlError error = e;
    error.setUrl(url());
    errors << error;
}

QString QQmlTypeCompiler::stringAt(int idx) const
{
    return document->stringAt(idx);
}

int QQmlTypeCompiler::registerString(const QString &str)
{
    return document->jsGenerator.registerString(str);
}

int QQmlTypeCompiler::registerConstant(QV4::ReturnedValue v)
{
    return document->jsGenerator.registerConstant(v);
}

const QV4::CompiledData::Unit *QQmlTypeCompiler::qmlUnit() const
{
    return document->javaScriptCompilationUnit->unitData();
}

const QQmlImports *QQmlTypeCompiler::imports() const
{
    return typeData->imports();
}

QVector<QmlIR::Object *> *QQmlTypeCompiler::qmlObjects() const
{
    return &document->objects;
}

QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches()
{
    return &m_propertyCaches;
}

const QQmlPropertyCacheVector *QQmlTypeCompiler::propertyCaches() const
{
    return &m_propertyCaches;
}

QQmlJS::MemoryPool *QQmlTypeCompiler::memoryPool()
{
    return document->jsParserEngine.pool();
}

QStringView QQmlTypeCompiler::newStringRef(const QString &string)
{
    return document->jsParserEngine.newStringRef(string);
}

const QV4::Compiler::StringTableGenerator *QQmlTypeCompiler::stringPool() const
{
    return &document->jsGenerator.stringTable;
}

QString QQmlTypeCompiler::bindingAsString(const QmlIR::Object *object, int scriptIndex) const
{
    return object->bindingAsString(document, scriptIndex);
}

void QQmlTypeCompiler::addImport(const QString &module, const QString &qualifier, QTypeRevision version)
{
    const quint32 moduleIdx = registerString(module);
    const quint32 qualifierIdx = registerString(qualifier);

    for (int i = 0, count = document->imports.size(); i < count; ++i) {
        const QV4::CompiledData::Import *existingImport = document->imports.at(i);
        if (existingImport->type == QV4::CompiledData::Import::ImportLibrary
            && existingImport->uriIndex == moduleIdx
            && existingImport->qualifierIndex == qualifierIdx)
            return;
    }
    auto pool = memoryPool();
    QV4::CompiledData::Import *import = pool->New<QV4::CompiledData::Import>();
    import->type = QV4::CompiledData::Import::ImportLibrary;
    import->version = version;
    import->uriIndex = moduleIdx;
    import->qualifierIndex = qualifierIdx;
    document->imports.append(import);
}

QQmlType QQmlTypeCompiler::qmlTypeForComponent(const QString &inlineComponentName) const
{
    return typeData->qmlType(inlineComponentName);
}

QQmlCompilePass::QQmlCompilePass(QQmlTypeCompiler *typeCompiler)
    : compiler(typeCompiler)
{
}

SignalHandlerResolver::SignalHandlerResolver(QQmlTypeCompiler *typeCompiler)
    : QQmlCompilePass(typeCompiler)
    , typeLoader(typeCompiler->typeLoader())
    , qmlObjects(*typeCompiler->qmlObjects())
    , imports(typeCompiler->imports())
    , customParsers(typeCompiler->customParserCache())
    , propertyCaches(typeCompiler->propertyCaches())
{
}

bool SignalHandlerResolver::resolveSignalHandlerExpressions()
{
    for (int objectIndex = 0; objectIndex < qmlObjects.size(); ++objectIndex) {
        const QmlIR::Object * const obj = qmlObjects.at(objectIndex);
        QQmlPropertyCache::ConstPtr cache = propertyCaches->at(objectIndex);
        if (!cache)
            continue;
        if (QQmlCustomParser *customParser = customParsers.value(obj->inheritedTypeNameIndex)) {
            if (!(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers))
                continue;
        }
        const QString elementName = stringAt(obj->inheritedTypeNameIndex);
        if (!resolveSignalHandlerExpressions(obj, elementName, cache))
            return false;
    }
    return true;
}

bool SignalHandlerResolver::resolveSignalHandlerExpressions(
        const QmlIR::Object *obj, const QString &typeName,
        const QQmlPropertyCache::ConstPtr &propertyCache,
        QQmlPropertyResolver::RevisionCheck checkRevision)
{
    // map from signal name defined in qml itself to list of parameters
    QHash<QString, QStringList> customSignals;

    for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
        const QString bindingPropertyName = stringAt(binding->propertyNameIndex);
        // Attached property?
        const QV4::CompiledData::Binding::Type bindingType = binding->type();
        if (bindingType == QV4::CompiledData::Binding::Type_AttachedProperty) {
            const QmlIR::Object *attachedObj = qmlObjects.at(binding->value.objectIndex);
            auto *typeRef = resolvedType(binding->propertyNameIndex);
            QQmlType type = typeRef ? typeRef->type() : QQmlType();
            if (!type.isValid())
                imports->resolveType(typeLoader, bindingPropertyName, &type, nullptr, nullptr);

            const QMetaObject *attachedType = type.attachedPropertiesType(typeLoader);
            if (!attachedType)
                COMPILE_EXCEPTION(binding, tr("Non-existent attached object"));
            QQmlPropertyCache::ConstPtr cache = QQmlMetaType::propertyCache(attachedType);

            // Ignore revisions of signals on attached objects. They are not unqualified.
            if (!resolveSignalHandlerExpressions(
                        attachedObj, bindingPropertyName, cache,
                        QQmlPropertyResolver::IgnoreRevision)) {
                return false;
            }

            continue;
        }

        QString qPropertyName;
        QString signalName;
        if (auto propertyName =
                    QQmlSignalNames::changedHandlerNameToPropertyName(bindingPropertyName)) {
            qPropertyName = *propertyName;
            signalName = *QQmlSignalNames::changedHandlerNameToSignalName(bindingPropertyName);
        } else {
            signalName = QQmlSignalNames::handlerNameToSignalName(bindingPropertyName)
                                 .value_or(QString());
        }
        if (signalName.isEmpty())
            continue;

        QQmlPropertyResolver resolver(propertyCache);

        bool notInRevision = false;
        const QQmlPropertyData *const signal
                = resolver.signal(signalName, &notInRevision, checkRevision);
        const QQmlPropertyData *const signalPropertyData
                = resolver.property(signalName, /*notInRevision ptr*/nullptr, checkRevision);
        const QQmlPropertyData *const qPropertyData = !qPropertyName.isEmpty()
                ? resolver.property(qPropertyName, nullptr, checkRevision)
                : nullptr;
        QString finalSignalHandlerPropertyName = signalName;
        QV4::CompiledData::Binding::Flag flag
                = QV4::CompiledData::Binding::IsSignalHandlerExpression;

        const bool isPropertyObserver
                = !signalPropertyData && qPropertyData && qPropertyData->notifiesViaBindable();
        if (signal && !(qPropertyData && qPropertyData->isAlias() && isPropertyObserver)) {
            int sigIndex = propertyCache->methodIndexToSignalIndex(signal->coreIndex());
            sigIndex = propertyCache->originalClone(sigIndex);

            bool unnamedParameter = false;

            QList<QByteArray> parameterNames = propertyCache->signalParameterNames(sigIndex);
            for (int i = 0; i < parameterNames.size(); ++i) {
                const QString param = QString::fromUtf8(parameterNames.at(i));
                if (param.isEmpty())
                    unnamedParameter = true;
                else if (unnamedParameter) {
                    COMPILE_EXCEPTION(binding, tr("Signal uses unnamed parameter followed by named parameter."));
                } else if (QV4::Compiler::Codegen::isNameGlobal(param)) {
                    COMPILE_EXCEPTION(binding, tr("Signal parameter \"%1\" hides global variable.").arg(param));
                }
            }
        } else if (isPropertyObserver) {
            finalSignalHandlerPropertyName = qPropertyName;
            flag = QV4::CompiledData::Binding::IsPropertyObserver;
        } else {
            if (notInRevision) {
                // Try assinging it as a property later
                if (signalPropertyData)
                    continue;

                const QString &originalPropertyName = stringAt(binding->propertyNameIndex);

                auto *typeRef = resolvedType(obj->inheritedTypeNameIndex);
                const QQmlType type = typeRef ? typeRef->type() : QQmlType();
                if (type.isValid()) {
                    COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available in %3 %4.%5.")
                                      .arg(typeName).arg(originalPropertyName).arg(type.module())
                                      .arg(type.version().majorVersion())
                                      .arg(type.version().minorVersion()));
                } else {
                    COMPILE_EXCEPTION(binding, tr("\"%1.%2\" is not available due to component versioning.").arg(typeName).arg(originalPropertyName));
                }
            }

            // Try to look up the signal parameter names in the object itself

            // build cache if necessary
            if (customSignals.isEmpty()) {
                for (const QmlIR::Signal *signal = obj->firstSignal(); signal; signal = signal->next) {
                    const QString &signalName = stringAt(signal->nameIndex);
                    customSignals.insert(signalName, signal->parameterStringList(compiler->stringPool()));
                }

                for (const QmlIR::Property *property = obj->firstProperty(); property; property = property->next) {
                    const QString propName = stringAt(property->nameIndex());
                    customSignals.insert(propName, QStringList());
                }
            }

            QHash<QString, QStringList>::ConstIterator entry = customSignals.constFind(signalName);
            if (entry == customSignals.constEnd() && !qPropertyName.isEmpty())
                entry = customSignals.constFind(qPropertyName);

            if (entry == customSignals.constEnd()) {
                // Can't find even a custom signal, then just don't do anything and try
                // keeping the binding as a regular property assignment.
                continue;
            }
        }

        // Binding object to signal means connect the signal to the object's default method.
        if (bindingType == QV4::CompiledData::Binding::Type_Object) {
            binding->setFlag(QV4::CompiledData::Binding::IsSignalHandlerObject);
            continue;
        }

        if (bindingType != QV4::CompiledData::Binding::Type_Script) {
            if (bindingType < QV4::CompiledData::Binding::Type_Script) {
                COMPILE_EXCEPTION(binding, tr("Cannot assign a value to a signal (expecting a script to be run)"));
            } else {
                COMPILE_EXCEPTION(binding, tr("Incorrectly specified signal assignment"));
            }
        }

        binding->propertyNameIndex = compiler->registerString(finalSignalHandlerPropertyName);
        binding->setFlag(flag);
    }
    return true;
}

QQmlEnumTypeResolver::QQmlEnumTypeResolver(QQmlTypeCompiler *typeCompiler)
    : QQmlCompilePass(typeCompiler)
    , qmlObjects(*typeCompiler->qmlObjects())
    , propertyCaches(typeCompiler->propertyCaches())
    , imports(typeCompiler->imports())
{
}

bool QQmlEnumTypeResolver::resolveEnumBindings()
{
    for (int i = 0; i < qmlObjects.size(); ++i) {
        QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(i);
        if (!propertyCache)
            continue;
        const QmlIR::Object *obj = qmlObjects.at(i);

        QQmlPropertyResolver resolver(propertyCache);

        for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
            const QV4::CompiledData::Binding::Flags bindingFlags = binding->flags();
            if (bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerExpression
                    || bindingFlags & QV4::CompiledData::Binding::IsSignalHandlerObject
                    || bindingFlags & QV4::CompiledData::Binding::IsPropertyObserver)
                continue;

            if (binding->type() != QV4::CompiledData::Binding::Type_Script)
                continue;

            const QString propertyName = stringAt(binding->propertyNameIndex);
            bool notInRevision = false;
            const QQmlPropertyData *pd = resolver.property(propertyName, &notInRevision);
            if (!pd || pd->isQList())
                continue;

            if (!pd->isEnum() && pd->propType().id() != QMetaType::Int)
                continue;

            if (!tryQualifiedEnumAssignment(obj, propertyCache, pd, binding))
                return false;
        }
    }

    return true;
}

bool QQmlEnumTypeResolver::assignEnumToBinding(QmlIR::Binding *binding, QStringView, int enumValue, bool)
{
    binding->setType(QV4::CompiledData::Binding::Type_Number);
    binding->value.constantValueIndex = compiler->registerConstant(QV4::Encode((double)enumValue));
//    binding->setNumberValueInternal((double)enumValue);
    binding->setFlag(QV4::CompiledData::Binding::IsResolvedEnum);
    return true;
}

bool QQmlEnumTypeResolver::tryQualifiedEnumAssignment(
        const QmlIR::Object *obj, const QQmlPropertyCache::ConstPtr &propertyCache,
        const QQmlPropertyData *prop, QmlIR::Binding *binding)
{
    bool isIntProp = (prop->propType().id() == QMetaType::Int) && !prop->isEnum();
    if (!prop->isEnum() && !isIntProp)
        return true;

    if (!prop->isWritable()
            && !(binding->hasFlag(QV4::CompiledData::Binding::InitializerForReadOnlyDeclaration))) {
        COMPILE_EXCEPTION(binding, tr("Invalid property assignment: \"%1\" is a read-only property")
                                           .arg(stringAt(binding->propertyNameIndex)));
    }

    Q_ASSERT(binding->type() == QV4::CompiledData::Binding::Type_Script);
    const QString string = compiler->bindingAsString(obj, binding->value.compiledScriptIndex);
    if (!string.constData()->isUpper())
        return true;

    // reject any "complex" expression (even simple arithmetic)
    // we do this by excluding everything that is not part of a
    // valid identifier or a dot
    for (const QChar &c : string)
        if (!(c.isLetterOrNumber() || c == u'.' || c == u'_' || c.isSpace()))
            return true;

    // we support one or two '.' in the enum phrase:
    // * <TypeName>.<EnumValue>
    // * <TypeName>.<ScopedEnumName>.<EnumValue>

    int dot = string.indexOf(QLatin1Char('.'));
    if (dot == -1 || dot == string.size()-1)
        return true;

    int dot2 = string.indexOf(QLatin1Char('.'), dot+1);
    if (dot2 != -1 && dot2 != string.size()-1) {
        if (!string.at(dot+1).isUpper())
            return true;
        if (string.indexOf(QLatin1Char('.'), dot2+1) != -1)
            return true;
    }

    QHashedStringRef typeName(string.constData(), dot);
    const bool isQtObject = (typeName == QLatin1String("Qt"));
    const QStringView scopedEnumName = (dot2 != -1 ? QStringView{string}.mid(dot + 1, dot2 - dot - 1) : QStringView());
    // ### consider supporting scoped enums in Qt namespace
    const QStringView enumValue = QStringView{string}.mid(!isQtObject && dot2 != -1 ? dot2 + 1 : dot + 1);

    if (isIntProp) { // ### C++11 allows enums to be other integral types. Should we support other integral types here?
        // Allow enum assignment to ints.
        bool ok;
        int enumval = evaluateEnum(typeName.toString(), scopedEnumName, enumValue, &ok);
        if (ok) {
            if (!assignEnumToBinding(binding, enumValue, enumval, isQtObject))
                return false;
        }
        return true;
    }
    QQmlType type;
    imports->resolveType(compiler->typeLoader(), typeName, &type, nullptr, nullptr);

    if (!type.isValid() && !isQtObject)
        return true;

    int value = 0;
    bool ok = false;

    auto *tr = resolvedType(obj->inheritedTypeNameIndex);

    // When these two match, we can short cut the search, unless...
    bool useFastPath = type.isValid() && tr && tr->type() == type;
    QMetaProperty mprop;
    QMetaEnum menum;
    if (useFastPath) {
        mprop = propertyCache->firstCppMetaObject()->property(prop->coreIndex());
        menum = mprop.enumerator();
        // ...the enumerator merely comes from a related metaobject, but the enum scope does not match
        // the typename we resolved
        if (!menum.isScoped() && scopedEnumName.isEmpty() && typeName != QString::fromUtf8(menum.scope()))
            useFastPath = false;;
    }
    if (useFastPath) {
        QByteArray enumName = enumValue.toUtf8();
        if (menum.isScoped() && !scopedEnumName.isEmpty() && enumName != scopedEnumName.toUtf8())
            return true;

        if (mprop.isFlagType()) {
            value = menum.keysToValue(enumName.constData(), &ok);
        } else {
            value = menum.keyToValue(enumName.constData(), &ok);
        }
    } else {
        // Otherwise we have to search the whole type
        if (type.isValid()) {
            if (!scopedEnumName.isEmpty()) {
                value = type.scopedEnumValue(
                        compiler->typeLoader(), scopedEnumName, enumValue, &ok);
            } else {
                value = type.enumValue(compiler->typeLoader(), QHashedStringRef(enumValue), &ok);
            }
        } else {
            QByteArray enumName = enumValue.toUtf8();
            const QMetaObject *metaObject = &Qt::staticMetaObject;
            for (int ii = metaObject->enumeratorCount() - 1; !ok && ii >= 0; --ii) {
                QMetaEnum e = metaObject->enumerator(ii);
                value = e.keyToValue(enumName.constData(), &ok);
            }
        }
    }

    if (!ok)
        return true;

    return assignEnumToBinding(binding, enumValue, value, isQtObject);
}

int QQmlEnumTypeResolver::evaluateEnum(const QString &scope, QStringView enumName, QStringView enumValue, bool *ok) const
{
    Q_ASSERT_X(ok, "QQmlEnumTypeResolver::evaluateEnum", "ok must not be a null pointer");
    *ok = false;

    if (scope != QLatin1String("Qt")) {
        QQmlType type;
        imports->resolveType(compiler->typeLoader(), scope, &type, nullptr, nullptr);
        if (!type.isValid())
            return -1;
        if (!enumName.isEmpty())
            return type.scopedEnumValue(compiler->typeLoader(), enumName, enumValue, ok);
        return type.enumValue(
                compiler->typeLoader(),
                QHashedStringRef(enumValue.constData(), enumValue.size()), ok);
    }

    const QMetaObject *mo = &Qt::staticMetaObject;
    int i = mo->enumeratorCount();
    const QByteArray ba = enumValue.toUtf8();
    while (i--) {
        int v = mo->enumerator(i).keyToValue(ba.constData(), ok);
        if (*ok)
            return v;
    }
    return -1;
}

QQmlCustomParserScriptIndexer::QQmlCustomParserScriptIndexer(QQmlTypeCompiler *typeCompiler)
    : QQmlCompilePass(typeCompiler)
    , qmlObjects(*typeCompiler->qmlObjects())
    , customParsers(typeCompiler->customParserCache())
{
}

void QQmlCustomParserScriptIndexer::annotateBindingsWithScriptStrings()
{
    scanObjectRecursively(/*root object*/0);
    for (int i = 0; i < qmlObjects.size(); ++i)
        if (qmlObjects.at(i)->flags & QV4::CompiledData::Object::IsInlineComponentRoot)
            scanObjectRecursively(i);
}

void QQmlCustomParserScriptIndexer::scanObjectRecursively(int objectIndex, bool annotateScriptBindings)
{
    const QmlIR::Object * const obj = qmlObjects.at(objectIndex);
    if (!annotateScriptBindings)
        annotateScriptBindings = customParsers.contains(obj->inheritedTypeNameIndex);
    for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
        switch (binding->type()) {
        case QV4::CompiledData::Binding::Type_Script:
            if (annotateScriptBindings) {
                binding->stringIndex = compiler->registerString(
                        compiler->bindingAsString(obj, binding->value.compiledScriptIndex));
            }
            break;
        case QV4::CompiledData::Binding::Type_Object:
        case QV4::CompiledData::Binding::Type_AttachedProperty:
        case QV4::CompiledData::Binding::Type_GroupProperty:
            scanObjectRecursively(binding->value.objectIndex, annotateScriptBindings);
            break;
        default:
            break;
        }
    }
}

QQmlAliasAnnotator::QQmlAliasAnnotator(QQmlTypeCompiler *typeCompiler)
    : QQmlCompilePass(typeCompiler)
    , qmlObjects(*typeCompiler->qmlObjects())
    , propertyCaches(typeCompiler->propertyCaches())
{
}

void QQmlAliasAnnotator::annotateBindingsToAliases()
{
    for (int i = 0; i < qmlObjects.size(); ++i) {
        QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(i);
        if (!propertyCache)
            continue;

        const QmlIR::Object *obj = qmlObjects.at(i);

        QQmlPropertyResolver resolver(propertyCache);
        const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty();

        for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
            if (!binding->isValueBinding())
                continue;
            bool notInRevision = false;
            const QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(stringAt(binding->propertyNameIndex), &notInRevision) : defaultProperty;
            if (pd && pd->isAlias())
                binding->setFlag(QV4::CompiledData::Binding::IsBindingToAlias);
        }
    }
}

QQmlScriptStringScanner::QQmlScriptStringScanner(QQmlTypeCompiler *typeCompiler)
    : QQmlCompilePass(typeCompiler)
    , qmlObjects(*typeCompiler->qmlObjects())
    , propertyCaches(typeCompiler->propertyCaches())
{

}

void QQmlScriptStringScanner::scan()
{
    const QMetaType scriptStringMetaType = QMetaType::fromType<QQmlScriptString>();
    for (int i = 0; i < qmlObjects.size(); ++i) {
        QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(i);
        if (!propertyCache)
            continue;

        const QmlIR::Object *obj = qmlObjects.at(i);

        QQmlPropertyResolver resolver(propertyCache);
        const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultProperty() : propertyCache->defaultProperty();

        for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
            if (binding->type() != QV4::CompiledData::Binding::Type_Script)
                continue;
            bool notInRevision = false;
            const QQmlPropertyData *pd = binding->propertyNameIndex != quint32(0) ? resolver.property(stringAt(binding->propertyNameIndex), &notInRevision) : defaultProperty;
            if (!pd || pd->propType() != scriptStringMetaType)
                continue;

            QString script = compiler->bindingAsString(obj, binding->value.compiledScriptIndex);
            binding->stringIndex = compiler->registerString(script);
        }
    }
}

template<>
void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::allocateNamedObjects(
        QmlIR::Object *object) const
{
    object->namedObjectsInComponent.allocate(m_compiler->memoryPool(), m_idToObjectIndex);
}

template<>
bool QQmlComponentAndAliasResolver<QQmlTypeCompiler>::markAsComponent(int index) const
{
    m_compiler->qmlObjects()->at(index)->flags |= QV4::CompiledData::Object::IsComponent;
    return true;
}

template<>
void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::setObjectId(int index) const
{
    m_compiler->qmlObjects()->at(index)->id = m_idToObjectIndex.size();
}

template<>
bool QQmlComponentAndAliasResolver<QQmlTypeCompiler>::wrapImplicitComponent(QmlIR::Binding *binding)
{
    QQmlJS::MemoryPool *pool = m_compiler->memoryPool();
    QVector<QmlIR::Object *> *qmlObjects = m_compiler->qmlObjects();

    // emulate "import QML 1.0" and then wrap the component in "QML.Component {}"
    QQmlType componentType = QQmlMetaType::qmlType(
                &QQmlComponent::staticMetaObject, QStringLiteral("QML"),
                QTypeRevision::fromVersion(1, 0));
    Q_ASSERT(componentType.isValid());
    const QString qualifier = QStringLiteral("QML");

    m_compiler->addImport(componentType.module(), qualifier, componentType.version());

    QmlIR::Object *syntheticComponent = pool->New<QmlIR::Object>();
    syntheticComponent->init(
                pool,
                m_compiler->registerString(
                    qualifier + QLatin1Char('.') + componentType.elementName()),
                m_compiler->registerString(QString()), binding->valueLocation);
    syntheticComponent->flags |= QV4::CompiledData::Object::IsComponent;

    if (!m_compiler->resolvedTypes->contains(syntheticComponent->inheritedTypeNameIndex)) {
        auto typeRef = new QV4::ResolvedTypeReference;
        typeRef->setType(componentType);
        typeRef->setVersion(componentType.version());
        m_compiler->resolvedTypes->insert(syntheticComponent->inheritedTypeNameIndex, typeRef);
    }

    qmlObjects->append(syntheticComponent);
    const int componentIndex = qmlObjects->size() - 1;
    // Keep property caches symmetric
    QQmlPropertyCache::ConstPtr componentCache
            = QQmlMetaType::propertyCache(&QQmlComponent::staticMetaObject);
    m_propertyCaches->append(componentCache);

    QmlIR::Binding *syntheticBinding = pool->New<QmlIR::Binding>();
    *syntheticBinding = *binding;

    // The synthetic binding inside Component has no name. It's just "Component { Foo {} }".
    syntheticBinding->propertyNameIndex = 0;

    syntheticBinding->setType(QV4::CompiledData::Binding::Type_Object);
    QString error = syntheticComponent->appendBinding(syntheticBinding, /*isListBinding*/false);
    Q_ASSERT(error.isEmpty());
    Q_UNUSED(error);

    binding->value.objectIndex = componentIndex;

    m_componentRoots.append(componentIndex);
    return true;
}

template<>
void QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveGeneralizedGroupProperty(
        const CompiledObject &component, CompiledBinding *binding)
{
    Q_UNUSED(component);
    // We cannot make it fail here. It might be a custom-parsed property
    const int targetObjectIndex = m_idToObjectIndex.value(binding->propertyNameIndex, -1);
    if (targetObjectIndex != -1)
        m_propertyCaches->set(binding->value.objectIndex, m_propertyCaches->at(targetObjectIndex));
}

/*!
    \internal

    Reorders the alias data within the linked list of \a obj so that
    aliases targeting other aliases on the same object come after their
    targets. This ensures that dependencies are resolved and appended to
    the property cache first, which is necessary because the runtime
    assumes that the property cache position matches the alias table index.

    Only considers same-object dependencies (where the alias's target id
    resolves to \a objectIndex). Cross-object dependencies are handled by
    the multi-pass resolution in resolveAliases().
*/
static bool sortAliasDependencies(
        QQmlTypeCompiler *compiler, QmlIR::Object *obj, int objectIndex,
        const QMap<int, int> &idToObjectIndex, QQmlError *error)
{
    using AliasArray = QVarLengthArray<QmlIR::Alias *, 8>;

    AliasArray ordered;
    ordered.reserve(obj->aliasCount());

    // if the default property is an alias, we need to later update the default property index
    QmlIR::Alias *defaultPropertyAlias = nullptr;
    qsizetype aliasCounter = 0;


    // Collect aliases as nodes in a graph. Non-local ones are already ordered.
    AliasArray nodes;
    for (QmlIR::Alias *a = obj->firstAlias(); a; ++aliasCounter, a = a->next) {
        if (obj->defaultPropertyIsAlias && aliasCounter == obj->indexOfDefaultPropertyOrAlias) {
            defaultPropertyAlias = a;
        }
        const int targetObjIdx = idToObjectIndex.value(a->idIndex(), -1);
        if (targetObjIdx == objectIndex)
            nodes.append(a);
        else
            ordered.append(a);
    }

    // Nothing to sort here.
    if (nodes.isEmpty())
        return true;

    // Collect dependencies as edges between nodes
    QVarLengthArray<qsizetype, 8> edges(nodes.size(), -1);
    for (qsizetype i = 0, end = nodes.size(); i < end; ++i) {
        const QStringView propValue = compiler->stringAt(nodes[i]->propertyNameIndex);
        const int dotIdx = propValue.indexOf(QLatin1Char('.'));
        const QStringView targetName = dotIdx != -1 ? propValue.left(dotIdx) : propValue;
        if (targetName.isEmpty())
            continue;

        for (qsizetype j = 0; j < end; ++j) {
            if (j != i && compiler->stringAt(nodes[j]->nameIndex()) == targetName) {
                edges[i] = j;
                break;
            }
        }
    }

    // Simple DFS-based topological sort
    for (qsizetype i = 0, end = nodes.size(); i < end; ++i) {
        // Skip already inserted nodes
        if (nodes[i] == nullptr)
            continue;

        // Follow the dependency chain to find the root.
        QVarLengthArray<qsizetype, 8> chain;
        for (qsizetype j = edges[i]; j != -1 && nodes[j]; j = edges[j]) {
            if (!chain.contains(j)) {
                chain.append(j);
                continue;
            }

            *error = qQmlCompileError(
                    nodes[j]->location,
                    QQmlComponentAndAliasResolverBase::tr("Cyclic alias"));
            return false;
        }

        // Emit in reverse (dependency first)
        for (qsizetype k = chain.size() - 1; k >= 0; --k)
            ordered.append(std::exchange(nodes[chain[k]], nullptr));
        ordered.append(std::exchange(nodes[i], nullptr));
    }

    // Apply the ordering in the IR
    obj->setFirstAlias(ordered[0]);
    if (ordered[0] == defaultPropertyAlias)
        obj->indexOfDefaultPropertyOrAlias = 0;
    for (qsizetype i = 0, end = ordered.size() - 1; i < end; ++i) {
        ordered[i]->next = ordered[i + 1];
        if (ordered[i] == defaultPropertyAlias)
            obj->indexOfDefaultPropertyOrAlias = i;
    }
    ordered.last()->next = nullptr;

    return true;
}

/*!
    \internal

    Attempts to resolve a "deep alias" — an alias whose sub-property path
    goes through an inline component binding or through another alias.
    For example: \c{alias foo: target.groupProp.innerProp}

    Searches the target object's bindings and aliases for \a property, then
    looks up \a subProperty on the bound/aliased object's property cache.

    On success, updates \a propIdx with the resolved value-type index and
    returns \c true.
*/
static bool resolveDeepAlias(
        QQmlTypeCompiler *compiler, const QmlIR::Object *targetObject,
        const QmlIR::Object &component, QStringView property, QStringView subProperty,
        QQmlPropertyIndex &propIdx, const QQmlPropertyCacheVector *propertyCaches,
        const QMap<int, int> &idToObjectIndex,
        const QSet<const QV4::CompiledData::Alias *> &resolvedAliases)
{
    for (auto it = targetObject->bindingsBegin(), end = targetObject->bindingsEnd();
            it != end; ++it) {
        auto binding = *it;
        if (compiler->stringAt(binding.propertyNameIndex) != property)
            continue;
        const auto &cache = propertyCaches->at(binding.value.objectIndex);
        if (!cache)
            continue;
        QQmlPropertyResolver resolver(cache);
        const QQmlPropertyData *pd = resolver.property(subProperty.toString());
        if (!pd)
            continue;
        propIdx = QQmlPropertyIndex(propIdx.coreIndex(), pd->coreIndex());
        return true;
    }

    for (auto it = targetObject->aliasesBegin(), end = targetObject->aliasesEnd();
            it != end; ++it) {
        auto innerAlias = *it;
        if (compiler->stringAt(innerAlias.nameIndex()) != property)
            continue;
        // After resolution, idIndex() contains the runtime object id
        // (set by setTargetObjectId), not the string table index.
        int innerObjectIndex;
        if (resolvedAliases.contains(&(*it)))
            innerObjectIndex = objectForId(compiler, component, innerAlias.targetObjectId());
        else
            innerObjectIndex = idToObjectIndex.value(innerAlias.idIndex(), -1);
        if (innerObjectIndex == -1)
            continue;
        const auto &cache = propertyCaches->at(innerObjectIndex);
        if (!cache)
            continue;
        QQmlPropertyResolver resolver(cache);
        const QQmlPropertyData *pd = resolver.property(subProperty.toString());
        if (!pd)
            continue;
        propIdx = QQmlPropertyIndex(propIdx.coreIndex(), pd->coreIndex());
        return true;
    }

    return false;
}

template<>
typename QQmlComponentAndAliasResolver<QQmlTypeCompiler>::AliasResolutionResult
QQmlComponentAndAliasResolver<QQmlTypeCompiler>::resolveAliasesInObject(
        const CompiledObject &component, int objectIndex,
        QQmlPropertyCacheAliasCreator<QQmlTypeCompiler> *aliasCacheCreator, QQmlError *error)
{
    // TODO: This method should not modify the aliases themselves. Rather, all information
    //       needed for handling them later should be stored in the property cache.
    //       Some of the information calculated here could be calculated already at compile time.
    //       See QTBUG-136572.

    Q_UNUSED(component);

    QmlIR::Object *obj = m_compiler->objectAt(objectIndex);
    if (!obj->aliasCount())
        return AllAliasesResolved;

    // Reorder aliases so that same-object dependencies come before their
    // dependents. This ensures the property cache ordering matches the
    // alias table, which the runtime relies on.
    if (!sortAliasDependencies(m_compiler, obj, objectIndex, m_idToObjectIndex, error))
        return NoAliasResolved;

    int aliasIndex = 0;
    int numSkippedAliases = 0;
    bool hasUnresolvedLocalAliases = false;

    for (QmlIR::Alias *alias = obj->firstAlias(); alias; alias = alias->next, ++aliasIndex) {
        if (resolvedAliases.contains(alias)) {
            ++numSkippedAliases;
            continue;
        }


        const int idIndex = alias->idIndex();
        const int targetObjectIndex = m_idToObjectIndex.value(idIndex, -1);
        if (targetObjectIndex == -1) {
            *error = qQmlCompileError(
                    alias->referenceLocation,
                    QQmlComponentAndAliasResolverBase::tr("Invalid alias reference. Unable to find id \"%1\"").arg(stringAt(idIndex)));
            break;
        }

        const QmlIR::Object *targetObject = m_compiler->objectAt(targetObjectIndex);
        Q_ASSERT(targetObject->id >= 0);
        alias->setTargetObjectId(targetObject->id);
        alias->setIsAliasToLocalAlias(false);

        const QString aliasPropertyValue = stringAt(alias->propertyNameIndex);

        QStringView property;
        QStringView subProperty;

        const int propertySeparator = aliasPropertyValue.indexOf(QLatin1Char('.'));
        if (propertySeparator != -1) {
            property = QStringView{aliasPropertyValue}.left(propertySeparator);
            subProperty = QStringView{aliasPropertyValue}.mid(propertySeparator + 1);
        } else
            property = QStringView(aliasPropertyValue);

        QQmlPropertyIndex propIdx;

        if (property.isEmpty()) {
            alias->setFlag(QV4::CompiledData::Alias::AliasPointsToPointerObject);
        } else {
            QQmlPropertyCache::ConstPtr targetCache = m_propertyCaches->at(targetObjectIndex);
            if (!targetCache) {
                *error = qQmlCompileError(
                        alias->referenceLocation,
                        QQmlComponentAndAliasResolverBase::tr("Invalid alias target location: %1").arg(property.toString()));
                break;
            }

            QQmlPropertyResolver resolver(targetCache);

            const QQmlPropertyData *targetProperty = resolver.property(
                    property.toString(), nullptr, QQmlPropertyResolver::IgnoreRevision);

            // If it's an alias that we haven't resolved yet, try again later.
            if (!targetProperty) {
                bool aliasPointsToOtherAlias = false;
                int localAliasIndex = 0;
                auto targetAlias = targetObject->aliasesBegin();
                for (const auto end = targetObject->aliasesEnd(); targetAlias != end;
                        ++targetAlias, ++localAliasIndex) {
                    if (stringAt(targetAlias->nameIndex()) == property) {
                        aliasPointsToOtherAlias = true;
                        break;
                    }
                }
                if (aliasPointsToOtherAlias) {
                    if (targetObjectIndex != objectIndex) {
                        // Don't continue, yet. We need to respect the order of objects.
                        alias->setIdIndex(idIndex);
                        return aliasIndex == numSkippedAliases
                                ? NoAliasResolved
                                : SomeAliasesResolved;
                    }

                    if (resolvedAliases.contains(targetAlias)) {
                        // Target already resolved. We can set the alias right away.
                        alias->localAliasIndex = localAliasIndex;
                        alias->setIsAliasToLocalAlias(true);
                        if (!appendAliasToPropertyCache(
                                    &component, alias, objectIndex, aliasIndex, -1,
                                    aliasCacheCreator, error)) {
                            break;
                        }
                        continue;
                    }

                    // Target isn't resolved yet, but it's in the same object.
                    // Continue with the other aliases.
                    alias->setIdIndex(idIndex);
                    // Try again later and resolve the target alias first.
                    ++numSkippedAliases;
                    hasUnresolvedLocalAliases = true;
                    continue;
                }
            }

            if (!targetProperty || targetProperty->coreIndex() > 0x0000FFFF) {
                *error = qQmlCompileError(
                        alias->referenceLocation,
                        QQmlComponentAndAliasResolverBase::tr("Invalid alias target location: %1").arg(property.toString()));
                break;
            }

            propIdx = QQmlPropertyIndex(targetProperty->coreIndex());

            if (!subProperty.isEmpty()) {
                const QMetaObject *valueTypeMetaObject = QQmlMetaType::metaObjectForValueType(targetProperty->propType());
                if (!valueTypeMetaObject) {
                    // could be a deep alias
                    bool isDeepAlias = subProperty.at(0).isLower();
                    if (isDeepAlias) {
                        isDeepAlias = resolveDeepAlias(
                                m_compiler, targetObject, component,
                                property, subProperty,
                                propIdx, m_propertyCaches, m_idToObjectIndex,
                                resolvedAliases);
                    }
                    if (!isDeepAlias) {
                        *error = qQmlCompileError(
                                alias->referenceLocation,
                                QQmlComponentAndAliasResolverBase::tr("Invalid alias target location: %1").arg(subProperty.toString()));
                        break;
                    }
                } else {

                    int valueTypeIndex =
                            valueTypeMetaObject->indexOfProperty(subProperty.toString().toUtf8().constData());
                    if (valueTypeIndex == -1) {
                        *error = qQmlCompileError(
                                alias->referenceLocation,
                                QQmlComponentAndAliasResolverBase::tr("Invalid alias target location: %1").arg(subProperty.toString()));
                        break;
                    }
                    Q_ASSERT(valueTypeIndex <= 0x0000FFFF);

                    propIdx = QQmlPropertyIndex(propIdx.coreIndex(), valueTypeIndex);
                }
            } else {
                if (targetProperty->isQObject())
                    alias->setFlag(QV4::CompiledData::Alias::AliasPointsToPointerObject);
            }
        }

        if (!appendAliasToPropertyCache(
                    &component, alias, objectIndex, aliasIndex, propIdx.toEncoded(),
                    aliasCacheCreator, error)) {
            break;
        }
    }

    if (numSkippedAliases == aliasIndex)
        return NoAliasResolved;

    if (aliasIndex == obj->aliasCount() && !hasUnresolvedLocalAliases)
        return AllAliasesResolved;

    return SomeAliasesResolved;
}

QQmlDeferredAndCustomParserBindingScanner::QQmlDeferredAndCustomParserBindingScanner(QQmlTypeCompiler *typeCompiler)
    : QQmlCompilePass(typeCompiler)
    , qmlObjects(typeCompiler->qmlObjects())
    , propertyCaches(typeCompiler->propertyCaches())
    , customParsers(typeCompiler->customParserCache())
    , _seenObjectWithId(false)
{
}

bool QQmlDeferredAndCustomParserBindingScanner::scanObject()
{
    for (int i = 0; i < qmlObjects->size(); ++i) {
        if ((qmlObjects->at(i)->flags & QV4::CompiledData::Object::IsInlineComponentRoot)
                && !scanObject(i, ScopeDeferred::False)) {
            return false;
        }
    }
    return scanObject(/*root object*/0, ScopeDeferred::False);
}

bool QQmlDeferredAndCustomParserBindingScanner::scanObject(
        int objectIndex, ScopeDeferred scopeDeferred)
{
    using namespace QV4::CompiledData;

    QmlIR::Object *obj = qmlObjects->at(objectIndex);
    if (obj->idNameIndex != 0)
        _seenObjectWithId = true;

    if (obj->flags & Object::IsComponent) {
        Q_ASSERT(obj->bindingCount() == 1);
        const Binding *componentBinding = obj->firstBinding();
        Q_ASSERT(componentBinding->type() == Binding::Type_Object);
        // Components are separate from their surrounding scope. They cannot be deferred.
        return scanObject(componentBinding->value.objectIndex, ScopeDeferred::False);
    }

    QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(objectIndex);
    if (!propertyCache)
        return true;

    QString defaultPropertyName;
    const QQmlPropertyData *defaultProperty = nullptr;
    if (obj->indexOfDefaultPropertyOrAlias != -1) {
        const QQmlPropertyCache *cache = propertyCache->parent().data();
        defaultPropertyName = cache->defaultPropertyName();
        defaultProperty = cache->defaultProperty();
    } else {
        defaultPropertyName = propertyCache->defaultPropertyName();
        defaultProperty = propertyCache->defaultProperty();
    }

    QQmlCustomParser *customParser = customParsers.value(obj->inheritedTypeNameIndex);

    QQmlPropertyResolver propertyResolver(propertyCache);

    QStringList deferredPropertyNames;
    QStringList immediatePropertyNames;
    {
        const QMetaObject *mo = propertyCache->firstCppMetaObject();
        const int deferredNamesIndex = mo->indexOfClassInfo("DeferredPropertyNames");
        const int immediateNamesIndex = mo->indexOfClassInfo("ImmediatePropertyNames");
        if (deferredNamesIndex != -1) {
            if (immediateNamesIndex != -1) {
                COMPILE_EXCEPTION(obj, tr("You cannot define both DeferredPropertyNames and "
                                          "ImmediatePropertyNames on the same type."));
            }
            const QMetaClassInfo classInfo = mo->classInfo(deferredNamesIndex);
            deferredPropertyNames = QString::fromUtf8(classInfo.value()).split(u',');
        } else if (immediateNamesIndex != -1) {
            const QMetaClassInfo classInfo = mo->classInfo(immediateNamesIndex);
            immediatePropertyNames = QString::fromUtf8(classInfo.value()).split(u',');

            // If the property contains an empty string, all properties shall be deferred.
            if (immediatePropertyNames.isEmpty())
                immediatePropertyNames.append(QString());
        }
    }

    for (QmlIR::Binding *binding = obj->firstBinding(); binding; binding = binding->next) {
        QString name = stringAt(binding->propertyNameIndex);

        if (customParser) {
            if (binding->type() == Binding::Type_AttachedProperty) {
                if (customParser->flags() & QQmlCustomParser::AcceptsAttachedProperties) {
                    binding->setFlag(Binding::IsCustomParserBinding);
                    obj->flags |= Object::HasCustomParserBindings;
                    continue;
                }
            } else if (QQmlSignalNames::isHandlerName(name)
                       && !(customParser->flags() & QQmlCustomParser::AcceptsSignalHandlers)) {
                obj->flags |= Object::HasCustomParserBindings;
                binding->setFlag(Binding::IsCustomParserBinding);
                continue;
            }
        }

        const bool hasPropertyData = [&]() {
            if (name.isEmpty()) {
                name = defaultPropertyName;
                if (defaultProperty)
                    return true;
            } else if (name.constData()->isUpper()) {
                // Upper case names cannot be custom-parsed unless they are attached properties
                // and the custom parser explicitly accepts them. See above for that case.
                return false;
            } else {
                bool notInRevision = false;
                if (propertyResolver.property(
                            name, &notInRevision, QQmlPropertyResolver::CheckRevision)) {
                    return true;
                }
            }

            if (!customParser)
                return false;

            const Binding::Flags bindingFlags = binding->flags();
            if (bindingFlags & Binding::IsSignalHandlerExpression
                        || bindingFlags & Binding::IsSignalHandlerObject
                        || bindingFlags & Binding::IsPropertyObserver) {
                // These signal handlers cannot be custom-parsed. We have already established
                // that the signal exists.
                return false;
            }

            // If the property isn't found, we may want to custom-parse the binding.
            obj->flags |= Object::HasCustomParserBindings;
            binding->setFlag(Binding::IsCustomParserBinding);
            return false;
        }();

        bool seenSubObjectWithId = false;
        bool isExternal = false;
        if (binding->type() >= Binding::Type_Object) {
            const bool isOwnProperty = hasPropertyData || binding->isAttachedProperty();
            isExternal = !isOwnProperty && binding->isGroupProperty();
            if (isOwnProperty || isExternal) {
                qSwap(_seenObjectWithId, seenSubObjectWithId);
                const bool subObjectValid = scanObject(
                            binding->value.objectIndex,
                            (isExternal || scopeDeferred == ScopeDeferred::True)
                                ? ScopeDeferred::True
                                : ScopeDeferred::False);
                qSwap(_seenObjectWithId, seenSubObjectWithId);
                if (!subObjectValid)
                    return false;
                _seenObjectWithId |= seenSubObjectWithId;
            }
        }

        bool isDeferred = false;
        if (!immediatePropertyNames.isEmpty() && !immediatePropertyNames.contains(name)) {
            if (seenSubObjectWithId) {
                COMPILE_EXCEPTION(binding, tr("You cannot assign an id to an object assigned "
                                              "to a deferred property."));
            }
            if (isExternal || !disableInternalDeferredProperties())
                isDeferred = true;
        } else if (!deferredPropertyNames.isEmpty() && deferredPropertyNames.contains(name)) {
            if (!seenSubObjectWithId && binding->type() != Binding::Type_GroupProperty) {
                if (isExternal || !disableInternalDeferredProperties())
                    isDeferred = true;
            }
        }

        if (binding->type() >= Binding::Type_Object) {
            if (isExternal && !isDeferred && !customParser) {
                COMPILE_EXCEPTION(
                            binding, tr("Cannot assign to non-existent property \"%1\"").arg(name));
            }
        }

        if (isDeferred) {
            binding->setFlag(Binding::IsDeferredBinding);
            obj->flags |= Object::HasDeferredBindings;
        }
    }

    return true;
}

QQmlDefaultPropertyMerger::QQmlDefaultPropertyMerger(QQmlTypeCompiler *typeCompiler)
    : QQmlCompilePass(typeCompiler)
    , qmlObjects(*typeCompiler->qmlObjects())
    , propertyCaches(typeCompiler->propertyCaches())
{

}

void QQmlDefaultPropertyMerger::mergeDefaultProperties()
{
    for (int i = 0; i < qmlObjects.size(); ++i)
        mergeDefaultProperties(i);
}

void QQmlDefaultPropertyMerger::mergeDefaultProperties(int objectIndex)
{
    QQmlPropertyCache::ConstPtr propertyCache = propertyCaches->at(objectIndex);
    if (!propertyCache)
        return;

    QmlIR::Object *object = qmlObjects.at(objectIndex);

    QString defaultProperty = object->indexOfDefaultPropertyOrAlias != -1 ? propertyCache->parent()->defaultPropertyName() : propertyCache->defaultPropertyName();
    QmlIR::Binding *bindingsToReinsert = nullptr;
    QmlIR::Binding *tail = nullptr;

    QmlIR::Binding *previousBinding = nullptr;
    QmlIR::Binding *binding = object->firstBinding();
    while (binding) {
        if (binding->propertyNameIndex == quint32(0) || stringAt(binding->propertyNameIndex) != defaultProperty) {
            previousBinding = binding;
            binding = binding->next;
            continue;
        }

        QmlIR::Binding *toReinsert = binding;
        binding = object->unlinkBinding(previousBinding, binding);

        if (!tail) {
            bindingsToReinsert = toReinsert;
            tail = toReinsert;
        } else {
            tail->next = toReinsert;
            tail = tail->next;
        }
        tail->next = nullptr;
    }

    binding = bindingsToReinsert;
    while (binding) {
        QmlIR::Binding *toReinsert = binding;
        binding = binding->next;
        object->insertSorted(toReinsert);
    }
}

QT_END_NAMESPACE
