// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
// Qt-Security score:significant

#include "qqmljstyperesolver_p.h"

#include "qqmljsimporter_p.h"
#include "qqmljsimportvisitor_p.h"
#include "qqmljslogger_p.h"
#include "qqmljsutils_p.h"
#include <private/qv4value_p.h>

#include <private/qduplicatetracker_p.h>

#include <QtCore/qloggingcategory.h>

QT_BEGIN_NAMESPACE

using namespace Qt::StringLiterals;

Q_STATIC_LOGGING_CATEGORY(lcTypeResolver, "qt.qml.compiler.typeresolver", QtInfoMsg);

static inline void assertExtension(const QQmlJSScope::ConstPtr &type, QLatin1String extension)
{
    Q_ASSERT(type);
    Q_ASSERT(type->extensionType().scope->internalName() == extension);
    Q_ASSERT(type->extensionIsJavaScript());
}

QQmlJSTypeResolver::QQmlJSTypeResolver(QQmlJSImporter *importer)
    : m_pool(std::make_unique<QQmlJSRegisterContentPool>())
    , m_imports(importer->builtinInternalNames())
{
    const QQmlJSImporter::ImportedTypes &builtinTypes = m_imports;

    m_voidType = builtinTypes.type(u"void"_s).scope;
    assertExtension(m_voidType, "undefined"_L1);

    m_nullType = builtinTypes.type(u"std::nullptr_t"_s).scope;
    Q_ASSERT(m_nullType);

    m_realType = builtinTypes.type(u"double"_s).scope;
    assertExtension(m_realType, "Number"_L1);

    m_floatType = builtinTypes.type(u"float"_s).scope;
    assertExtension(m_floatType, "Number"_L1);

    m_int8Type = builtinTypes.type(u"qint8"_s).scope;
    assertExtension(m_int8Type, "Number"_L1);

    m_uint8Type = builtinTypes.type(u"quint8"_s).scope;
    assertExtension(m_uint8Type, "Number"_L1);

    m_int16Type = builtinTypes.type(u"short"_s).scope;
    assertExtension(m_int16Type, "Number"_L1);

    m_uint16Type = builtinTypes.type(u"ushort"_s).scope;
    assertExtension(m_uint16Type, "Number"_L1);

    m_int32Type = builtinTypes.type(u"int"_s).scope;
    assertExtension(m_int32Type, "Number"_L1);

    m_uint32Type = builtinTypes.type(u"uint"_s).scope;
    assertExtension(m_uint32Type, "Number"_L1);

    m_int64Type = builtinTypes.type(u"qlonglong"_s).scope;
    Q_ASSERT(m_int64Type);

    m_uint64Type = builtinTypes.type(u"qulonglong"_s).scope;
    Q_ASSERT(m_uint64Type);

    m_sizeType = builtinTypes.type(u"qsizetype"_s).scope;
    assertExtension(m_sizeType, "Number"_L1);

    // qsizetype is either a 32bit or a 64bit signed integer. We don't want to special-case it.
    Q_ASSERT(m_sizeType == m_int32Type || m_sizeType == m_int64Type);

    m_boolType = builtinTypes.type(u"bool"_s).scope;
    assertExtension(m_boolType, "Boolean"_L1);

    m_stringType = builtinTypes.type(u"QString"_s).scope;
    assertExtension(m_stringType, "String"_L1);

    m_stringListType = builtinTypes.type(u"QStringList"_s).scope;
    assertExtension(m_stringListType, "Array"_L1);

    m_byteArrayType = builtinTypes.type(u"QByteArray"_s).scope;
    assertExtension(m_byteArrayType, "ArrayBuffer"_L1);

    m_urlType = builtinTypes.type(u"QUrl"_s).scope;
    assertExtension(m_urlType, "URL"_L1);

    m_dateTimeType = builtinTypes.type(u"QDateTime"_s).scope;
    assertExtension(m_dateTimeType, "Date"_L1);

    m_dateType = builtinTypes.type(u"QDate"_s).scope;
    Q_ASSERT(m_dateType);

    m_timeType = builtinTypes.type(u"QTime"_s).scope;
    Q_ASSERT(m_timeType);

    m_regexpType = builtinTypes.type(u"QRegularExpression"_s).scope;
    Q_ASSERT(m_regexpType);

    m_variantListType = builtinTypes.type(u"QVariantList"_s).scope;
    assertExtension(m_variantListType, "Array"_L1);

    m_variantMapType = builtinTypes.type(u"QVariantMap"_s).scope;
    Q_ASSERT(m_variantMapType);
    m_varType = builtinTypes.type(u"QVariant"_s).scope;
    Q_ASSERT(m_varType);

    m_qmlPropertyMapType = builtinTypes.type(u"QQmlPropertyMap"_s).scope;
    Q_ASSERT(m_qmlPropertyMapType);

    m_jsValueType = builtinTypes.type(u"QJSValue"_s).scope;
    Q_ASSERT(m_jsValueType);

    m_qObjectType = builtinTypes.type(u"QObject"_s).scope;
    assertExtension(m_qObjectType, "Object"_L1);

    m_qObjectListType = builtinTypes.type(u"QObjectList"_s).scope;
    assertExtension(m_qObjectListType, "Array"_L1);

    m_qQmlScriptStringType = builtinTypes.type(u"QQmlScriptString"_s).scope;
    Q_ASSERT(m_qQmlScriptStringType);

    m_functionType = builtinTypes.type(u"function"_s).scope;
    Q_ASSERT(m_functionType);

    m_numberPrototype = builtinTypes.type(u"NumberPrototype"_s).scope;
    Q_ASSERT(m_numberPrototype);

    m_arrayPrototype = builtinTypes.type(u"ArrayPrototype"_s).scope;
    Q_ASSERT(m_arrayPrototype);

    m_listPropertyType = m_qObjectType->listType();
    Q_ASSERT(m_listPropertyType->internalName() == u"QQmlListProperty<QObject>"_s);
    Q_ASSERT(m_listPropertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence);
    Q_ASSERT(m_listPropertyType->elementTypeName() == u"QObject"_s);
    assertExtension(m_listPropertyType, "Array"_L1);

    QQmlJSScope::Ptr emptyType = QQmlJSScope::create();
    emptyType->setAccessSemantics(QQmlJSScope::AccessSemantics::None);
    m_emptyType = emptyType;

    QQmlJSScope::Ptr jsPrimitiveType = QQmlJSScope::create();
    jsPrimitiveType->setInternalName(u"QJSPrimitiveValue"_s);
    jsPrimitiveType->setFilePath(u"qjsprimitivevalue.h"_s);
    jsPrimitiveType->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
    m_jsPrimitiveType = jsPrimitiveType;

    QQmlJSScope::Ptr metaObjectType = QQmlJSScope::create();
    metaObjectType->setInternalName(u"const QMetaObject"_s);
    metaObjectType->setFilePath(u"qmetaobject.h"_s);
    metaObjectType->setAccessSemantics(QQmlJSScope::AccessSemantics::Reference);
    m_metaObjectType = metaObjectType;

    m_jsGlobalObject = importer->jsGlobalObject();

    QQmlJSScope::Ptr forInIteratorPtr = QQmlJSScope::create();
    forInIteratorPtr->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
    forInIteratorPtr->setFilePath(u"qjslist.h"_s);
    forInIteratorPtr->setInternalName(u"QJSListForInIterator::Ptr"_s);
    m_forInIteratorPtr = forInIteratorPtr;

    QQmlJSScope::Ptr forOfIteratorPtr = QQmlJSScope::create();
    forOfIteratorPtr->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
    forOfIteratorPtr->setFilePath(u"qjslist.h"_s);
    forOfIteratorPtr->setInternalName(u"QJSListForOfIterator::Ptr"_s);
    m_forOfIteratorPtr = forOfIteratorPtr;

    // We use this as scope type quite often, and it should always be the same scope type.
    m_jsGlobalObjectContent = m_pool->createType(
            m_jsGlobalObject, QQmlJSRegisterContent::InvalidLookupIndex,
            QQmlJSRegisterContent::ScopeObject);
}

/*!
    \internal

    Initializes the type resolver. As part of that initialization, makes \a
    visitor traverse the program when given.
*/
void QQmlJSTypeResolver::init(QQmlJSImportVisitor *visitor, QQmlJS::AST::Node *program)
{
    m_logger = visitor->logger();

    m_objectsById.clear();
    m_objectsByLocation.clear();
    m_imports.clear();
    m_signalHandlers.clear();

    if (program)
        program->accept(visitor);

    m_objectsById = visitor->addressableScopes();
    m_objectsByLocation = visitor->scopesBylocation();
    m_signalHandlers = visitor->signalHandlers();
    m_imports = visitor->imports();
    m_seenModuleQualifiers = visitor->seenModuleQualifiers();
}

QQmlJSScope::ConstPtr QQmlJSTypeResolver::mathObject() const
{
    return jsGlobalObject()->property(u"Math"_s).type();
}

QQmlJSScope::ConstPtr QQmlJSTypeResolver::consoleObject() const
{
    return jsGlobalObject()->property(u"console"_s).type();
}

QQmlJSScope::ConstPtr
QQmlJSTypeResolver::scopeForLocation(const QV4::CompiledData::Location &location) const
{
    // #if required for standalone DOM compilation against Qt 6.2
    qCDebug(lcTypeResolver()).nospace()
            << "looking for object at " << location.line() << ':' << location.column();
    return m_objectsByLocation[location];
}

QQmlJSScope::ConstPtr QQmlJSTypeResolver::typeFromAST(QQmlJS::AST::Type *type) const
{
    const QString typeId = QmlIR::IRBuilder::asString(type->typeId);
    if (!type->typeArgument)
        return m_imports.type(typeId).scope;
    if (typeId == u"list"_s) {
        if (const QQmlJSScope::ConstPtr typeArgument = typeForName(type->typeArgument->toString()))
            return typeArgument->listType();
    }
    return QQmlJSScope::ConstPtr();
}

QQmlJSScope::ConstPtr QQmlJSTypeResolver::typeForConst(QV4::ReturnedValue rv) const
{
    QV4::Value value = QV4::Value::fromReturnedValue(rv);
    if (value.isUndefined())
        return voidType();

    if (value.isInt32())
        return int32Type();

    if (value.isBoolean())
        return boolType();

    if (value.isDouble())
        return realType();

    if (value.isNull())
        return nullType();

    if (value.isEmpty())
        return emptyType();

    return {};
}

QQmlJSRegisterContent
QQmlJSTypeResolver::typeForBinaryOperation(QSOperator::Op oper, QQmlJSRegisterContent left,
                                           QQmlJSRegisterContent right) const
{
    Q_ASSERT(left.isValid());
    Q_ASSERT(right.isValid());

    switch (oper) {
    case QSOperator::Op::Equal:
    case QSOperator::Op::NotEqual:
    case QSOperator::Op::StrictEqual:
    case QSOperator::Op::StrictNotEqual:
    case QSOperator::Op::Lt:
    case QSOperator::Op::Gt:
    case QSOperator::Op::Ge:
    case QSOperator::Op::In:
    case QSOperator::Op::Le:
        return operationType(boolType());
    case QSOperator::Op::BitAnd:
    case QSOperator::Op::BitOr:
    case QSOperator::Op::BitXor:
    case QSOperator::Op::LShift:
    case QSOperator::Op::RShift:
        return operationType(int32Type());
    case QSOperator::Op::URShift:
        return operationType(uint32Type());
    case QSOperator::Op::Add: {
        const auto leftContents = left.containedType();
        const auto rightContents = right.containedType();
        if (leftContents == stringType() || rightContents == stringType())
            return operationType(stringType());

        const QQmlJSScope::ConstPtr result = merge(leftContents, rightContents);
        if (result == boolType())
            return operationType(int32Type());
        if (isNumeric(result))
            return operationType(realType());

        return operationType(jsPrimitiveType());
    }
    case QSOperator::Op::Sub:
    case QSOperator::Op::Mul:
    case QSOperator::Op::Exp: {
        const QQmlJSScope::ConstPtr result = merge(left.containedType(), right.containedType());
        return operationType(result == boolType() ? int32Type() : realType());
    }
    case QSOperator::Op::Div:
    case QSOperator::Op::Mod:
        return operationType(realType());
    case QSOperator::Op::As:
        return operationType(right.containedType());
    default:
        break;
    }

    return operationType(merge(left.containedType(), right.containedType()));
}

QQmlJSRegisterContent QQmlJSTypeResolver::typeForArithmeticUnaryOperation(
        UnaryOperator op, QQmlJSRegisterContent operand) const
{
    switch (op) {
    case UnaryOperator::Not:
        return operationType(boolType());
    case UnaryOperator::Complement:
        return operationType(int32Type());
    case UnaryOperator::Plus:
        if (isIntegral(operand))
            return operationType(operand.containedType());
        Q_FALLTHROUGH();
    default:
        if (operand.containedType() == boolType())
            return operationType(int32Type());
        break;
    }

    return operationType(realType());
}

bool QQmlJSTypeResolver::isPrimitive(QQmlJSRegisterContent type) const
{
    return isPrimitive(type.containedType());
}

bool QQmlJSTypeResolver::isNumeric(QQmlJSRegisterContent type) const
{
    return isNumeric(type.containedType());
}

bool QQmlJSTypeResolver::isIntegral(QQmlJSRegisterContent type) const
{
    return isIntegral(type.containedType());
}

bool QQmlJSTypeResolver::isIntegral(const QQmlJSScope::ConstPtr &type) const
{
    return isSignedInteger(type) || isUnsignedInteger(type);
}

bool QQmlJSTypeResolver::isPrimitive(const QQmlJSScope::ConstPtr &type) const
{
    return (isNumeric(type) && type != m_int64Type && type != m_uint64Type)
            || type == m_boolType   || type == m_voidType || type == m_nullType
            || type == m_stringType || type == m_jsPrimitiveType;
}

bool QQmlJSTypeResolver::isNumeric(const QQmlJSScope::ConstPtr &type) const
{
    if (!type) // should this be a precondition instead?
        return false;

    if (type->scopeType() == QQmlJSScope::ScopeType::EnumScope)
        return true;

    if (type == m_realType)
        return true;
    if (type == m_floatType)
        return true;
    if (type == m_int8Type)
        return true;
    if (type == m_uint8Type)
        return true;
    if (type == m_int16Type)
        return true;
    if (type == m_uint16Type)
        return true;
    if (type == m_int32Type)
        return true;
    if (type == m_uint32Type)
        return true;
    if (type == m_int64Type)
        return true;
    if (type == m_uint64Type)
        return true;
    // sizetype is covered by one of the above cases (int32 or int64)
    // booleans are not numeric

    // the number prototype is numeric
    if (type == m_numberPrototype)
        return true;

    // and types directly inheriting from it, notably number, are also
    // numeric
    if (type->baseType() == m_numberPrototype)
        return true;

    // it isn't possible (for users) to derive from m_numberPrototpye,
    // so we know the list above is exhaustive / we don't need to go up
    // further in the inheritance chain

    return false;
}

bool QQmlJSTypeResolver::isSignedInteger(const QQmlJSScope::ConstPtr &type) const
{
    return type == m_int8Type || type == m_int16Type
            || type == m_int32Type || type == m_int64Type;
}

bool QQmlJSTypeResolver::isUnsignedInteger(const QQmlJSScope::ConstPtr &type) const
{
    return type == m_uint8Type || type == m_uint16Type
            || type == m_uint32Type || type == m_uint64Type;
}

bool QQmlJSTypeResolver::isNativeArrayIndex(const QQmlJSScope::ConstPtr &type) const
{
    return type == m_uint8Type || type == m_int8Type || type == m_uint16Type || type == m_int16Type
            || type == m_uint32Type || type == m_int32Type;
}

QQmlJSScope::ConstPtr QQmlJSTypeResolver::containedTypeForName(const QString &name) const
{
    QQmlJSScope::ConstPtr type = typeForName(name);

    if (!type || type->isSingleton() || type->isScript())
        return type;

    switch (type->accessSemantics()) {
    case QQmlJSScope::AccessSemantics::Reference:
        if (const auto attached = type->attachedType())
            return genericType(attached) ? attached : QQmlJSScope::ConstPtr();
        return metaObjectType();
    case QQmlJSScope::AccessSemantics::None:
        return metaObjectType();
    case QQmlJSScope::AccessSemantics::Sequence:
    case QQmlJSScope::AccessSemantics::Value:
        return canAddressValueTypes() ?  metaObjectType() : QQmlJSScope::ConstPtr();
    }

    Q_UNREACHABLE_RETURN(QQmlJSScope::ConstPtr());
}

QQmlJSRegisterContent QQmlJSTypeResolver::registerContentForName(
        const QString &name, QQmlJSRegisterContent scopeType) const
{
    QQmlJSScope::ConstPtr type = typeForName(name);
    if (!type)
        return QQmlJSRegisterContent();

    if (type->isSingleton()) {
        return m_pool->createType(
                type, QQmlJSRegisterContent::InvalidLookupIndex,
                QQmlJSRegisterContent::Singleton, scopeType);
    }

    if (type->isScript()) {
        return m_pool->createType(
                type, QQmlJSRegisterContent::InvalidLookupIndex,
                QQmlJSRegisterContent::Script, scopeType);
    }

    const QQmlJSRegisterContent namedType = m_pool->createType(
            type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::TypeByName,
            scopeType);

    if (const auto attached = type->attachedType()) {
        if (!genericType(attached)) {
            m_logger->log(u"Cannot resolve generic base of attached %1"_s.arg(
                                  attached->internalName()),
                          qmlCompiler, attached->sourceLocation());
            return {};
        } else if (type->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
            m_logger->log(u"Cannot retrieve attached object for non-reference type %1"_s.arg(
                                  type->internalName()),
                          qmlCompiler, type->sourceLocation());
            return {};
        } else {
            // We don't know yet whether we need the attached or the plain object. In direct
            // mode, we will figure this out using the scope type and access any enums of the
            // plain type directly. In indirect mode, we can use enum lookups.
            return m_pool->createType(
                        attached, QQmlJSRegisterContent::InvalidLookupIndex,
                        QQmlJSRegisterContent::Attachment, namedType);
        }
    }

    switch (type->accessSemantics()) {
    case QQmlJSScope::AccessSemantics::None:
    case QQmlJSScope::AccessSemantics::Reference:
        // A plain reference to a non-singleton, non-attached type.
        // We may still need the plain type reference for enum lookups,
        // Store it as QMetaObject.
        // This only works with namespaces and object types.
        return m_pool->createType(
                metaObjectType(), QQmlJSRegisterContent::InvalidLookupIndex,
                QQmlJSRegisterContent::MetaType, namedType);
    case QQmlJSScope::AccessSemantics::Sequence:
    case QQmlJSScope::AccessSemantics::Value:
        if (scopeType.isImportNamespace() || canAddressValueTypes()) {
            return m_pool->createType(
                    metaObjectType(), QQmlJSRegisterContent::InvalidLookupIndex,
                    QQmlJSRegisterContent::MetaType, namedType);
        }
        // Else this is not actually a type reference. You cannot get the metaobject
        // of a value type in QML and sequences don't even have metaobjects.
        break;
    }

    return QQmlJSRegisterContent();
}

QQmlJSRegisterContent QQmlJSTypeResolver::original(QQmlJSRegisterContent type) const
{
    QQmlJSRegisterContent result = type.original();
    return result.isNull() ? type : result;
}

QQmlJSScope::ConstPtr QQmlJSTypeResolver::originalContainedType(
        QQmlJSRegisterContent container) const
{
    return original(container).containedType();
}

bool QQmlJSTypeResolver::adjustTrackedType(
        QQmlJSRegisterContent tracked, const QQmlJSScope::ConstPtr &conversion) const
{
    if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
        return true;

    QQmlJSScope::ConstPtr contained = tracked.containedType();
    QQmlJSScope::ConstPtr result = conversion;

    // Do not adjust to the JavaScript extension of the original type. Rather keep the original
    // type in that case.
    QQmlJSUtils::searchBaseAndExtensionTypes(
            contained, [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind kind) {
        if (kind != QQmlJSScope::ExtensionJavaScript || scope != result)
            return false;
        result = contained;
        return true;
    });


    // If we cannot convert to the new type without the help of e.g. lookupResultMetaType(),
    // we better not change the type.
    if (!canPrimitivelyConvertFromTo(contained, result)
        && !canPopulate(result, contained, nullptr)
        && !selectConstructor(result, contained, nullptr).isValid()) {
        return false;
    }

    m_pool->adjustType(tracked, result);
    return true;
}

bool QQmlJSTypeResolver::adjustTrackedType(
        QQmlJSRegisterContent tracked, QQmlJSRegisterContent conversion) const
{
    return adjustTrackedType(tracked, conversion.containedType());
}

bool QQmlJSTypeResolver::adjustTrackedType(
        QQmlJSRegisterContent tracked, const QList<QQmlJSRegisterContent> &conversions) const
{
    if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
        return true;

    QQmlJSScope::ConstPtr result;
    for (QQmlJSRegisterContent type : conversions)
        result = merge(type.containedType(), result);

    return adjustTrackedType(tracked, result);
}

void QQmlJSTypeResolver::adjustOriginalType(
        QQmlJSRegisterContent tracked, const QQmlJSScope::ConstPtr &conversion) const
{
    if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
        return;

    m_pool->generalizeType(tracked, conversion);
}

void QQmlJSTypeResolver::generalizeType(QQmlJSRegisterContent type) const
{
    if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
        return;

    for (QQmlJSRegisterContent orig = type; !orig.isNull(); orig = orig.original()) {
        if (!orig.shadowed().isValid())
            m_pool->generalizeType(orig, genericType(orig.containedType()));
    }
}

bool QQmlJSTypeResolver::canConvertFromTo(const QQmlJSScope::ConstPtr &from,
                                          const QQmlJSScope::ConstPtr &to) const
{
    if (canPrimitivelyConvertFromTo(from, to)
            || canPopulate(to, from, nullptr)
            || selectConstructor(to, from, nullptr).isValid()) {
        return true;
    }

    // ### need a generic solution for custom cpp types:
    // if (from->m_hasBoolOverload && equals(to, boolType))
    //    return true;

    // All of these types have QString conversions that require a certain format
    // TODO: Actually verify these strings or deprecate them.
    //       Some of those type are builtins or should be builtins. We should add code for them
    //       in QQmlJSCodeGenerator::conversion().
    if (from == m_stringType && !to.isNull()) {
        const QString toTypeName = to->internalName();
        if (toTypeName == u"QPoint"_s || toTypeName == u"QPointF"_s
                || toTypeName == u"QSize"_s || toTypeName == u"QSizeF"_s
                || toTypeName == u"QRect"_s || toTypeName == u"QRectF"_s) {
            return true;
        }
    }

    return false;
}

bool QQmlJSTypeResolver::canConvertFromTo(QQmlJSRegisterContent from,
                                          QQmlJSRegisterContent to) const
{
    return canConvertFromTo(from.containedType(), to.containedType());
}

static QQmlJSRegisterContent::ContentVariant mergeVariants(QQmlJSRegisterContent::ContentVariant a,
                                                           QQmlJSRegisterContent::ContentVariant b)
{
    return (a == b) ? a : QQmlJSRegisterContent::Unknown;
}

QQmlJSRegisterContent QQmlJSTypeResolver::merge(
        QQmlJSRegisterContent a, QQmlJSRegisterContent b) const
{
    Q_ASSERT(a != b);

    // We cannot easily provide an operator< for QQmlJSRegisterContent.
    // Therefore we use qHash and operator== here to deduplicate. That's somewhat inefficient.
    QSet<QQmlJSRegisterContent> origins;

    QQmlJSRegisterContent aResultScope;
    if (a.isConversion()) {
        const auto aOrigins = a.conversionOrigins();
        for (const auto &aOrigin : aOrigins)
            origins.insert(aOrigin);
        aResultScope = a.conversionResultScope();
    } else {
        origins.insert(a);
        aResultScope = a.scope();
    }

    QQmlJSRegisterContent bResultScope;
    if (b.isConversion()) {
        const auto bOrigins = b.conversionOrigins();
        for (const auto &bOrigin : bOrigins)
            origins.insert(bOrigin);
        bResultScope = b.conversionResultScope();
    } else {
        origins.insert(b);
        bResultScope = b.scope();
    }

    const auto mergeScopes = [&](QQmlJSRegisterContent a, QQmlJSRegisterContent b) {
        // If they are the same, we don't want to re-track them.
        // We want the repetition on type mismatches to converge.
        return (a == b) ? a : merge(a, b);
    };

    return m_pool->createConversion(
            origins.values(),
            merge(a.containedType(), b.containedType()),
            mergeScopes(aResultScope, bResultScope),
            mergeVariants(a.variant(), b.variant()),
            mergeScopes(a.scope(), b.scope()));
}

QQmlJSScope::ConstPtr QQmlJSTypeResolver::merge(const QQmlJSScope::ConstPtr &a,
                                                const QQmlJSScope::ConstPtr &b) const
{
    if (a.isNull())
        return b;

    if (b.isNull())
        return a;

    const auto baseOrExtension
            = [](const QQmlJSScope::ConstPtr &a, const QQmlJSScope::ConstPtr &b) {
        QQmlJSScope::ConstPtr found;
        QQmlJSUtils::searchBaseAndExtensionTypes(
                a, [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind kind) {
            switch (kind) {
            case QQmlJSScope::NotExtension:
                // If b inherits scope, then scope is a common base type of a and b
                if (b->inherits(scope)) {
                    found = scope;
                    return true;
                }
                break;
            case QQmlJSScope::ExtensionJavaScript:
                // Merging a type with its JavaScript extension produces the type.
                // Giving the JavaScript extension as type to be read means we expect any type
                // that fulfills the given JavaScript interface
                if (scope == b) {
                    found = a;
                    return true;
                }
                break;
            case QQmlJSScope::ExtensionType:
            case QQmlJSScope::ExtensionNamespace:
                break;
            }
            return false;
        });
        return found;
    };

    if (a == b)
        return a;

    if (a == jsValueType() || a == varType())
        return a;
    if (b == jsValueType() || b == varType())
        return b;

    const auto isInt32Compatible = [&](const QQmlJSScope::ConstPtr &type) {
        return (isIntegral(type)
                    && type != uint32Type()
                    && type != int64Type()
                    && type != uint64Type())
                || type == boolType();
    };

    if (isInt32Compatible(a) && isInt32Compatible(b))
        return int32Type();

    const auto isUInt32Compatible = [&](const QQmlJSScope::ConstPtr &type) {
        return (isUnsignedInteger(type) && type != uint64Type()) || type == boolType();
    };

    if (isUInt32Compatible(a) && isUInt32Compatible(b))
        return uint32Type();

    if (isNumeric(a) && isNumeric(b))
        return realType();

    if (isPrimitive(a) && isPrimitive(b))
        return jsPrimitiveType();

    if (const auto base = baseOrExtension(a, b))
        return base;

    if (const auto base = baseOrExtension(b, a))
        return base;

    if ((a == nullType() || a == boolType()) && b->isReferenceType())
        return b;

    if ((b == nullType() || b == boolType()) && a->isReferenceType())
        return a;

    return varType();
}

bool QQmlJSTypeResolver::canHold(
        const QQmlJSScope::ConstPtr &container, const QQmlJSScope::ConstPtr &contained) const
{
    if (container == contained || container == m_varType || container == m_jsValueType)
        return true;

    if (container == m_jsPrimitiveType)
        return isPrimitive(contained);

    if (container == m_variantListType)
        return contained->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence;

    if (container == m_qObjectListType || container == m_listPropertyType) {
        if (contained->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence)
            return false;
        if (QQmlJSScope::ConstPtr element = contained->elementType())
            return element->isReferenceType();
        return false;
    }

    if (QQmlJSUtils::searchBaseAndExtensionTypes(
                container, [&](const QQmlJSScope::ConstPtr &base) {
        return base == contained;
    })) {
        return true;
    }

    if (container->isReferenceType()) {
        if (QQmlJSUtils::searchBaseAndExtensionTypes(
                    contained, [&](const QQmlJSScope::ConstPtr &base) {
            return base == container;
        })) {
            return true;
        }
    }

    return false;
}


bool QQmlJSTypeResolver::canHoldUndefined(QQmlJSRegisterContent content) const
{
    const auto canBeUndefined = [this](const QQmlJSScope::ConstPtr &type) {
        return type == m_voidType || type == m_varType
                || type == m_jsValueType || type == m_jsPrimitiveType;
    };

    if (!canBeUndefined(content.containedType()))
        return false;

    if (!content.isConversion())
        return true;

    const auto origins = content.conversionOrigins();
    for (const auto &origin : origins) {
        if (canBeUndefined(originalContainedType(origin)))
            return true;
    }

    return false;
}

bool QQmlJSTypeResolver::isOptionalType(QQmlJSRegisterContent content) const
{
    if (!content.isConversion())
        return false;

    const auto origins = content.conversionOrigins();
    if (origins.length() != 2)
        return false;

    // Conversion origins are always adjusted to the conversion result. None of them will be void.
    // Therefore, retrieve the originals first.

    return original(origins[0]).contains(m_voidType) || original(origins[1]).contains(m_voidType);
}

QQmlJSRegisterContent QQmlJSTypeResolver::extractNonVoidFromOptionalType(
        QQmlJSRegisterContent content) const
{
    if (!isOptionalType(content))
        return QQmlJSRegisterContent();

    // Conversion origins are always adjusted to the conversion result. None of them will be void.
    // Therefore, retrieve the originals first.

    auto origins = content.conversionOrigins();
    std::transform(origins.cbegin(), origins.cend(), origins.begin(),
                   [this](QQmlJSRegisterContent content) {return original(content);});
    const QQmlJSRegisterContent result = origins[0].contains(m_voidType)
            ? origins[1]
            : origins[0];

    // The result may still be undefined. You can write "undefined ?? undefined ?? 1"
    return result;
}

QQmlJSScope::ConstPtr QQmlJSTypeResolver::genericType(
        const QQmlJSScope::ConstPtr &type,
        ComponentIsGeneric allowComponent) const
{
    if (type->isScript())
        return m_jsValueType;

    if (type == m_metaObjectType)
        return m_metaObjectType;

    if (type->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
        QString unresolvedBaseTypeName;
        for (auto base = type; base;) {
            // QObject and QQmlComponent are the two required base types.
            // Any QML type system has to define those, or use the ones from builtins.
            // As QQmlComponent is derived from QObject, we can restrict ourselves to the latter.
            // This results in less if'ery when retrieving a QObject* from somewhere and deciding
            // what it is.
            if (base->internalName() == u"QObject"_s) {
                return base;
            } else if (allowComponent == ComponentIsGeneric::Yes
                       && base->internalName() == u"QQmlComponent"_s) {
                return base;
            }

            if (auto baseBase = base->baseType()) {
                base = baseBase;
            } else {
                unresolvedBaseTypeName = base->baseTypeName();
                break;
            }
        }

        // Reference types that are not QObject or QQmlComponent are likely JavaScript objects.
        // We don't want to deal with those, but m_jsValueType is the best generic option.
        if (type->filePath().isEmpty())
            return m_jsValueType;

        m_logger->log(u"Object type %1 is not derived from QObject or QQmlComponent. "
                      "You may need to fully qualify all names in C++ so that moc can see them. "
                      "You may also need to add qt_extract_metatypes(<target containing %2>)."_s
                      .arg(type->internalName(), unresolvedBaseTypeName),
                      qmlCompiler, type->sourceLocation());

        // If it does have a filePath, it's some C++ type which we haven't fully resolved.
        return m_jsValueType;
    }

    if (type->isListProperty())
        return m_listPropertyType;

    if (type->scopeType() == QQmlSA::ScopeType::EnumScope)
        return type->baseType();

    if (isPrimitive(type)) {
        // If the filePath is set, the type is storable and we can just return it.
        if (!type->filePath().isEmpty())
            return type;

        // If the type is JavaScript's 'number' type, it's not directly storable, but still
        // primitive. We use C++ 'double' then.
        if (isNumeric(type))
            return m_realType;

        // Otherwise we use QJSPrimitiveValue.
        // TODO: JavaScript's 'string' and 'boolean' could be special-cased here.
        return m_jsPrimitiveType;
    }

    for (const QQmlJSScope::ConstPtr &builtin : {
                 m_realType, m_floatType, m_int8Type, m_uint8Type, m_int16Type, m_uint16Type,
                 m_int32Type, m_uint32Type, m_int64Type, m_uint64Type, m_boolType, m_stringType,
                 m_stringListType, m_byteArrayType, m_urlType, m_dateTimeType, m_dateType,
                 m_timeType, m_variantListType, m_variantMapType, m_varType, m_jsValueType,
                 m_jsPrimitiveType, m_listPropertyType, m_qObjectType, m_qObjectListType,
                 m_metaObjectType, m_forInIteratorPtr, m_forOfIteratorPtr }) {
        if (type == builtin || type == builtin->listType())
            return type;
    }

    return m_varType;
}

/*!
 * \internal
 * The type of a JavaScript literal value appearing in script code
 */
QQmlJSRegisterContent QQmlJSTypeResolver::literalType(const QQmlJSScope::ConstPtr &type) const
{
    return m_pool->createType(
            type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Literal);
}

/*!
 * \internal
 * The type of the result of a JavaScript operation
 */
QQmlJSRegisterContent QQmlJSTypeResolver::operationType(const QQmlJSScope::ConstPtr &type) const
{
    return m_pool->createType(
            type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Operation);
}

/*!
 * \internal
 * A type named explicitly, for example in "as"-casts or as function annotation.
 */
QQmlJSRegisterContent QQmlJSTypeResolver::namedType(const QQmlJSScope::ConstPtr &type) const
{
    return m_pool->createType(
            type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::TypeByName);
}

QQmlJSRegisterContent QQmlJSTypeResolver::syntheticType(const QQmlJSScope::ConstPtr &type) const
{
    return m_pool->createType(
            type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Unknown);
}

static QQmlJSRegisterContent::ContentVariant scopeContentVariant(QQmlJSScope::ExtensionKind mode,
                                                                 bool isMethod)
{
    switch (mode) {
    case QQmlJSScope::NotExtension:
    case QQmlJSScope::ExtensionType:
    case QQmlJSScope::ExtensionJavaScript:
        return isMethod
                ? QQmlJSRegisterContent::Method
                : QQmlJSRegisterContent::Property;
    case QQmlJSScope::ExtensionNamespace:
        break;
    }
    Q_UNREACHABLE_RETURN(QQmlJSRegisterContent::Unknown);
}

static bool isRevisionAllowed(int memberRevision, const QQmlJSScope::ConstPtr &scope)
{
    Q_ASSERT(scope->isComposite());
    const QTypeRevision revision = QTypeRevision::fromEncodedVersion(memberRevision);

    // If the memberRevision is either invalid or 0.0, then everything is allowed.
    if (!revision.isValid() || revision == QTypeRevision::zero())
        return true;

    const QTypeRevision typeRevision = QQmlJSScope::nonCompositeBaseRevision(
                {scope->baseType(), scope->baseTypeRevision()});

    // If the revision is not valid, we haven't found a non-composite base type.
    // There is nothing we can say about the property then.
    return typeRevision.isValid() && typeRevision >= revision;
}

QQmlJSScope::ConstPtr QQmlJSTypeResolver::resolveParentProperty(
        const QString &name, const QQmlJSScope::ConstPtr &base,
        const QQmlJSScope::ConstPtr &propType) const
{
    if (m_parentMode != UseDocumentParent || name != base->parentPropertyName())
        return propType;

    const QQmlJSScope::ConstPtr baseParent = base->parentScope();
    if (!baseParent || !baseParent->inherits(propType))
        return propType;

    const QString defaultPropertyName = baseParent->defaultPropertyName();
    if (defaultPropertyName.isEmpty()) // no reason to search for bindings
        return propType;

    const QList<QQmlJSMetaPropertyBinding> defaultPropBindings
            = baseParent->propertyBindings(defaultPropertyName);
    for (const QQmlJSMetaPropertyBinding &binding : defaultPropBindings) {
        if (binding.bindingType() == QQmlSA::BindingType::Object && binding.objectType() == base)
            return baseParent;
    }

    return propType;
}

/*!
 * \internal
 *
 * Retrieves the type of whatever \a name signifies in the given \a scope.
 * \a name can be an ID, a property of the scope, a singleton, an attachment,
 * a plain type reference or a JavaScript global.
 *
 * TODO: The lookup is actually wrong. We cannot really retrieve JavaScript
 *       globals here because any runtime-inserted context property would
 *       override them. We still do because we don't have a better solution for
 *       identifying e.g. the console object, yet.
 *
 * \a options tells us whether to consider components as bound. If components
 * are bound we can retrieve objects identified by ID in outer contexts.
 *
 * TODO: This is also wrong because we should alternate scopes and contexts when
 *       traveling the scope/context hierarchy. Currently we have IDs from any
 *       context override all scope properties if components are considered
 *       bound. This is mostly because we don't care about outer scopes at all;
 *       so we cannot determine with certainty whether an ID from a far outer
 *       context is overridden by a property of a near outer scope. To
 *       complicate this further, user context properties can also be inserted
 *       in outer contexts at run time, shadowing names in further removed outer
 *       scopes and contexts. What we need to do is determine where exactly what
 *       kind of property can show up and defend against that with additional
 *       pragmas.
 *
 * Note: It probably takes at least 3 nested bound components in one document to
 *       trigger the misbehavior.
 */
QQmlJSScope::ConstPtr QQmlJSTypeResolver::scopedType(
        const QQmlJSScope::ConstPtr &scope, const QString &name,
        QQmlJSScopesByIdOptions options) const
{
    QQmlJSScopesById::CertainCallback<QQmlJSScope::ConstPtr> identified;
    if (m_objectsById.possibleScopes(name, scope, options, identified)
            != QQmlJSScopesById::Success::Yes) {
        // Could not determine component boundaries
        return {};
    }

    if (identified.result) {
        // Found a definite match
        return identified.result;
    }

    if (QQmlJSScope::ConstPtr base = QQmlJSScope::findCurrentQMLScope(scope)) {
        QQmlJSScope::ConstPtr result;
        if (QQmlJSUtils::searchBaseAndExtensionTypes(
                    base, [&](const QQmlJSScope::ConstPtr &found, QQmlJSScope::ExtensionKind mode) {
            if (mode == QQmlJSScope::ExtensionNamespace) // no use for it here
                return false;

            if (found->hasOwnProperty(name)) {
                const QQmlJSMetaProperty prop = found->ownProperty(name);
                if (!isRevisionAllowed(prop.revision(), scope))
                    return false;

                result = resolveParentProperty(name, base, prop.type());
                return true;
            }

            if (found->hasOwnMethod(name)) {
                const auto methods = found->ownMethods(name);
                for (const auto &method : methods) {
                    if (isRevisionAllowed(method.revision(), scope)) {
                        result = jsValueType();
                        return true;
                    }
                }
            }

            return false;
        })) {
            return result;
        }
    }

    if (QQmlJSScope::ConstPtr result = containedTypeForName(name))
        return result;

    if (m_jsGlobalObject->hasProperty(name))
        return m_jsGlobalObject->property(name).type();

    if (m_jsGlobalObject->hasMethod(name))
        return jsValueType();

    return {};
}

/*!
 * \internal
 *
 * Same as the other scopedType method, but accepts a QQmlJSRegisterContent and
 * also returns one. This way you not only get the type, but also the content
 * variant and various meta info.
 */
QQmlJSRegisterContent QQmlJSTypeResolver::scopedType(QQmlJSRegisterContent scope,
                                                     const QString &name, int lookupIndex,
                                                     QQmlJSScopesByIdOptions options) const
{
    const QQmlJSScope::ConstPtr contained = scope.containedType();

    QQmlJSScopesById::CertainCallback<QQmlJSScope::ConstPtr> identified;
    if (m_objectsById.possibleScopes(name, contained, options, identified)
            != QQmlJSScopesById::Success::Yes) {
        // Could not determine component boundaries
        return {};
    }

    if (identified.result) {
        // Found a definite match
        return m_pool->createType(
                identified.result, lookupIndex, QQmlJSRegisterContent::ObjectById, scope);
    }

    if (QQmlJSScope::ConstPtr base = QQmlJSScope::findCurrentQMLScope(contained)) {
        QQmlJSRegisterContent result;
        if (QQmlJSUtils::searchBaseAndExtensionTypes(
                    base, [&](const QQmlJSScope::ConstPtr &found, QQmlJSScope::ExtensionKind mode) {
            if (mode == QQmlJSScope::ExtensionNamespace) // no use for it here
                return false;

            const QQmlJSRegisterContent resultScope = mode == QQmlJSScope::NotExtension
                    ? scope
                    : extensionType(found, scope);

            if (found->hasOwnProperty(name)) {
                QQmlJSMetaProperty prop = found->ownProperty(name);
                if (!isRevisionAllowed(prop.revision(), contained))
                    return false;

                prop.setType(resolveParentProperty(name, base, prop.type()));
                result = m_pool->createProperty(
                        prop, QQmlJSRegisterContent::InvalidLookupIndex, lookupIndex,
                        scopeContentVariant(mode, false), resultScope);
                return true;
            }

            if (found->hasOwnMethod(name)) {
                auto methods = found->ownMethods(name);
                for (auto it = methods.begin(); it != methods.end();) {
                    if (!isRevisionAllowed(it->revision(), contained))
                        it = methods.erase(it);
                    else
                        ++it;
                }
                if (methods.isEmpty())
                    return false;
                result = m_pool->createMethod(
                        methods, jsValueType(), scopeContentVariant(mode, true), resultScope);
                return true;
            }

            // Unqualified enums are not allowed
            return false;
        })) {
            return result;
        }
    }

    QQmlJSRegisterContent result = registerContentForName(name, scope);

    if (result.isValid())
        return result;

    if (m_jsGlobalObject->hasProperty(name)) {
        return m_pool->createProperty(
                m_jsGlobalObject->property(name), QQmlJSRegisterContent::InvalidLookupIndex,
                lookupIndex, QQmlJSRegisterContent::Property, m_jsGlobalObjectContent);
    } else if (m_jsGlobalObject->hasMethod(name)) {
        return m_pool->createMethod(
                m_jsGlobalObject->methods(name), jsValueType(),
                QQmlJSRegisterContent::Property, m_jsGlobalObjectContent);
    }

    return {};
}

/*!
 * \fn QQmlJSScope::ConstPtr typeForId(const QQmlJSScope::ConstPtr &scope, const QString &name, QQmlJSScopesByIdOptions options) const
 *
 * \internal
 *
 * Same as scopedType(), but assumes that the \a name is an ID and only searches
 * the context.
 *
 * TODO: This is just as wrong as scopedType() in that it disregards both scope
 *       properties overriding context properties and run time context
 *       properties.
 */

bool QQmlJSTypeResolver::checkEnums(
        QQmlJSRegisterContent scope, const QString &name,
        QQmlJSRegisterContent *result) const
{
    // You can't have lower case enum names in QML, even if we know the enums here.
    if (name.isEmpty() || !name.at(0).isUpper())
        return false;

    const auto enums = scope.containedType()->ownEnumerations();
    for (const auto &enumeration : enums) {
        if ((enumeration.isScoped() || enumeration.isQml()) && enumeration.name() == name) {
            *result = m_pool->createEnumeration(
                    enumeration, QString(),
                    QQmlJSRegisterContent::Enum,
                    scope);
            return true;
        }

        if ((!enumeration.isScoped() || enumeration.isQml()
             || !scope.containedType()->enforcesScopedEnums()) && enumeration.hasKey(name)) {
            *result = m_pool->createEnumeration(
                    enumeration, name,
                    QQmlJSRegisterContent::Enum,
                    scope);
            return true;
        }
    }

    return false;
}

bool QQmlJSTypeResolver::canPopulate(
        const QQmlJSScope::ConstPtr &type, const QQmlJSScope::ConstPtr &passedArgumentType,
        bool *isExtension) const
{
    // TODO: We could allow QVariantMap and QVariantHash to be populated, but that needs extra
    //       code in the code generator.

    if (type.isNull()
            || canHold(passedArgumentType, type)
            || isPrimitive(passedArgumentType)
            || type->accessSemantics() != QQmlJSScope::AccessSemantics::Value
            || !type->isStructured()) {
        return false;
    }

    if (isExtension)
        *isExtension = !type->extensionType().scope.isNull();

    return true;
}

QQmlJSMetaMethod QQmlJSTypeResolver::selectConstructor(
        const QQmlJSScope::ConstPtr &type, const QQmlJSScope::ConstPtr &passedArgumentType,
        bool *isExtension) const
{
    // If the "from" type can hold the target type, we should not try to coerce
    // it to any constructor argument.
    if (type.isNull()
            || canHold(passedArgumentType, type)
            || type->accessSemantics() != QQmlJSScope::AccessSemantics::Value
            || !type->isCreatable()) {
        return QQmlJSMetaMethod();
    }

    auto doSelectConstructor = [&](const QQmlJSScope::ConstPtr &type) {
        QQmlJSMetaMethod candidate;

        const auto ownMethods = type->ownMethods();
        for (const QQmlJSMetaMethod &method : ownMethods) {
            if (!method.isConstructor())
                continue;

            const auto index = method.constructorIndex();
            Q_ASSERT(index != QQmlJSMetaMethod::RelativeFunctionIndex::Invalid);

            const auto methodArguments = method.parameters();
            if (methodArguments.size() != 1)
                continue;

            const QQmlJSScope::ConstPtr methodArgumentType = methodArguments[0].type();

            if (passedArgumentType == methodArgumentType)
                return method;

            // Do not select further ctors here. We don't want to do multi-step construction as that
            // is confusing and easily leads to infinite recursion.
            if (!candidate.isValid()
                && canPrimitivelyConvertFromTo(passedArgumentType, methodArgumentType)) {
                candidate = method;
            }
        }

        return candidate;
    };

    if (QQmlJSScope::ConstPtr extension = type->extensionType().scope) {
        const QQmlJSMetaMethod ctor = doSelectConstructor(extension);
        if (ctor.isValid()) {
            if (isExtension)
                *isExtension = true;
            return ctor;
        }
    }

    if (isExtension)
        *isExtension = false;

    return doSelectConstructor(type);
}

bool QQmlJSTypeResolver::areEquivalentLists(
        const QQmlJSScope::ConstPtr &a, const QQmlJSScope::ConstPtr &b) const
{
    const QQmlJSScope::ConstPtr equivalentLists[2][2] = {
        { m_stringListType, m_stringType->listType() },
        { m_variantListType, m_varType->listType() }
    };

    for (const auto eq : equivalentLists) {
        if ((a == eq[0] && b == eq[1]) || (a == eq[1] && b == eq[0]))
            return true;
    }

    return false;
}

bool QQmlJSTypeResolver::isTriviallyCopyable(const QQmlJSScope::ConstPtr &type) const
{
    // pointers are trivially copyable
    if (type->isReferenceType())
        return true;

    // Enum values are trivially copyable
    if (type->scopeType() == QQmlSA::ScopeType::EnumScope)
        return true;

    for (const QQmlJSScope::ConstPtr &trivial : {
            m_nullType, m_voidType,
            m_boolType, m_metaObjectType,
            m_realType, m_floatType,
            m_int8Type, m_uint8Type,
            m_int16Type, m_uint16Type,
            m_int32Type, m_uint32Type,
            m_int64Type, m_uint64Type }) {
        if (type == trivial)
            return true;
    }

    return false;
}

bool QQmlJSTypeResolver::inherits(const QQmlJSScope::ConstPtr &derived, const QQmlJSScope::ConstPtr &base) const
{
    const bool matchByName = !base->isComposite();
    for (QQmlJSScope::ConstPtr derivedBase = derived; derivedBase;
            derivedBase = derivedBase->baseType()) {
        if (derivedBase == base)
            return true;
        if (matchByName
                && !derivedBase->isComposite()
                && derivedBase->internalName() == base->internalName()) {
            return true;
        }
    }
    return false;
}

bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo(
        const QQmlJSScope::ConstPtr &from, const QQmlJSScope::ConstPtr &to) const
{
    if (from == to)
        return true;
    if (from == m_varType || to == m_varType)
        return true;
    if (from == m_jsValueType || to == m_jsValueType)
        return true;
    if (to == m_qQmlScriptStringType)
        return true;
    if (isNumeric(from) && isNumeric(to))
        return true;
    // We can convert everything to bool.
    if (to == m_boolType)
        return true;

    if (from->accessSemantics() == QQmlJSScope::AccessSemantics::Reference
            && to == m_stringType) {
        return true;
    }

    // Yes, our String has number constructors.
    if (isNumeric(from) && to == m_stringType)
        return true;

    // We can convert strings to numbers, but not to enums
    if (from == m_stringType && isNumeric(to))
        return to->scopeType() != QQmlJSScope::ScopeType::EnumScope;

    // We can always convert between strings and urls.
    if ((from == m_stringType && to == m_urlType)
            || (from == m_urlType && to == m_stringType)) {
        return true;
    }

    // We can always convert between strings and byte arrays.
    if ((from == m_stringType && to == m_byteArrayType)
            || (from == m_byteArrayType && to == m_stringType)) {
        return true;
    }

    if (to == m_voidType)
        return true;

    if (to.isNull())
        return from == m_voidType;

    const auto types = { m_dateTimeType, m_dateType, m_timeType, m_stringType };
    for (const auto &originType : types) {
        if (from != originType)
            continue;

        for (const auto &targetType : types) {
            if (to == targetType)
                return true;
        }

        if (to == m_realType)
            return true;

        break;
    }

    if (from == m_nullType && to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
        return true;

    if (from == m_jsPrimitiveType) {
        // You can cast any primitive to a nullptr
        return isPrimitive(to) || to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference;
    }

    if (to == m_jsPrimitiveType)
        return isPrimitive(from);

    const bool matchByName = !to->isComposite();
    Q_ASSERT(!matchByName || !to->internalName().isEmpty());
    if (QQmlJSUtils::searchBaseAndExtensionTypes(
            from, [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind kind) {
        switch (kind) {
        case QQmlJSScope::NotExtension:
        case QQmlJSScope::ExtensionJavaScript:
            // Converting to a base type is trivially supported.
            // Converting to the JavaScript extension of a type just produces the type itself.
            // Giving the JavaScript extension as type to be converted to means we expect any
            // result that fulfills the given JavaScript interface.
            return scope == to
                    || (matchByName && scope->internalName() == to->internalName());
        case QQmlJSScope::ExtensionType:
        case QQmlJSScope::ExtensionNamespace:
            break;
        }
        return false;
    })) {
        return true;
    }

    if (from == m_variantListType)
        return to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence;

    // We can convert anything that fits into QJSPrimitiveValue
    if (canConvertFromTo(from, m_jsPrimitiveType) && canConvertFromTo(m_jsPrimitiveType, to))
        return true;

    if (areEquivalentLists(from, to))
        return true;

    if (from->isListProperty()
            && to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
            && canConvertFromTo(from->elementType(), to->elementType())) {
        return true;
    }

    // it is possible to assing a singlar object to a list property if it could be stored in the list
    if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
            && from->accessSemantics()  == QQmlJSScope::AccessSemantics::Reference
            && from->inherits(to->elementType())) {
        return true;
    }

    if (to == m_stringType && from->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence)
        return canConvertFromTo(from->elementType(), m_stringType);

    return false;
}

QQmlJSRegisterContent QQmlJSTypeResolver::lengthProperty(
        bool isWritable, QQmlJSRegisterContent scope) const
{
    QQmlJSMetaProperty prop;
    prop.setPropertyName(u"length"_s);
    prop.setTypeName(u"qsizetype"_s);
    prop.setType(sizeType());
    prop.setIsWritable(isWritable);
    return m_pool->createProperty(
            prop, QQmlJSRegisterContent::InvalidLookupIndex,
            QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Property, scope);
}

QQmlJSRegisterContent QQmlJSTypeResolver::memberType(
        QQmlJSRegisterContent type, const QString &name, int baseLookupIndex,
        int resultLookupIndex) const
{
    QQmlJSRegisterContent result;
    const QQmlJSScope::ConstPtr contained = type.containedType();

    // If we got a plain type reference we have to check the enums of the _scope_.
    if (contained == metaObjectType())
        return memberEnumType(type.scope(), name);

    if (contained == variantMapType() || contained->inherits(qmlPropertyMapType())) {
        QQmlJSMetaProperty prop;
        prop.setPropertyName(name);
        prop.setTypeName(u"QVariant"_s);
        prop.setType(varType());
        prop.setIsWritable(true);
        return m_pool->createProperty(
                prop, baseLookupIndex, resultLookupIndex,
                QQmlJSRegisterContent::Property, type);
    }

    if (contained == jsValueType()) {
        QQmlJSMetaProperty prop;
        prop.setPropertyName(name);
        prop.setTypeName(u"QJSValue"_s);
        prop.setType(jsValueType());
        prop.setIsWritable(true);
        return m_pool->createProperty(
                prop, baseLookupIndex, resultLookupIndex,
                QQmlJSRegisterContent::Property, type);
    }

    if ((contained == stringType()
            || contained->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence)
            && name == u"length"_s) {
        return lengthProperty(contained != stringType(), type);
    }

    const auto check = [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) {
        const QQmlJSRegisterContent resultScope = mode == QQmlJSScope::NotExtension
                ? baseType(scope, type)
                : extensionType(scope, type);

        if (mode != QQmlJSScope::ExtensionNamespace) {
            if (scope->hasOwnProperty(name)) {
                const auto prop = scope->ownProperty(name);
                result = m_pool->createProperty(
                        prop, baseLookupIndex, resultLookupIndex,
                        QQmlJSRegisterContent::Property, resultScope);
                return true;
            }

            if (scope->hasOwnMethod(name)) {
                const auto methods = scope->ownMethods(name);
                result = m_pool->createMethod(
                        methods, jsValueType(), QQmlJSRegisterContent::Method, resultScope);
                return true;
            }
        }

        return checkEnums(resultScope, name, &result);
    };

    if (QQmlJSUtils::searchBaseAndExtensionTypes(type.containedType(), check))
        return result;

    for (auto scope = contained;
         scope && (QQmlSA::isFunctionScope(scope->scopeType())
                   || scope->scopeType() == QQmlSA::ScopeType::JSLexicalScope);
         scope = scope->parentScope()) {
        if (auto ownIdentifier = scope->ownJSIdentifier(name)) {
            QQmlJSMetaProperty prop;
            prop.setPropertyName(name);
            prop.setTypeName(u"QJSValue"_s);
            prop.setType(jsValueType());
            prop.setIsWritable(!(ownIdentifier.value().isConst));

            return m_pool->createProperty(
                    prop, baseLookupIndex, resultLookupIndex,
                    QQmlJSRegisterContent::Property,
                    parentScope(scope, type));
        }
    }

    // check enums before checking attached types of attached types (chained attached types)
    if (type.isType()) {
        if (auto result = memberEnumType(type.scope(), name); result.isValid())
            return result;
    }

    if (QQmlJSScope::ConstPtr attachedBase = typeForName(name)) {
        if (QQmlJSScope::ConstPtr attached = attachedBase->attachedType()) {
            if (!genericType(attached)) {
                m_logger->log(u"Cannot resolve generic base of attached %1"_s.arg(
                                      attached->internalName()),
                              qmlCompiler, attached->sourceLocation());
                return {};
            } else if (contained->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
                m_logger->log(u"Cannot retrieve attached object for non-reference type %1"_s.arg(
                                      contained->internalName()),
                              qmlCompiler, contained->sourceLocation());
                return {};
            } else {
                const QQmlJSRegisterContent namedType = m_pool->createType(
                        attachedBase, QQmlJSRegisterContent::InvalidLookupIndex,
                        QQmlJSRegisterContent::TypeByName, type);

                return m_pool->createType(
                        attached, resultLookupIndex, QQmlJSRegisterContent::Attachment,
                        namedType);
            }
        }
    }

    return {};
}

QQmlJSRegisterContent QQmlJSTypeResolver::memberEnumType(
        QQmlJSRegisterContent type, const QString &name) const
{
    QQmlJSRegisterContent result;

    if (QQmlJSUtils::searchBaseAndExtensionTypes(
                type.containedType(),
                [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) {
                    return checkEnums(mode == QQmlJSScope::NotExtension
                                              ? baseType(scope, type)
                                              : extensionType(scope, type),
                                      name, &result);
                })) {
        return result;
    }

    return {};
}

QQmlJSRegisterContent QQmlJSTypeResolver::memberType(
        QQmlJSRegisterContent type, const QString &name, int lookupIndex) const
{
    if (type.isType())
        return memberType(type, name, type.resultLookupIndex(), lookupIndex);
    if (type.isProperty() || type.isMethodCall())
        return memberType(type, name, type.resultLookupIndex(), lookupIndex);
    if (type.isEnumeration()) {
        const auto enumeration = type.enumeration();
        if (!type.enumMember().isEmpty() || !enumeration.hasKey(name))
            return {};
        return m_pool->createEnumeration(
                enumeration, name, QQmlJSRegisterContent::Enum, type.scope());
    }
    if (type.isMethod()) {
        QQmlJSMetaProperty prop;
        prop.setTypeName(u"QJSValue"_s);
        prop.setPropertyName(name);
        prop.setType(jsValueType());
        prop.setIsWritable(true);
        return m_pool->createProperty(
                prop, QQmlJSRegisterContent::InvalidLookupIndex, lookupIndex,
                QQmlJSRegisterContent::Property, type);
    }
    if (type.isImportNamespace()) {
        Q_ASSERT(type.scopeType()->isReferenceType());
        return registerContentForName(name, type);
    }
    if (type.isConversion()) {
        if (const auto result = memberType(
                    type, name, type.resultLookupIndex(), lookupIndex);
            result.isValid()) {
            return result;
        }

        if (const auto result = memberEnumType(type.scope(), name); result.isValid())
            return result;

        // If the conversion consists of only undefined and one actual type,
        // we can produce the members of that one type.
        // If the value is then actually undefined, the result is an exception.

        const auto nonVoid = extractNonVoidFromOptionalType(type);

        // If the conversion cannot hold the original type, it loses information.
        return (!nonVoid.isNull() && canHold(type.conversionResultType(), nonVoid.containedType()))
                ? memberType(nonVoid, name, type.resultLookupIndex(), lookupIndex)
                : QQmlJSRegisterContent();
    }

    Q_UNREACHABLE_RETURN({});
}

QQmlJSRegisterContent QQmlJSTypeResolver::elementType(QQmlJSRegisterContent list) const
{
    QQmlJSScope::ConstPtr value;

    auto valueType = [&](const QQmlJSScope::ConstPtr &scope) -> QQmlJSScope::ConstPtr {
        if (scope->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence)
            return scope->elementType();

        if (scope == m_forInIteratorPtr)
            return m_sizeType;

        if (scope == m_forOfIteratorPtr)
            return list.scopeType()->elementType();

        if (scope == m_jsValueType || scope == m_varType)
            return m_jsValueType;

        if (scope == m_stringType)
            return m_stringType;

        return QQmlJSScope::ConstPtr();
    };

    value = valueType(list.containedType());

    if (value.isNull())
        return {};

    QQmlJSMetaProperty property;
    property.setPropertyName(u"[]"_s);
    property.setTypeName(value->internalName());
    property.setType(value);

    return m_pool->createProperty(
            property, QQmlJSRegisterContent::InvalidLookupIndex,
            QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ListValue,
            list);
}

QQmlJSRegisterContent QQmlJSTypeResolver::returnType(
        const QQmlJSMetaMethod &method, const QQmlJSScope::ConstPtr &returnType,
        QQmlJSRegisterContent scope) const
{
    return m_pool->createMethodCall(method, returnType, scope);
}

QQmlJSRegisterContent QQmlJSTypeResolver::extensionType(
        const QQmlJSScope::ConstPtr &extension, QQmlJSRegisterContent base) const
{
    return m_pool->createType(
            extension, base.resultLookupIndex(), QQmlJSRegisterContent::Extension, base);
}

/*!
 * \internal
 * Encodes \a base as a base type of \a derived and returns a QQmlJSRegisterContent.
 * "Base type" here is understood the same way as std::is_base_of would understand it.
 * That means, if you pass the contained type of \a derived as \a base, then \a derived
 * itself is returned.
 */
QQmlJSRegisterContent QQmlJSTypeResolver::baseType(
        const QQmlJSScope::ConstPtr &base, QQmlJSRegisterContent derived) const
{
    return m_pool->createType(
            base, derived.resultLookupIndex(), QQmlJSRegisterContent::BaseType, derived);
}

/*!
 * \internal
 * Encodes \a parent as a parent scope of \a child and returns a QQmlJSRegisterContent.
 * "Parent scope" here means any scope above, but also _including_ \a child.
 * That means, if you pass the contained type of \a child as \a parent, then \a child
 * itself is returned.
 */
QQmlJSRegisterContent QQmlJSTypeResolver::parentScope(
        const QQmlJSScope::ConstPtr &parent, QQmlJSRegisterContent child) const
{
    return m_pool->createType(
            parent, child.resultLookupIndex(), QQmlJSRegisterContent::ParentScope, child);
}

QQmlJSRegisterContent QQmlJSTypeResolver::iteratorPointer(
        QQmlJSRegisterContent listType, QQmlJS::AST::ForEachType type,
        int lookupIndex) const
{
    const QQmlJSScope::ConstPtr value = (type == QQmlJS::AST::ForEachType::In)
            ? m_int32Type
            : elementType(listType).containedType();

    QQmlJSScope::ConstPtr iteratorPointer = type == QQmlJS::AST::ForEachType::In
            ? m_forInIteratorPtr
            : m_forOfIteratorPtr;

    QQmlJSMetaProperty prop;
    prop.setPropertyName(u"<>"_s);
    prop.setTypeName(iteratorPointer->internalName());
    prop.setType(iteratorPointer);
    return m_pool->createProperty(
            prop, lookupIndex,
            QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ListIterator,
            listType);
}

QQmlJSScope::ConstPtr QQmlJSTypeResolver::storedType(const QQmlJSScope::ConstPtr &type) const
{
    if (type.isNull())
        return {};
    if (type == voidType())
        return type;
    if (type->isScript())
        return jsValueType();
    if (type->isComposite()) {
        if (const QQmlJSScope::ConstPtr nonComposite = QQmlJSScope::nonCompositeBaseType(type))
            return nonComposite;

        // If we can't find the non-composite base, we really don't know what it is.
        return genericType(type);
    }
    if (type->filePath().isEmpty())
        return genericType(type);
    return type;
}

static QQmlJSRegisterContent doConvert(
        QQmlJSRegisterContent from, const QQmlJSScope::ConstPtr &to,
        QQmlJSRegisterContent scope, QQmlJSRegisterContentPool *pool)
{
    if (from.isConversion()) {
        return pool->createConversion(
                from.conversionOrigins(), to,
                scope.isValid() ? scope : from.conversionResultScope(),
                from.variant(), from.scope());
    }

    return pool->createConversion(
            QList<QQmlJSRegisterContent>{from},
            to, scope, from.variant(),
            from.scope());
}

QQmlJSRegisterContent QQmlJSTypeResolver::convert(
        QQmlJSRegisterContent from, QQmlJSRegisterContent to) const
{
    return doConvert(from, to.containedType(), to.scope(), m_pool.get());
}

QQmlJSRegisterContent QQmlJSTypeResolver::convert(
        QQmlJSRegisterContent from, const QQmlJSScope::ConstPtr &to) const
{
    return doConvert(from, to, QQmlJSRegisterContent(), m_pool.get());
}

QT_END_NAMESPACE
