// 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:critical reason:code-generation

#include "qqmljscodegenerator_p.h"
#include "qqmljsmetatypes_p.h"
#include "qqmljsregistercontent_p.h"
#include "qqmljsscope_p.h"
#include "qqmljsutils_p.h"

#include <private/qqmljstypepropagator_p.h>

#include <private/qqmlirbuilder_p.h>
#include <private/qqmljsscope_p.h>
#include <private/qqmljsutils_p.h>
#include <private/qv4compilerscanfunctions_p.h>
#include <private/qduplicatetracker_p.h>

#include <QtCore/qdir.h>
#include <QtCore/qfileinfo.h>

QT_BEGIN_NAMESPACE

using namespace Qt::StringLiterals;

/*!
 * \internal
 * \class QQmlJSCodeGenerator
 *
 * This is a final compile pass that generates C++ code from a function and the
 * annotations produced by previous passes. Such annotations are produced by
 * QQmlJSTypePropagator, and possibly amended by other passes.
 */

#define BYTECODE_UNIMPLEMENTED() Q_ASSERT_X(false, Q_FUNC_INFO, "not implemented");

#define INJECT_TRACE_INFO(function) \
    static const bool injectTraceInfo = true; \
    if (injectTraceInfo) { \
        m_body += u"// "_s + QStringLiteral(#function) + u'\n'; \
    }

#define REJECT \
    return reject

static bool isTypeStorable(const QQmlJSTypeResolver *resolver, const QQmlJSScope::ConstPtr &type)
{
    return !type.isNull() && type != resolver->nullType() && type != resolver->voidType();
}

QString QQmlJSCodeGenerator::castTargetName(const QQmlJSScope::ConstPtr &type) const
{
    return type->augmentedInternalName();
}

QQmlJSCodeGenerator::QQmlJSCodeGenerator(
        const QV4::Compiler::Context *compilerContext,
        const QV4::Compiler::JSUnitGenerator *unitGenerator, const QQmlJSTypeResolver *typeResolver,
        QQmlJSLogger *logger, const BasicBlocks &basicBlocks,
        const InstructionAnnotations &annotations, bool noAotValidation)
    : QQmlJSCompilePass(unitGenerator, typeResolver, logger, basicBlocks, annotations)
    , m_context(compilerContext)
    , m_lookupSignaturesRecorder(logger->filePath(), typeResolver)
    , m_noAotValidation(noAotValidation)
{}

QString QQmlJSCodeGenerator::metaTypeFromType(const QQmlJSScope::ConstPtr &type) const
{
    return u"QMetaType::fromType<"_s + type->augmentedInternalName() + u">()"_s;
}

QString QQmlJSCodeGenerator::metaTypeFromName(const QQmlJSScope::ConstPtr &type) const
{
    return u"[]() { static const auto t = QMetaType::fromName(\""_s
            + QString::fromUtf8(QMetaObject::normalizedType(type->augmentedInternalName().toUtf8()))
            + u"\"); return t; }()"_s;
}

QString QQmlJSCodeGenerator::compositeListMetaType(const QString &elementName) const
{
    return u"QQmlPrivate::compositeListMetaType(aotContext->compilationUnit, "_s
            + (m_jsUnitGenerator->hasStringId(elementName)
                       ? QString::number(m_jsUnitGenerator->getStringId(elementName)) + u')'
                       : u"QStringLiteral(\"%1\"))"_s.arg(elementName));
}

QString QQmlJSCodeGenerator::compositeMetaType(const QString &elementName) const
{
    return u"QQmlPrivate::compositeMetaType(aotContext->compilationUnit, "_s
            + (m_jsUnitGenerator->hasStringId(elementName)
                       ? QString::number(m_jsUnitGenerator->getStringId(elementName)) + u')'
                       : u"QStringLiteral(\"%1\"))"_s.arg(elementName));
}

QString QQmlJSCodeGenerator::metaObject(const QQmlJSScope::ConstPtr &objectType)
{
    if (objectType->isComposite()) {
        const QString name = m_typeResolver->nameForType(objectType);
        if (name.isEmpty()) {
            REJECT<QString>(
                    u"retrieving the metaObject of a composite type without an element name."_s);
        }
        return compositeMetaType(name) + u".metaObject()"_s;
    }

    if (objectType->internalName() == u"QObject"_s
            || objectType->internalName() == u"QQmlComponent"_s) {
        return u'&' + objectType->internalName() + u"::staticMetaObject"_s;
    }
    return metaTypeFromName(objectType) + u".metaObject()"_s;
}

QString QQmlJSCodeGenerator::metaType(const QQmlJSScope::ConstPtr &type)
{
    if (type->isOpaqueType()) {
        REJECT<QString>("Retrieving the metatype of an unregistered type"_L1);
    }
    if (type->isComposite()) {
        const QString name = m_typeResolver->nameForType(type);
        if (name.isEmpty()) {
            REJECT<QString>(
                    u"retrieving the metaType of a composite type without an element name."_s);
        }
        return compositeMetaType(name);
    }

    if (type->isListProperty() && type->elementType()->isComposite()) {
        const QString name = m_typeResolver->nameForType(type->elementType());
        Q_ASSERT(!name.isEmpty()); // There can't be a list with anonymous composite value type
        return compositeListMetaType(name);
    }

    return (m_typeResolver->genericType(type) == type)
            ? metaTypeFromType(type)
            : metaTypeFromName(type);
}

static QString registerName(int registerIndex, int offset)
{
    // 'a<n>': argument number n
    // 'v<n>_<m>': value produced by instruction for register n at byte code offset m
    // 'c<n>_<m>': value produced by conversion for register n at byte code offset m
    // The output of QV4_SHOW_BYTECODE=1 corresponds to the 'm' values.
    // We can convert a value and then store a new value in the same register with one instruction.
    // That's why we need both 'v' and 'c'.

    if (offset < 0)
        return u"s.a%1"_s.arg(registerIndex - QQmlJSCompilePass::Argc);
    if (registerIndex < 0)
        return u"s.c%1_%2"_s.arg(-registerIndex).arg(offset);
    return u"s.v%1_%2"_s.arg(registerIndex).arg(offset);
}

QQmlJSAotFunction QQmlJSCodeGenerator::run(const Function *function, bool basicBlocksValidationFailed)
{
    m_function = function;

    if (m_context->contextType == QV4::Compiler::ContextType::Binding
        && m_function->returnType.contains(m_typeResolver->qQmlScriptStringType())) {
        const QString reason = u"binding for property of type QQmlScriptString; nothing to do."_s;
        skip(reason);
        QQmlJSAotFunction result;
        result.skipReason = reason;
        return result;
    }

    const auto addVariable = [&](int registerIndex, int offset, QQmlJSRegisterContent content) {
        const QQmlJSScope::ConstPtr seenType = content.storedType();
        // Don't generate any variables for registers that are initialized with undefined.
        if (!isTypeStorable(m_typeResolver, seenType))
            return;

        const auto oldSize = m_registerVariables.size();
        auto &e = m_registerVariables[content];
        if (m_registerVariables.size() != oldSize) {
            e.variableName = registerName(registerIndex, offset);
            e.storedType = seenType;
            e.initialRegisterIndex = std::abs(registerIndex);
        } else {
            e.variableName += u'_' + registerName(registerIndex, offset).mid(2); // skip "s."
        }
        ++e.numTracked;
    };

QT_WARNING_PUSH
QT_WARNING_DISABLE_CLANG("-Wrange-loop-analysis")
    for (auto it = m_annotations.constBegin(), iend = m_annotations.constEnd(); it != iend; ++it) {
        const int registerIndex = it->second.changedRegisterIndex;
        if (registerIndex != InvalidRegister)
            addVariable(registerIndex, it.key(), it->second.changedRegister);
        for (auto jt = it->second.typeConversions.constBegin(),
             jend = it->second.typeConversions.constEnd();
             jt != jend; ++jt) {
            // Conversions get a negative register number to discern them.
            const int registerIndex = jt.key();
            if (registerIndex != InvalidRegister)
                addVariable(-registerIndex, it.key(), jt.value().content);
        }
    }
QT_WARNING_POP

    // ensure we have m_labels for loops
    for (const auto loopLabel : m_context->labelInfo)
        m_labels.insert(loopLabel, u"label_%1"_s.arg(m_labels.size()));

    // Initialize the first instruction's state to hold the arguments.
    // After this, the arguments (or whatever becomes of them) are carried
    // over into any further basic blocks automatically.
    m_state.State::operator=(initialState(m_function));

    m_pool->setAllocationMode(QQmlJSRegisterContentPool::Temporary);
    const QByteArray byteCode = function->code;
    decode(byteCode.constData(), static_cast<uint>(byteCode.size()));
    m_pool->setAllocationMode(QQmlJSRegisterContentPool::Permanent);

    QQmlJSAotFunction result;
    result.includes.swap(m_includes);

    if (basicBlocksValidationFailed) {
        result.code += "// QV4_BASIC_BLOCK_VALIDATION_FAILED: This file failed compilation "_L1
                       "with basic blocks validation but compiled without it.\n"_L1;
    }

    result.code += u"// %1 at line %2, column %3\n"_s
            .arg(m_context->name).arg(m_context->line).arg(m_context->column);

    QStringList initializations;
    QStringList markings;
    for (auto registerIt = m_registerVariables.cbegin(), registerEnd = m_registerVariables.cend();
         registerIt != registerEnd; ++registerIt) {

        // Remove the "s.". Inside the struct we need the plain name.
        QString declarationName = registerIt->variableName.mid(2);

        const int registerIndex = registerIt->initialRegisterIndex;
        const bool registerIsArgument = isArgument(registerIndex);

        QString code = registerIt->storedType->internalName();

        const QQmlJSScope::ConstPtr storedType = registerIt->storedType;
        const bool isPointer
                = (storedType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference);
        if (isPointer)
            code += u" *"_s;
        else
            code += u' ';

        if (!registerIsArgument
                && registerIndex != Accumulator
                && registerIndex != This
                && !function->registerTypes[registerIndex - firstRegisterIndex()].contains(
                    m_typeResolver->voidType())) {
            code += declarationName + u" = "_s;
            code += convertStored(m_typeResolver->voidType(), storedType, QString());
        } else if (registerIsArgument && argumentType(registerIndex).isStoredIn(storedType)) {
            const int argumentIndex = registerIndex - FirstArgument;
            const QQmlJSRegisterContent argument
                    = m_function->argumentTypes[argumentIndex];
            const QQmlJSRegisterContent originalArgument = originalType(argument);

            const bool needsConversion = argument != originalArgument;
            if (!isPointer && registerIt->numTracked == 1 && !needsConversion) {
                // Not a pointer, never written to, and doesn't need any initial conversion.
                // This is a readonly argument.
                //
                // We would like to make the variable a const ref if it's a readonly argument,
                // but due to the various call interfaces accepting non-const values, we can't.
                // We rely on those calls to still not modify their arguments in place.
                code += u'&';
            }

            code += declarationName + u" = "_s;

            const auto originalContained = m_typeResolver->originalContainedType(argument);
            QString originalValue;
            const bool needsQVariantWrapping =
                    storedType->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence
                    && !originalContained->isReferenceType()
                    && storedType == m_typeResolver->varType()
                    && originalContained != m_typeResolver->varType();
            if (needsQVariantWrapping) {
                originalValue = u"QVariant(%1, argv[%2])"_s.arg(metaTypeFromName(originalContained))
                                        .arg(QString::number(argumentIndex + 1));
            } else {
                originalValue = u"(*static_cast<"_s + castTargetName(originalArgument.storedType())
                        + u"*>(argv["_s + QString::number(argumentIndex + 1) + u"]))"_s;
            }

            if (needsConversion)
                code += conversion(originalArgument, argument, originalValue);
            else
                code += originalValue;
        } else if (isPointer) {
            code += declarationName + u" = nullptr"_s;
        } else {
            code += declarationName;
        }
        code += u";\n"_s;

        initializations.push_back(std::move(code));

        if (isPointer) {
            markings.append(u"    aotContext->mark("_s + declarationName + u", markStack);\n");
        } else if (storedType == m_typeResolver->varType()) {
            markings.append(u"    aotContext->mark("_s + declarationName + u", markStack);\n");
        } else if (storedType == m_typeResolver->listPropertyType()) {
            // No need to mark that since it's always backed by a property
        } else if (storedType == m_typeResolver->variantMapType()
                   || storedType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
            QString marking = u"    for (const auto &v : std::as_const(" + declarationName + u"))\n"
                    + u"        aotContext->mark(v, markStack);\n";
            markings.append(std::move(marking));
        }
    }

    result.code += u"struct Storage : QQmlPrivate::AOTTrackedLocalsStorage {\n"_s;
    result.code += u"Storage(const QQmlPrivate::AOTCompiledContext *ctxt, void **a)"_s;
    result.code += u"   : aotContext(ctxt), argv(a) {}\n"_s;
    result.code += u"void markObjects(QV4::MarkStack *markStack) const final {"_s;
    result.code += u"    Q_UNUSED(markStack);\n"_s;

    markings.sort();
    for (const QString &marking : std::as_const(markings))
        result.code += marking;

    result.code += u"}\n"_s;
    result.code += u"const QQmlPrivate::AOTCompiledContext *aotContext;\n"_s;
    result.code += u"void **argv;\n"_s;

    // Sort them to obtain stable output.
    initializations.sort();
    for (const QString &initialization : std::as_const(initializations))
        result.code += initialization;

    result.code += u"};\nStorage s(aotContext, argv);\n"_s;
    result.code += u"aotContext->setLocals(&s);\n"_s;

    result.code += m_body;


    QString signature
            = u"    struct { QV4::ExecutableCompilationUnit *compilationUnit; } c { contextUnit };\n"
               "    const auto *aotContext = &c;\n"
               "    Q_UNUSED(aotContext);\n"_s;

    if (function->returnType.isValid()) {
        signature += u"    argTypes[0] = %1;\n"_s.arg(
                metaType(function->returnType.containedType()));
    } else {
        signature += u"    argTypes[0] = QMetaType();\n"_s;
    }
    result.numArguments = function->argumentTypes.length();
    for (qsizetype i = 0; i != result.numArguments; ++i) {
        signature += u"    argTypes[%1] = %2;\n"_s.arg(
                QString::number(i + 1),
                metaType(m_typeResolver->originalContainedType(function->argumentTypes[i])));
    }

    result.signature = std::move(signature);
    return result;
}

void QQmlJSCodeGenerator::generateReturnError()
{
    const auto finalizeReturn = qScopeGuard([this]() { m_body += u"return;\n"_s; });

    m_body += u"aotContext->setReturnValueUndefined();\n"_s;
    const auto ret = m_function->returnType;
    if (!ret.isValid() || ret.contains(m_typeResolver->voidType()))
        return;

    m_body += u"if (argv[0]) {\n"_s;

    const auto contained = ret.containedType();
    const auto stored = ret.storedType();
    if (contained->isReferenceType() && stored->isReferenceType()) {
        m_body += u"    *static_cast<"_s
                + stored->augmentedInternalName()
                + u" *>(argv[0]) = nullptr;\n"_s;
    } else if (contained == stored) {
        m_body += u"    *static_cast<"_s + stored->internalName() + u" *>(argv[0]) = "_s
                + stored->internalName() + u"();\n"_s;
    } else {
        m_body += u"    const QMetaType returnType = "_s
                + metaType(ret.containedType()) + u";\n"_s;
        m_body += u"    returnType.destruct(argv[0]);\n"_s;
        m_body += u"    returnType.construct(argv[0]);\n "_s;
    }

    m_body += u"}\n"_s;
}

void QQmlJSCodeGenerator::generate_Ret()
{
    INJECT_TRACE_INFO(generate_Ret);

    const auto finalizeReturn = qScopeGuard([this]() {
        m_body += u"return;\n"_s;
        m_skipUntilNextLabel = true;
        resetState();
    });

    if (!m_function->returnType.isValid())
        return;

    m_body += u"if (argv[0]) {\n"_s;

    const QString signalUndefined = u"aotContext->setReturnValueUndefined();\n"_s;
    const QString in = m_state.accumulatorVariableIn;

    const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();

    if (in.isEmpty()) {
        if (accumulatorIn.isStoredIn(m_typeResolver->voidType()))
            m_body += signalUndefined;
    } else if (accumulatorIn.isStoredIn(m_typeResolver->varType())) {
        m_body += u"    if (!"_s + in + u".isValid())\n"_s;
        m_body += u"        "_s + signalUndefined;
    } else if (accumulatorIn.isStoredIn(m_typeResolver->jsPrimitiveType())) {
        m_body += u"    if ("_s + in + u".type() == QJSPrimitiveValue::Undefined)\n"_s;
        m_body += u"        "_s + signalUndefined;
    } else if (accumulatorIn.isStoredIn(m_typeResolver->jsValueType())) {
        m_body += u"    if ("_s + in + u".isUndefined())\n"_s;
        m_body += u"        "_s + signalUndefined;
    }

    if (m_function->returnType.contains(m_typeResolver->voidType())) {
        m_body += u"}\n"_s;
        return;
    }

    const auto contained = m_function->returnType.containedType();
    const auto stored = m_function->returnType.storedType();
    if (contained == stored || (contained->isReferenceType() && stored->isReferenceType())) {
        // We can always std::move here, no matter what the optimization pass has detected. The
        // function returns and nothing can access the accumulator register anymore afterwards.
        m_body += u"    *static_cast<"_s
                + stored->augmentedInternalName()
                + u" *>(argv[0]) = "_s
                + conversion(
                          accumulatorIn, m_function->returnType,
                          m_typeResolver->isTriviallyCopyable(accumulatorIn.storedType())
                                  ? in
                                  : u"std::move("_s + in + u')')
                + u";\n"_s;
    } else if (accumulatorIn.contains(contained)) {
        m_body += u"    const QMetaType returnType = "_s + contentType(accumulatorIn, in)
                + u";\n"_s;
        m_body += u"    returnType.destruct(argv[0]);\n"_s;
        m_body += u"    returnType.construct(argv[0], "_s
                + contentPointer(accumulatorIn, in) + u");\n"_s;
    } else {
        m_body += u"    const auto converted = "_s
                + conversion(accumulatorIn, m_function->returnType,
                             consumedAccumulatorVariableIn()) + u";\n"_s;
        m_body += u"    const QMetaType returnType = "_s
                + contentType(m_function->returnType, u"converted"_s)
                + u";\n"_s;
        m_body += u"    returnType.destruct(argv[0]);\n"_s;
        m_body += u"    returnType.construct(argv[0], "_s
                + contentPointer(m_function->returnType, u"converted"_s) + u");\n"_s;
    }

    m_body += u"}\n"_s;
}

void QQmlJSCodeGenerator::generate_Debug()
{
    BYTECODE_UNIMPLEMENTED();
}

static QString toNumericString(double value)
{
    if (value >= std::numeric_limits<int>::min() && value <= std::numeric_limits<int>::max()) {
        const int i = value;
        if (i == value)
            return QString::number(i);
    }

    switch (qFpClassify(value)) {
    case FP_INFINITE: {
        const QString inf = u"std::numeric_limits<double>::infinity()"_s;
        return std::signbit(value) ? (u'-' + inf) : inf;
    }
    case FP_NAN:
        return u"std::numeric_limits<double>::quiet_NaN()"_s;
    case FP_ZERO:
        return std::signbit(value) ? u"-0.0"_s : u"0"_s;
    default:
        break;
    }

    return QString::number(value, 'f', std::numeric_limits<double>::max_digits10);
}

void QQmlJSCodeGenerator::generate_LoadConst(int index)
{
    INJECT_TRACE_INFO(generate_LoadConst);

    // You cannot actually get it to generate LoadConst for anything but double. We have
    // a numer of specialized instructions for the other types, after all. However, let's
    // play it safe.

    const QV4::ReturnedValue encodedConst = m_jsUnitGenerator->constant(index);
    const QV4::StaticValue value = QV4::StaticValue::fromReturnedValue(encodedConst);
    const QQmlJSScope::ConstPtr type = m_typeResolver->typeForConst(encodedConst);

    m_body += m_state.accumulatorVariableOut + u" = "_s;
    if (type == m_typeResolver->realType()) {
        m_body += conversion(
                    type, m_state.accumulatorOut(),
                    toNumericString(value.doubleValue()));
    } else if (type == m_typeResolver->int32Type()) {
        m_body += conversion(
                    type, m_state.accumulatorOut(),
                    QString::number(value.integerValue()));
    } else if (type == m_typeResolver->boolType()) {
        m_body += conversion(
                    type, m_state.accumulatorOut(),
                    value.booleanValue() ? u"true"_s : u"false"_s);
    } else if (type == m_typeResolver->voidType()) {
        m_body += conversion(
                    type, m_state.accumulatorOut(),
                    QString());
    } else if (type == m_typeResolver->nullType()) {
        m_body += conversion(
                    type, m_state.accumulatorOut(),
                    u"nullptr"_s);
    } else {
        REJECT(u"unsupported constant type"_s);
    }

    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generate_LoadZero()
{
    INJECT_TRACE_INFO(generate_LoadZero);

    m_body += m_state.accumulatorVariableOut;
    m_body += u" = "_s + conversion(
                m_typeResolver->int32Type(), m_state.accumulatorOut(), u"0"_s);
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generate_LoadTrue()
{
    INJECT_TRACE_INFO(generate_LoadTrue);

    m_body += m_state.accumulatorVariableOut;
    m_body += u" = "_s + conversion(
                m_typeResolver->boolType(), m_state.accumulatorOut(), u"true"_s);
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generate_LoadFalse()
{
    INJECT_TRACE_INFO(generate_LoadFalse);

    m_body += m_state.accumulatorVariableOut;
    m_body += u" = "_s + conversion(
                m_typeResolver->boolType(), m_state.accumulatorOut(), u"false"_s);
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generate_LoadNull()
{
    INJECT_TRACE_INFO(generate_LoadNull);

    m_body += m_state.accumulatorVariableOut + u" = "_s;
    m_body += conversion(m_typeResolver->nullType(), m_state.accumulatorOut(),
                         u"nullptr"_s);
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generate_LoadUndefined()
{
    INJECT_TRACE_INFO(generate_LoadUndefined);

    m_body += m_state.accumulatorVariableOut + u" = "_s;
    m_body += conversion(m_typeResolver->voidType(), m_state.accumulatorOut(),
                         QString());
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generate_LoadInt(int value)
{
    INJECT_TRACE_INFO(generate_LoadInt);

    m_body += m_state.accumulatorVariableOut;
    m_body += u" = "_s;
    m_body += conversion(m_typeResolver->int32Type(), m_state.accumulatorOut(),
                         QString::number(value));
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generate_MoveConst(int constIndex, int destTemp)
{
    INJECT_TRACE_INFO(generate_MoveConst);

    Q_ASSERT(destTemp == m_state.changedRegisterIndex());

    auto var = changedRegisterVariable();
    if (var.isEmpty())
        return; // Do not load 'undefined'

    const auto v4Value = QV4::StaticValue::fromReturnedValue(
                m_jsUnitGenerator->constant(constIndex));

    const auto changed = m_state.changedRegister();
    QQmlJSScope::ConstPtr contained;
    QString input;

    m_body += var + u" = "_s;
    if (v4Value.isNull()) {
        contained = m_typeResolver->nullType();
    } else if (v4Value.isUndefined()) {
        contained = m_typeResolver->voidType();
    } else if (v4Value.isBoolean()) {
        contained = m_typeResolver->boolType();
        input = v4Value.booleanValue() ? u"true"_s : u"false"_s;
    } else if (v4Value.isInteger()) {
        contained = m_typeResolver->int32Type();
        input = QString::number(v4Value.int_32());
    } else if (v4Value.isDouble()) {
        contained = m_typeResolver->realType();
        input = toNumericString(v4Value.doubleValue());
    } else {
        REJECT(u"unknown const type"_s);
    }
    m_body += conversion(contained, changed, input) + u";\n"_s;
}

void QQmlJSCodeGenerator::generate_LoadReg(int reg)
{
    INJECT_TRACE_INFO(generate_LoadReg);

    if (registerVariable(reg) == m_state.accumulatorVariableOut)
        return;

    m_body += m_state.accumulatorVariableOut;
    m_body += u" = "_s;
    m_body += conversion(
                registerType(reg), m_state.accumulatorOut(), consumedRegisterVariable(reg));
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generate_StoreReg(int reg)
{
    INJECT_TRACE_INFO(generate_StoreReg);

    Q_ASSERT(m_state.changedRegisterIndex() == reg);
    Q_ASSERT(m_state.accumulatorIn().isValid());
    const QString var = changedRegisterVariable();
    if (var.isEmpty())
        return; // don't store "undefined"
    if (var == m_state.accumulatorVariableIn)
        return;
    m_body += var;
    m_body += u" = "_s;
    m_body += conversion(m_state.accumulatorIn(), m_state.changedRegister(),
                         consumedAccumulatorVariableIn());
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generate_MoveReg(int srcReg, int destReg)
{
    INJECT_TRACE_INFO(generate_MoveReg);

    Q_ASSERT(m_state.changedRegisterIndex() == destReg);
    const QString destRegName = changedRegisterVariable();
    if (destRegName.isEmpty())
        return; // don't store things we cannot store.
    if (destRegName == registerVariable(srcReg))
        return; // don't emit redundant copy statements
    m_body += destRegName;
    m_body += u" = "_s;
    m_body += conversion(
                registerType(srcReg), m_state.changedRegister(), consumedRegisterVariable(srcReg));
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generate_LoadImport(int index)
{
    Q_UNUSED(index)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_LoadLocal(int index)
{
    Q_UNUSED(index);
    REJECT(u"LoadLocal"_s);
}

void QQmlJSCodeGenerator::generate_StoreLocal(int index)
{
    Q_UNUSED(index)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_LoadScopedLocal(int scope, int index)
{
    Q_UNUSED(scope)
    Q_UNUSED(index)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_StoreScopedLocal(int scope, int index)
{
    Q_UNUSED(scope)
    Q_UNUSED(index)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_LoadRuntimeString(int stringId)
{
    INJECT_TRACE_INFO(generate_LoadRuntimeString);

    m_body += m_state.accumulatorVariableOut;
    m_body += u" = "_s;
    m_body += conversion(m_typeResolver->stringType(), m_state.accumulatorOut(),
                         QQmlJSUtils::toLiteral(m_jsUnitGenerator->stringForIndex(stringId)));
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generate_MoveRegExp(int regExpId, int destReg)
{
    Q_UNUSED(regExpId)
    Q_UNUSED(destReg)
    REJECT(u"MoveRegExp"_s);
}

void QQmlJSCodeGenerator::generate_LoadClosure(int value)
{
    Q_UNUSED(value)
    REJECT(u"LoadClosure"_s);
}

void QQmlJSCodeGenerator::generate_LoadName(int nameIndex)
{
    Q_UNUSED(nameIndex)
    REJECT(u"LoadName"_s);
}

void QQmlJSCodeGenerator::generate_LoadGlobalLookup(int index)
{
    INJECT_TRACE_INFO(generate_LoadGlobalLookup);

    AccumulatorConverter registers(this);

    const QString lookup = u"aotContext->loadGlobalLookup("_s + QString::number(index) + u", "_s
            + contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut) + u')';
    const QString initialization = u"aotContext->initLoadGlobalLookup("_s
            + QString::number(index) + u", "_s
            + contentType(m_state.accumulatorOut(), m_state.accumulatorVariableOut) + u')';
    const QString preparation = getLookupPreparation(
            m_state.accumulatorOut(), m_state.accumulatorVariableOut, index);
    generateLookup(lookup, initialization, preparation);
}

void QQmlJSCodeGenerator::generate_LoadQmlContextPropertyLookup(int index)
{
    INJECT_TRACE_INFO(generate_LoadQmlContextPropertyLookup);

    AccumulatorConverter registers(this);

    const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();

    const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
    if (m_state.accumulatorOut().scope().contains(m_typeResolver->jsGlobalObject())) {
        // This produces a QJSValue. The QQmlJSMetaProperty used to analyze it may have more details
        // but the QQmlJSAotContext API does not reflect them.
        m_body += m_state.accumulatorVariableOut + u" = "_s
                + conversion(
                    m_typeResolver->jsValueType(), m_state.accumulatorOut(),
                    u"aotContext->javaScriptGlobalProperty("_s + QString::number(nameIndex) + u")")
                + u";\n"_s;
        return;
    }

    const QString indexString = QString::number(index);
    if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectById) {
        const QString lookup = u"aotContext->loadContextIdLookup("_s
                + indexString + u", "_s
                + contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut) + u')';
        const QString initialization = u"aotContext->initLoadContextIdLookup("_s
                + indexString + u')';
        generateLookup(lookup, initialization);
        return;
    }

    const bool isProperty = m_state.accumulatorOut().isProperty();
    if (isProperty) {
        const QString lookup = u"aotContext->loadScopeObjectPropertyLookup("_s
                + indexString + u", "_s
                + contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut) + u')';
        const QString initialization = u"aotContext->initLoadScopeObjectPropertyLookup("_s
                + indexString + u')';
        const QString preparation = getLookupPreparation(
                    m_state.accumulatorOut(), m_state.accumulatorVariableOut, index);

        recordPropertyLookup(m_typeResolver->original(m_state.accumulatorOut()).scopeType(),
                             originalProperty(m_state.accumulatorOut()));
        generateLookup(lookup, initialization, preparation);
    } else if (m_state.accumulatorOut().isType() || m_state.accumulatorOut().isImportNamespace()) {
        generateTypeLookup(index);
    } else {
        REJECT(u"lookup of %1"_s.arg(m_state.accumulatorOut().descriptiveName()));
    }
}

void QQmlJSCodeGenerator::generate_StoreNameSloppy(int nameIndex)
{
    INJECT_TRACE_INFO(generate_StoreNameSloppy);

    const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
    const QQmlJSRegisterContent type = m_typeResolver->scopedType(m_function->qmlScope, name);
    Q_ASSERT(type.isProperty());

    switch (type.variant()) {
    case QQmlJSRegisterContent::Property: {
        // Do not convert here. We may intentionally pass the "wrong" type, for example to trigger
        // a property reset.
        m_body += u"aotContext->storeNameSloppy("_s + QString::number(nameIndex)
                + u", "_s
                + contentPointer(m_state.accumulatorIn(), m_state.accumulatorVariableIn)
                + u", "_s
                + contentType(m_state.accumulatorIn(), m_state.accumulatorVariableIn) + u')';
        m_body += u";\n"_s;
        break;
    }
    case QQmlJSRegisterContent::Method:
        REJECT(u"assignment to scope method"_s);
    default:
        Q_UNREACHABLE();
    }
}

void QQmlJSCodeGenerator::generate_StoreNameStrict(int name)
{
    Q_UNUSED(name)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_LoadElement(int base)
{
    INJECT_TRACE_INFO(generate_LoadElement);

    const QQmlJSRegisterContent baseType = registerType(base);

    if (!baseType.isList() && !baseType.isStoredIn(m_typeResolver->stringType()))
        REJECT(u"LoadElement with non-list base type "_s + baseType.descriptiveName());

    const QString voidAssignment = u"    "_s + m_state.accumulatorVariableOut + u" = "_s +
            conversion(literalType(m_typeResolver->voidType()),
                       m_state.accumulatorOut(), QString()) + u";\n"_s;

    AccumulatorConverter registers(this);

    QString indexName = m_state.accumulatorVariableIn;
    QQmlJSScope::ConstPtr indexType;
    if (m_typeResolver->isNumeric(m_state.accumulatorIn())) {
        indexType = m_state.accumulatorIn().containedType();
    } else if (m_state.accumulatorIn().isConversion()) {
        const auto target = m_typeResolver->extractNonVoidFromOptionalType(m_state.accumulatorIn());
        if (m_typeResolver->isNumeric(target)) {
            indexType = target.containedType();
            m_body += u"if (!" + indexName + u".metaType().isValid())\n"
                    + voidAssignment
                    + u"else ";
            indexName = convertStored(
                    m_state.accumulatorIn().storedType(), indexType, indexName);
        } else {
            REJECT(u"LoadElement with non-numeric argument"_s);
        }
    }

    const QString baseName = registerVariable(base);

    if (!m_typeResolver->isNativeArrayIndex(indexType)) {
        m_body += u"if (!QJSNumberCoercion::isArrayIndex("_s + indexName + u"))\n"_s
                + voidAssignment
                + u"else "_s;
    } else if (!m_typeResolver->isUnsignedInteger(indexType)) {
        m_body += u"if ("_s + indexName + u" < 0)\n"_s
                + voidAssignment
                + u"else "_s;
    }

    if (baseType.isStoredIn(m_typeResolver->listPropertyType())) {
        // Our QQmlListProperty only keeps plain QObject*.
        m_body += u"if ("_s + indexName + u" < "_s + baseName
                + u".count(&"_s + baseName + u"))\n"_s;
        m_body += u"    "_s + m_state.accumulatorVariableOut + u" = "_s +
                conversion(m_typeResolver->qObjectType(), m_state.accumulatorOut(),
                           baseName + u".at(&"_s + baseName + u", "_s
                           + indexName + u')') + u";\n"_s;
        m_body += u"else\n"_s
                + voidAssignment;
        return;
    }

    // Since we can do .at() below, we know that we can natively store the element type.
    QQmlJSRegisterContent elementType = m_typeResolver->elementType(baseType);
    elementType = m_pool->storedIn(
            elementType, m_typeResolver->storedType(elementType.containedType()));

    QString access = baseName + u".at("_s + indexName + u')';

    // TODO: Once we get a char type in QML, use it here.
    if (baseType.isStoredIn(m_typeResolver->stringType()))
        access = u"QString("_s + access + u")"_s;
    else if (isRegisterAffectedBySideEffects(base))
        REJECT(u"LoadElement on a sequence potentially affected by side effects"_s);
    else if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence)
        REJECT(u"LoadElement on a sequence wrapped in a non-sequence type"_s);

    m_body += u"if ("_s + indexName + u" < "_s + baseName + u".size())\n"_s;
    m_body += u"    "_s + m_state.accumulatorVariableOut + u" = "_s +
            conversion(elementType, m_state.accumulatorOut(), access) + u";\n"_s;
    m_body += u"else\n"_s
            + voidAssignment;
}

void QQmlJSCodeGenerator::generate_StoreElement(int base, int index)
{
    INJECT_TRACE_INFO(generate_StoreElement);

    const QQmlJSRegisterContent baseType = registerType(base);
    const QQmlJSScope::ConstPtr indexType = registerType(index).containedType();

    if (!m_typeResolver->isNumeric(indexType) || !baseType.isList())
        REJECT(u"StoreElement with non-list base type or non-numeric arguments"_s);

    if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence)
        REJECT(u"indirect StoreElement"_s);

    const QString baseName = registerVariable(base);
    const QString indexName = registerVariable(index);

    const auto elementType = m_typeResolver->genericType(
            m_typeResolver->elementType(baseType).containedType());

    addInclude(u"QtQml/qjslist.h"_s);
    if (!m_typeResolver->isNativeArrayIndex(indexType))
        m_body += u"if (QJSNumberCoercion::isArrayIndex("_s + indexName + u")) {\n"_s;
    else if (!m_typeResolver->isUnsignedInteger(indexType))
        m_body += u"if ("_s + indexName + u" >= 0) {\n"_s;
    else
        m_body += u"{\n"_s;

    if (baseType.isStoredIn(m_typeResolver->listPropertyType())) {
        m_body += u"    if ("_s + indexName + u" < "_s + baseName + u".count(&"_s + baseName
                + u"))\n"_s;
        m_body += u"        "_s + baseName + u".replace(&"_s + baseName
                + u", "_s + indexName + u", "_s;
        m_body += conversion(m_state.accumulatorIn(), elementType, m_state.accumulatorVariableIn)
                + u");\n"_s;
        m_body += u"}\n"_s;
        return;
    }

    if (isRegisterAffectedBySideEffects(base))
        REJECT(u"StoreElement on a sequence potentially affected by side effects"_s);
    if (isRegisterAffectedBySideEffects(Accumulator))
        REJECT(u"StoreElement of a value potentially affected by side effects"_s);

    m_body += u"    if ("_s + indexName + u" >= " + baseName + u".size())\n"_s;
    m_body += u"        QJSList(&"_s + baseName + u", aotContext->engine).resize("_s
            + indexName + u" + 1);\n"_s;
    m_body += u"    "_s + baseName + u'[' + indexName + u"] = "_s;
    m_body += conversion(m_state.accumulatorIn(), elementType, m_state.accumulatorVariableIn)
            + u";\n"_s;
    m_body += u"}\n"_s;

    generateWriteBack(base);
}

void QQmlJSCodeGenerator::generate_LoadProperty(int nameIndex)
{
    Q_UNUSED(nameIndex)
    REJECT(u"LoadProperty"_s);
}

void QQmlJSCodeGenerator::generate_LoadOptionalProperty(int name, int offset)
{
    Q_UNUSED(name)
    Q_UNUSED(offset)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generateEnumLookup(int index)
{
    const QString enumMember = m_state.accumulatorOut().enumMember();

    // If we're referring to the type, there's nothing to do.
    // However, we should not get here since no one can ever use the enum metatype.
    // The lookup is dead code and should be optimized away.
    // ... unless you are actually trying to store the metatype itself in a property.
    //     We cannot compile such code.
    if (enumMember.isEmpty())
        REJECT(u"Lookup of enum metatype"_s);

    // If the metaenum has the value, just use it and skip all the rest.
    const QQmlJSMetaEnum metaEnum = m_state.accumulatorOut().enumeration();
    if (metaEnum.hasValues()) {
        m_body += m_state.accumulatorVariableOut + u" = "_s
                + QString::number(metaEnum.value(enumMember));
        m_body += u";\n"_s;
        const QQmlJSRegisterContent &scope = m_state.accumulatorOut().scope();
        recordEnumKeyLookup(m_typeResolver->originalContainedType(scope), metaEnum, enumMember);
        return;
    }

    const QQmlJSScope::ConstPtr scopeType = m_state.accumulatorOut().scopeType();

    // Otherwise we would have found an enum with values.
    Q_ASSERT(!scopeType->isComposite());

    const QString enumName = metaEnum.isFlag() ? metaEnum.alias() : metaEnum.name();
    if (enumName.isEmpty()) {
        if (metaEnum.isFlag() && !metaEnum.name().isEmpty()) {
            REJECT(u"qmltypes misses name entry for flag; "
                   "did you pass the enum type to Q_FLAG instead of the QFlag type?"
                   "\nType is %1, enum name is %2"_s.arg(scopeType->internalName(), metaEnum.name()));
        }
        REJECT(u"qmltypes misses name entry for enum"_s);
    }
    const QString lookup = u"aotContext->getEnumLookup("_s + QString::number(index)
            + u", &"_s + m_state.accumulatorVariableOut + u')';
    const QString initialization = u"aotContext->initGetEnumLookup("_s
            + QString::number(index) + u", "_s + metaObject(scopeType)
            + u", \""_s + enumName + u"\", \""_s + enumMember
            + u"\")"_s;

    // No need to record C++ enums lookup as they are resolved at runtime
    generateLookup(lookup, initialization);
}

void QQmlJSCodeGenerator::generateTypeLookup(int index)
{
    const QString indexString = QString::number(index);
    const QQmlJSRegisterContent accumulatorIn = m_state.registers.value(Accumulator).content;
    const QString namespaceString
            = accumulatorIn.isImportNamespace()
                ? QString::number(accumulatorIn.importNamespace())
                : u"QQmlPrivate::AOTCompiledContext::InvalidStringId"_s;

    switch (m_state.accumulatorOut().variant()) {
    case QQmlJSRegisterContent::Singleton: {
        rejectIfNonQObjectOut(u"non-QObject singleton type"_s);
        const QString lookup = u"aotContext->loadSingletonLookup("_s + indexString
                + u", &"_s + m_state.accumulatorVariableOut + u')';
        const QString initialization = u"aotContext->initLoadSingletonLookup("_s + indexString
                + u", "_s + namespaceString + u')';
        generateLookup(lookup, initialization);
        break;
    }
    case QQmlJSRegisterContent::ModulePrefix:
        break;
    case QQmlJSRegisterContent::Attachment: {
        rejectIfNonQObjectOut(u"non-QObject attached type"_s);
        const QString lookup = u"aotContext->loadAttachedLookup("_s + indexString
                + u", aotContext->qmlScopeObject, &"_s + m_state.accumulatorVariableOut + u')';
        const QString initialization = u"aotContext->initLoadAttachedLookup("_s + indexString
                + u", "_s + namespaceString + u", aotContext->qmlScopeObject)"_s;
        generateLookup(lookup, initialization);
        break;
    }
    case QQmlJSRegisterContent::Script:
        REJECT(u"script lookup"_s);
    case QQmlJSRegisterContent::MetaType: {
        if (!m_state.accumulatorOut().isStoredIn(m_typeResolver->metaObjectType())) {
            // TODO: Can we trigger this somehow?
            //       It might be impossible, but we better be safe here.
            REJECT(u"meta-object stored in different type"_s);
        }
        const QString lookup = u"aotContext->loadTypeLookup("_s + indexString
                + u", &"_s + m_state.accumulatorVariableOut + u')';
        const QString initialization = u"aotContext->initLoadTypeLookup("_s + indexString
                + u", "_s + namespaceString + u")"_s;
        generateLookup(lookup, initialization);
        break;
    }
    default:
        Q_UNREACHABLE();
    }
}

void QQmlJSCodeGenerator::generateVariantEqualityComparison(
        QQmlJSRegisterContent nonStorableContent, const QString &registerName, bool invert)
{
    const auto nonStorableType = nonStorableContent.containedType();
    QQmlJSScope::ConstPtr comparedType = (nonStorableType == m_typeResolver->nullType())
            ? m_typeResolver->nullType()
            : m_typeResolver->voidType();

    // The common operations for both nulltype and voidtype
    m_body += u"if ("_s + registerName
            + u".metaType() == QMetaType::fromType<QJSPrimitiveValue>()) {\n"_s
            + m_state.accumulatorVariableOut + u" = "_s
            + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
                         u"static_cast<const QJSPrimitiveValue *>("_s + registerName
                                 + u".constData())"_s + u"->type() "_s
                                 + (invert ? u"!="_s : u"=="_s)
                                 + (comparedType == m_typeResolver->nullType()
                                            ? u"QJSPrimitiveValue::Null"_s
                                            : u"QJSPrimitiveValue::Undefined"_s))
            + u";\n} else if ("_s + registerName
            + u".metaType() == QMetaType::fromType<QJSValue>()) {\n"_s
            + m_state.accumulatorVariableOut + u" = "_s
            + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
                         (invert ? u"!"_s : QString()) + u"static_cast<const QJSValue *>("_s
                                 + registerName + u".constData())"_s + u"->"_s
                                 + (comparedType == m_typeResolver->nullType()
                                            ? u"isNull()"_s
                                            : u"isUndefined()"_s))
            + u";\n}"_s;

    // Generate nullType specific operations (the case when variant contains QObject * or
    // std::nullptr_t)
    if (nonStorableType == m_typeResolver->nullType()) {
        m_body += u"else if ("_s + registerName
                + u".metaType().flags().testFlag(QMetaType::PointerToQObject)) {\n"_s
                + m_state.accumulatorVariableOut + u" = "_s
                + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
                             u"*static_cast<QObject *const *>("_s + registerName
                                     + u".constData())"_s + (invert ? u"!="_s : u"=="_s)
                                     + u" nullptr"_s)
                + u";\n} else if ("_s + registerName
                + u".metaType() == QMetaType::fromType<std::nullptr_t>()) {\n"_s
                + m_state.accumulatorVariableOut + u" = "_s
                + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
                             (invert ? u"false"_s : u"true"_s))
                + u";\n}\n"_s;
    }

    // fallback case (if variant contains a different type, then it is not null or undefined)
    m_body += u"else {\n"_s + m_state.accumulatorVariableOut + u" = "_s
            + conversion(m_typeResolver->boolType(), m_state.accumulatorOut(),
                         (invert ? (registerName + u".isValid() ? true : false"_s)
                                 : (registerName + u".isValid() ? false : true"_s)))
            + u";\n}"_s;
}

void QQmlJSCodeGenerator::generateVariantEqualityComparison(
        QQmlJSRegisterContent storableContent, const QString &typedRegisterName,
        const QString &varRegisterName, bool invert)
{
    // enumerations are ===-equal to their underlying type and they are stored as such.
    // Therefore, use the underlying type right away.
    const QQmlJSScope::ConstPtr contained = storableContent.isEnumeration()
              ? storableContent.storedType()
              : storableContent.containedType();

    const QQmlJSScope::ConstPtr boolType = m_typeResolver->boolType();
    if (contained->isReferenceType()) {
        const QQmlJSScope::ConstPtr comparable = m_typeResolver->qObjectType();
        const QString cmpExpr = (invert ? u"!"_s : QString()) + u"(("
                + varRegisterName + u".metaType().flags() & QMetaType::PointerToQObject) "_s
                + u" && "_s + conversion(storableContent, comparable, typedRegisterName) + u" == "_s
                + conversion(m_typeResolver->varType(), comparable, varRegisterName) + u')';

        m_body += m_state.accumulatorVariableOut + u" = "_s
                + conversion(boolType, m_state.accumulatorOut(), cmpExpr) + u";\n";
        return;
    }

    if (m_typeResolver->isPrimitive(contained)) {
        const QQmlJSScope::ConstPtr comparable = m_typeResolver->jsPrimitiveType();
        const QString cmpExpr = (invert ? u"!"_s : QString())
                + conversion(storableContent, comparable, typedRegisterName)
                + u".strictlyEquals("_s
                + conversion(m_typeResolver->varType(), comparable, varRegisterName) + u')';

        m_body += m_state.accumulatorVariableOut + u" = "_s
                + conversion(boolType, m_state.accumulatorOut(), cmpExpr) + u";\n"_s;
        return;
    }

    REJECT(u"comparison of non-primitive, non-object type to var"_s);
}

void QQmlJSCodeGenerator::generateArrayInitializer(int argc, int argv)
{
    const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();
    const QQmlJSScope::ConstPtr element = stored->elementType();
    Q_ASSERT(element);

    QStringList initializer;
    for (int i = 0; i < argc; ++i) {
        initializer += convertStored(
                registerType(argv + i).storedType(), element,
                consumedRegisterVariable(argv + i));
    }

    m_body += m_state.accumulatorVariableOut + u" = "_s + stored->internalName() + u'{';
    m_body += initializer.join(u", "_s);
    m_body += u"};\n";
}

void QQmlJSCodeGenerator::generateWriteBack(int registerIndex)
{
    QString writeBackRegister = registerVariable(registerIndex);
    bool writeBackAffectedBySideEffects = isRegisterAffectedBySideEffects(registerIndex);

    for (QQmlJSRegisterContent writeBack = registerType(registerIndex);
         !writeBack.storedType()->isReferenceType();) {

        switch (writeBack.variant()) {
        case QQmlJSRegisterContent::Literal:
        case QQmlJSRegisterContent::Operation:
            // If the value type or list was produced as a literal or as result
            // of an operation (like DefineArray ...), we don't have to write back.
            return;
        default:
            break;
        }

        if (writeBackAffectedBySideEffects)
            REJECT(u"write-back of value affected by side effects"_s);

        if (writeBack.isConversion())
            REJECT(u"write-back of converted value"_s);

        const int lookupIndex = writeBack.resultLookupIndex();

        // This is essential for the soundness of the type system.
        //
        // If a value or a list is returned from a function, we cannot know
        // whether it is a copy or a reference. Therefore, we cannot know whether
        // we have to write it back and so we have to REJECT any write on it.
        //
        // Only if we are sure that the value is locally created we can be sure
        // we don't have to write it back. In this latter case we could allow
        // a modification that doesn't write back.
        if (lookupIndex == -1)
            REJECT(u"write-back of non-lookup"_s);

        const QString writeBackIndexString = QString::number(lookupIndex);

        const QQmlJSRegisterContent::ContentVariant variant = writeBack.variant();
        if (variant == QQmlJSRegisterContent::Property && isQmlScopeObject(writeBack.scope())) {
            const QString lookup = u"aotContext->writeBackScopeObjectPropertyLookup("_s
                    + writeBackIndexString
                    + u", "_s + contentPointer(writeBack, writeBackRegister) + u')';
            const QString initialization = u"aotContext->initLoadScopeObjectPropertyLookup("_s
                    + writeBackIndexString + u')';
            recordPropertyLookup(m_typeResolver->originalContainedType(writeBack.scope()),
                                 originalProperty(writeBack));
            generateLookup(lookup, initialization);
            break;
        }


        QQmlJSRegisterContent outerContent;
        QString outerRegister;
        bool outerAffectedBySideEffects = false;
        for (auto it = m_state.lookups.constBegin(), end = m_state.lookups.constEnd();
             it != end; ++it) {
            if (it.value().content.resultLookupIndex() == writeBack.baseLookupIndex()) {
                outerContent = it.value().content;
                outerRegister = lookupVariable(outerContent.resultLookupIndex());
                outerAffectedBySideEffects = it.value().affectedBySideEffects;
                break;
            }
        }

        // If the lookup doesn't exist, it was killed by state merge.
        if (!outerContent.isValid())
            REJECT(u"write-back of lookup across jumps or merges."_s);

        Q_ASSERT(!outerRegister.isEmpty());

        switch (writeBack.variant()) {
        case QQmlJSRegisterContent::Property: {
            const QQmlJSMetaProperty property = originalProperty(writeBack);
            if (writeBack.scopeType()->isReferenceType()) {
                const QString lookup = u"aotContext->writeBackObjectLookup("_s
                        + writeBackIndexString
                        + u", "_s + outerRegister
                        + u", "_s + contentPointer(writeBack, writeBackRegister) + u')';

                const QString initialization = (m_state.registers[registerIndex].isShadowable
                                        ? u"aotContext->initGetObjectLookupAsVariant("_s
                                        : u"aotContext->initGetObjectLookup("_s)
                        + writeBackIndexString + u", "_s + outerRegister + u')';
                recordPropertyLookup(m_typeResolver->originalContainedType(writeBack.scope()), property);
                generateLookup(lookup, initialization);
            } else {
                const QString valuePointer = contentPointer(outerContent, outerRegister);
                const QString lookup = u"aotContext->writeBackValueLookup("_s
                        + writeBackIndexString
                        + u", "_s + valuePointer
                        + u", "_s + contentPointer(writeBack, writeBackRegister) + u')';
                const QString initialization = u"aotContext->initGetValueLookup("_s
                        + writeBackIndexString
                        + u", "_s + metaObject(writeBack.scopeType()) + u')';
                recordPropertyLookup(m_typeResolver->originalContainedType(writeBack.scope()), property);
                generateLookup(lookup, initialization);
            }
            break;
        }
        default:
            REJECT(u"SetLookup on value types (because of missing write-back)"_s);
        }

        writeBackRegister = outerRegister;
        writeBack = outerContent;
        writeBackAffectedBySideEffects = outerAffectedBySideEffects;
    }
}

void QQmlJSCodeGenerator::rejectIfNonQObjectOut(const QString &error)
{
    if (m_state.accumulatorOut().storedType()->accessSemantics()
        != QQmlJSScope::AccessSemantics::Reference) {
        REJECT(error);
    }
}

void QQmlJSCodeGenerator::rejectIfBadArray()
{
    const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();
    if (stored->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence) {
        // This rejects any attempt to store the list into a QVariant.
        // Therefore, we don't have to adjust the contained type below.

        REJECT(u"storing an array in a non-sequence type"_s);
    } else if (stored->isListProperty()) {
        // We can, technically, generate code for this. But it's dangerous:
        //
        // const QString storage = m_state.accumulatorVariableOut + u"_storage"_s;
        // m_body += stored->internalName() + u"::ListType " + storage
        //         + u" = {"_s + initializer.join(u", "_s) + u"};\n"_s;
        // m_body += m_state.accumulatorVariableOut
        //         + u" = " + stored->internalName() + u"(nullptr, &"_s + storage + u");\n"_s;

        REJECT(u"creating a QQmlListProperty not backed by a property"_s);

    }
}

/*!
 * \internal
 *
 * generates a check for the content pointer to be valid.
 * Returns true if the content pointer needs to be retrieved from a QVariant, or
 * false if the variable can be used as-is.
 */
bool QQmlJSCodeGenerator::generateContentPointerCheck(
        const QQmlJSScope::ConstPtr &required, QQmlJSRegisterContent actual,
        const QString &variable, const QString &errorMessage)
{
    const QQmlJSScope::ConstPtr scope = required;
    const QQmlJSScope::ConstPtr input = actual.containedType();
    if (QQmlJSUtils::searchBaseAndExtensionTypes(input,
            [&](const QQmlJSScope::ConstPtr &base) { return base == scope; })) {
        return false;
    }

    if (!m_typeResolver->canHold(input, scope)) {
        REJECT<bool>(u"lookup of members of %1 in %2"_s
                             .arg(scope->internalName(), input->internalName()));
    }

    bool needsVarContentConversion = false;
    QString processedErrorMessage;
    if (actual.storedType()->isReferenceType()) {
        // Since we have verified the type in qqmljstypepropagator.cpp we now know
        // that we can only have either null or the actual type here. Therefore,
        // it's enough to check the pointer for null.
        m_body += u"if ("_s + variable + u" == nullptr) {\n    "_s;
        processedErrorMessage = errorMessage.arg(u"null");
    } else if (actual.isStoredIn(m_typeResolver->varType())) {
        // Since we have verified the type in qqmljstypepropagator.cpp we now know
        // that we can only have either undefined or the actual type here. Therefore,
        // it's enough to check the QVariant for isValid().
        m_body += u"if (!"_s + variable + u".isValid()) {\n    "_s;
        needsVarContentConversion = true;
        processedErrorMessage = errorMessage.arg(u"undefined");
    } else {
        REJECT<bool>(u"retrieving metatype from %1"_s.arg(actual.descriptiveName()));
    }

    generateSetInstructionPointer();
    m_body += u"    aotContext->engine->throwError(QJSValue::TypeError, "_s;
    m_body += u"QLatin1String(\"%1\"));\n"_s.arg(processedErrorMessage);
    generateReturnError();
    m_body += u"}\n"_s;
    return needsVarContentConversion;
}

QString QQmlJSCodeGenerator::generateCallConstructor(
        const QQmlJSMetaMethod &ctor, const QList<QQmlJSRegisterContent> &argumentTypes,
        const QStringList &arguments, const QString &metaType, const QString &metaObject)
{
    const auto parameterTypes = ctor.parameters();
    Q_ASSERT(parameterTypes.length() == argumentTypes.length());

    // We need to store the converted arguments in a temporaries because they might not be lvalues.
    QStringList argPointers;

    QString result = u"[&](){\n"_s;
    for (qsizetype i = 0, end = parameterTypes.length(); i < end; ++i) {
        const QQmlJSRegisterContent argumentType = argumentTypes[i];
        const QQmlJSScope::ConstPtr parameterType = parameterTypes[i].type();
        const QString argument = arguments[i];
        const QString arg = u"arg"_s + QString::number(i);

        result += u"    auto "_s + arg + u" = "_s;
        if (argumentType.contains(parameterType)) {
            result += argument;
            argPointers.append(contentPointer(argumentType, arg));
        } else {
            const QQmlJSRegisterContent parameterTypeConversion
                    = m_pool->storedIn(
                        m_typeResolver->convert(argumentType, parameterType),
                        m_typeResolver->genericType(parameterType));
            result += conversion(argumentType, parameterTypeConversion, argument);
            argPointers.append(contentPointer(parameterTypeConversion, arg));
        }
        result += u";\n"_s;
    }

    result += u"    void *args[] = {"_s + argPointers.join(u',') + u"};\n"_s;
    result += u"    return aotContext->constructValueType("_s + metaType + u", "_s + metaObject
            + u", "_s + QString::number(int(ctor.constructorIndex())) + u", args);\n"_s;

    return result + u"}()"_s;
}

static bool canTypeBeAffectedBySideEffects(
        const QQmlJSTypeResolver *typeResolver, const QQmlJSRegisterContent &baseType)
{
    const QQmlJSScope::ConstPtr contained = baseType.containedType();
    switch (contained->accessSemantics()) {
    case QQmlSA::AccessSemantics::Reference:
    case QQmlSA::AccessSemantics::None:
        // References and namespaces can't be affected by side effects.
        return false;
    case QQmlSA::AccessSemantics::Value:
        // Value types can have inner objects, and we may have pre-created them where the
        // interpreter keeps them in JavaScript object form for longer.
        // TODO: We can probably improve here.
        return !typeResolver->isPrimitive(contained);
    case QQmlSA::AccessSemantics::Sequence: {
        // List properties are never affected by side effects
        if (contained->isListProperty())
            return false;

        switch (baseType.variant()) {
        case QQmlJSRegisterContent::Operation:
        case QQmlJSRegisterContent::Literal: {
            // Stack-created lists of primitives and pointers can't be affected by side effects
            const QQmlJSScope::ConstPtr elementContained = contained->elementType();
            return !elementContained->isReferenceType()
                    && !typeResolver->isPrimitive(elementContained);
        }
        default:
            return true;
        }
    }
    }
    return true;
}

bool QQmlJSCodeGenerator::isRegisterAffectedBySideEffects(int registerIndex)
{
    if (!m_state.isRegisterAffectedBySideEffects(registerIndex))
        return false;

    QQmlJSRegisterContent baseType = registerType(registerIndex);
    if (baseType.isConversion()) {
        // A conversion can be affected by side effects if any of its origins can.
        // Conversions are unrolled on creation, so we don't have to recurse.

        const auto origins = baseType.conversionOrigins();
        for (QQmlJSRegisterContent origin : origins) {
            if (canTypeBeAffectedBySideEffects(m_typeResolver, m_typeResolver->original(origin)))
                return true;
        }

        return false;
    }

    return canTypeBeAffectedBySideEffects(m_typeResolver, m_typeResolver->original(baseType));
}

QString QQmlJSCodeGenerator::resolveValueTypeContentPointer(
        const QQmlJSScope::ConstPtr &required, QQmlJSRegisterContent actual,
        const QString &variable, const QString &errorMessage)
{
    if (generateContentPointerCheck(required, actual, variable, errorMessage))
        return variable + u".data()"_s;
    return contentPointer(actual, variable);
}

QString QQmlJSCodeGenerator::resolveQObjectPointer(
        const QQmlJSScope::ConstPtr &required, QQmlJSRegisterContent actual,
        const QString &variable, const QString &errorMessage)
{
    if (generateContentPointerCheck(required, actual, variable, errorMessage))
        return u"*static_cast<QObject *const *>("_s + variable + u".constData())"_s;
    return variable;
}

void QQmlJSCodeGenerator::generate_GetLookup(int index)
{
    INJECT_TRACE_INFO(generate_GetLookup);
    generate_GetLookupHelper(index);
}

QString QQmlJSCodeGenerator::generateVariantMapGetLookup(
        const QString &map, const int nameIndex)
{
    const QString mapLookup = map
            + u"["_s + QQmlJSUtils::toLiteral(m_jsUnitGenerator->lookupName(nameIndex)) + u"]"_s;

    return m_state.accumulatorVariableOut + u" = "_s
            + conversion(m_typeResolver->varType(), m_state.accumulatorOut(), mapLookup)
            + u";\n"_s;
}

QString QQmlJSCodeGenerator::generateVariantMapSetLookup(
        const QString &map, const int nameIndex,
        const QQmlJSScope::ConstPtr &property, const QString &variableIn)
{
    const QString mapLookup = map
            + u"["_s + QQmlJSUtils::toLiteral(m_jsUnitGenerator->lookupName(nameIndex)) + u"]"_s;

    return mapLookup + u" = "_s
            + conversion(property, m_typeResolver->varType(), variableIn)
            + u";\n"_s;
}

void QQmlJSCodeGenerator::generate_GetLookupHelper(int index)
{
    if (m_state.accumulatorOut().isMethod())
        REJECT(u"lookup of function property."_s);

    if (m_state.accumulatorOut().scope().contains(m_typeResolver->mathObject())) {
        QString name = m_jsUnitGenerator->lookupName(index);

        double value{};
        if (name == u"E") {
            value = std::exp(1.0);
        } else if (name == u"LN10") {
            value = log(10.0);
        } else if (name == u"LN2") {
            value = log(2.0);
        } else if (name == u"LOG10E") {
            value = log10(std::exp(1.0));
        } else if (name == u"LOG2E") {
            value = log2(std::exp(1.0));
        } else if (name == u"PI") {
            value = 3.14159265358979323846;
        } else if (name == u"SQRT1_2") {
            value = std::sqrt(0.5);
        } else if (name == u"SQRT2") {
            value = std::sqrt(2.0);
        } else {
            Q_UNREACHABLE();
        }

        m_body += m_state.accumulatorVariableOut + u" = "_s
                  + conversion(m_typeResolver->realType(), m_state.accumulatorOut(), toNumericString(value))
                  + u";\n"_s;
        return;
    }

    if (m_state.accumulatorOut().isImportNamespace()) {
        Q_ASSERT(m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ModulePrefix);
        // If we have an object module prefix, we need to pass through the original object.
        if (m_state.accumulatorVariableIn != m_state.accumulatorVariableOut) {
            m_body += m_state.accumulatorVariableOut + u" = "_s
                    + conversion(m_state.accumulatorIn(), m_state.accumulatorOut(),
                                 m_state.accumulatorVariableIn)
                    + u";\n"_s;
        }
        return;
    }

    AccumulatorConverter registers(this);

    if (m_state.accumulatorOut().isEnumeration()) {
        generateEnumLookup(index);
        return;
    }

    const QString indexString = QString::number(index);
    const QString namespaceString = m_state.accumulatorIn().isImportNamespace()
            ? QString::number(m_state.accumulatorIn().importNamespace())
            : u"QQmlPrivate::AOTCompiledContext::InvalidStringId"_s;
    const auto accumulatorIn = m_state.accumulatorIn();
    const QQmlJSRegisterContent scope = m_state.accumulatorOut().scope();
    const QQmlJSRegisterContent originalScope
            = scope.original().isValid() ? scope.original() : scope;
    const bool isReferenceType = originalScope.containedType()->isReferenceType();

    switch (m_state.accumulatorOut().variant()) {
    case QQmlJSRegisterContent::Attachment: {
        if (isQmlScopeObject(m_state.accumulatorOut().attachee())) {
            generateTypeLookup(index);
            return;
        }
        if (!isReferenceType) {
            // This can happen on incomplete type information. We contextually know that the
            // type must be a QObject, but we cannot construct the inheritance chain. Then we
            // store it in a generic type. Technically we could even convert it to QObject*, but
            // that would be expensive.
            REJECT(u"attached object for non-QObject type"_s);
        }

        if (!m_state.accumulatorIn().storedType()->isReferenceType()) {
            // This can happen if we retroactively determine that the property might not be
            // what we think it is (ie, it can be shadowed).
            REJECT(u"attached object of potentially non-QObject base"_s);
        }

        rejectIfNonQObjectOut(u"non-QObject attached type"_s);

        const QString lookup = u"aotContext->loadAttachedLookup("_s + indexString
                + u", "_s + m_state.accumulatorVariableIn
                + u", &"_s + m_state.accumulatorVariableOut + u')';
        const QString initialization = u"aotContext->initLoadAttachedLookup("_s
                + indexString + u", "_s + namespaceString + u", "_s
                + m_state.accumulatorVariableIn + u')';
        generateLookup(lookup, initialization);
        return;
    }
    case QQmlJSRegisterContent::Singleton:
    case QQmlJSRegisterContent::Script:
    case QQmlJSRegisterContent::MetaType: {
        generateTypeLookup(index);
        return;
    }
    default:
        break;
    }

    Q_ASSERT(m_state.accumulatorOut().isProperty());

    if (accumulatorIn.isStoredIn(m_typeResolver->jsValueType())) {
        REJECT(u"lookup in QJSValue"_s);
    } else if (isReferenceType) {
        const QString inputPointer = resolveQObjectPointer(
                    scope.containedType(), accumulatorIn, m_state.accumulatorVariableIn,
                    u"Cannot read property '%1' of %2"_s.arg(
                        m_jsUnitGenerator->lookupName(index)));
        const QString lookup = u"aotContext->getObjectLookup("_s + indexString
                + u", "_s + inputPointer + u", "_s
                + contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut) + u')';
        const QString initialization = (m_state.isShadowable()
                                                ? u"aotContext->initGetObjectLookupAsVariant("_s
                                                : u"aotContext->initGetObjectLookup("_s)
                + indexString + u", "_s + inputPointer + u')';
        const QString preparation = getLookupPreparation(
                    m_state.accumulatorOut(), m_state.accumulatorVariableOut, index);
        recordPropertyLookup(m_typeResolver->originalContainedType(m_state.accumulatorOut().scope()),
                             originalProperty(m_state.accumulatorOut()));
        generateLookup(lookup, initialization, preparation);
    } else if ((originalScope.containedType()->accessSemantics()
                        == QQmlJSScope::AccessSemantics::Sequence
                    || originalScope.contains(m_typeResolver->stringType()))
               && m_jsUnitGenerator->lookupName(index) == u"length"_s) {
        const QQmlJSScope::ConstPtr stored = accumulatorIn.storedType();
        if (stored->isListProperty()) {
            m_body += m_state.accumulatorVariableOut + u" = "_s;
            m_body += conversion(
                        originalType(m_state.accumulatorOut()),
                        m_state.accumulatorOut(),
                        m_state.accumulatorVariableIn + u".count("_s + u'&'
                            + m_state.accumulatorVariableIn + u')');
            m_body += u";\n"_s;
        } else if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
                   || stored == m_typeResolver->stringType()) {
            m_body += m_state.accumulatorVariableOut + u" = "_s
                    + conversion(originalType(m_state.accumulatorOut()),
                                 m_state.accumulatorOut(),
                                 m_state.accumulatorVariableIn + u".length()"_s)
                    + u";\n"_s;
        } else if (originalScope.contains(m_typeResolver->stringType())) {
            m_body += m_state.accumulatorVariableOut + u" = "_s
                    + conversion(
                              m_typeResolver->sizeType(), m_state.accumulatorOut(),
                              conversion(m_state.accumulatorIn(), m_typeResolver->stringType(),
                                         m_state.accumulatorVariableIn)
                                      + u".length()"_s)
                    + u";\n"_s;
        } else {
            REJECT(u"access to 'length' property of sequence wrapped in non-sequence"_s);
        }
    } else if (accumulatorIn.isStoredIn(m_typeResolver->variantMapType())) {
        m_body += generateVariantMapGetLookup(m_state.accumulatorVariableIn, index);
    } else {
        if (m_state.isRegisterAffectedBySideEffects(Accumulator))
            REJECT(u"reading from a value that's potentially affected by side effects"_s);

        const QString inputContentPointer = resolveValueTypeContentPointer(
                    scope.containedType(), accumulatorIn, m_state.accumulatorVariableIn,
                    u"Cannot read property '%1' of %2"_s.arg(
                        m_jsUnitGenerator->lookupName(index)));

        if (scope.contains(m_typeResolver->variantMapType())) {
            m_body += generateVariantMapGetLookup(
                    u"(*static_cast<const QVariantMap *>("_s
                            + inputContentPointer + u"))"_s, index);
            return;
        }

        const QString lookup = u"aotContext->getValueLookup("_s + indexString
                + u", "_s + inputContentPointer
                + u", "_s + contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut)
                + u')';
        const QString initialization = u"aotContext->initGetValueLookup("_s
                + indexString + u", "_s
                + metaObject(scope.containedType()) + u')';
        const QString preparation = getLookupPreparation(
                m_state.accumulatorOut(), m_state.accumulatorVariableOut, index);
        recordPropertyLookup(m_typeResolver->originalContainedType(scope),
                             originalProperty(m_state.accumulatorOut()));
        generateLookup(lookup, initialization, preparation);
    }
}

void QQmlJSCodeGenerator::generate_GetOptionalLookup(int index, int offset)
{
    INJECT_TRACE_INFO(generate_GetOptionalLookup);

    const QQmlJSRegisterContent accumulatorIn = m_state.accumulatorIn();
    QString accumulatorVarIn = m_state.accumulatorVariableIn;

    const auto &annotation = m_annotations[currentInstructionOffset()];
    if (accumulatorIn.storedType()->isReferenceType()) {
        m_body += u"if (!%1)\n"_s.arg(accumulatorVarIn);
        generateJumpCodeWithTypeConversions(offset);
    } else if (accumulatorIn.isStoredIn(m_typeResolver->varType())) {
        m_body += u"if (!%1.isValid() || ((%1.metaType().flags() & QMetaType::PointerToQObject) "
                  "&& %1.value<QObject *>() == nullptr))\n"_s.arg(accumulatorVarIn);
        generateJumpCodeWithTypeConversions(offset);
    } else if (accumulatorIn.isStoredIn(m_typeResolver->jsPrimitiveType())) {
        m_body += u"if (%1.equals(QJSPrimitiveUndefined()) "
                  "|| %1.equals(QJSPrimitiveNull()))\n"_s.arg(accumulatorVarIn);
        generateJumpCodeWithTypeConversions(offset);
    } else if (annotation.changedRegisterIndex == Accumulator
               && annotation.changedRegister.variant() == QQmlJSRegisterContent::Enum) {
        // Nothing
    } else if (accumulatorIn.isStoredIn(m_typeResolver->jsValueType())) {
        m_body += u"if (%1.isNull() || %1.isUndefined())\n"_s.arg(accumulatorVarIn);
        generateJumpCodeWithTypeConversions(offset);
    } else if (!m_typeResolver->canHoldUndefined(accumulatorIn.storage())) {
        // The base cannot hold undefined and isn't a reference type, generate a regular get lookup
    } else {
        Q_UNREACHABLE(); // No other accumulatorIn stored types should be possible
    }

    generate_GetLookupHelper(index);
}

void QQmlJSCodeGenerator::generate_StoreProperty(int nameIndex, int baseReg)
{
    Q_UNUSED(nameIndex)
    Q_UNUSED(baseReg)
    REJECT(u"StoreProperty"_s);
}

// TODO: This shouldn't be necessary. If the content can be stored directly, then it should
//       be stored and used directly. If it cannot be stored directly, it should be stored
//       as QVariant, but then we cannot dereference the content pointer either.
static QString derefContentPointer(const QString &contentPointer)
{
    Q_ASSERT(contentPointer.startsWith(u'&') || contentPointer[0].isLetterOrNumber());
    return contentPointer.startsWith(u'&') ? contentPointer.mid(1) : (u'*' + contentPointer);
}

void QQmlJSCodeGenerator::generate_SetLookup(int index, int baseReg)
{
    INJECT_TRACE_INFO(generate_SetLookup);

    const QString indexString = QString::number(index);
    const QQmlJSScope::ConstPtr valueType = m_state.accumulatorIn().storedType();
    const QQmlJSRegisterContent property = m_state.readAccumulator();
    Q_ASSERT(property.isConversion());
    const QQmlJSRegisterContent original
            = m_typeResolver->original(property.conversionResultScope());
    const QQmlJSScope::ConstPtr originalScope = original.containedType();

    const QString &propertyName = m_jsUnitGenerator->lookupName(index);
    if (property.storedType().isNull()) {
        REJECT(u"SetLookup. Could not find property " + propertyName + u" on type "
               + originalScope->internalName());
    }

    const QString object = registerVariable(baseReg);
    m_body += u"{\n"_s;
    QString variableIn;
    if (!m_state.accumulatorIn().contains(property.containedType())) {
        m_body += property.storedType()->augmentedInternalName() + u" converted = "_s
                + conversion(m_state.accumulatorIn(), property, consumedAccumulatorVariableIn())
                + u";\n"_s;
        variableIn = contentPointer(property, u"converted"_s);
    } else {
        variableIn = contentPointer(property, m_state.accumulatorVariableIn);
    }

    switch (originalScope->accessSemantics()) {
    case QQmlJSScope::AccessSemantics::Reference: {
        const QString basePointer = resolveQObjectPointer(
                    originalScope, registerType(baseReg), object,
                    u"TypeError: Value is %1 and could not be converted to an object"_s);

        const QString lookup = u"aotContext->setObjectLookup("_s + indexString
                + u", "_s + basePointer + u", "_s + variableIn + u')';

        // We use the asVariant lookup also for non-shadowable properties if the input can hold
        // undefined since that may be a reset. See QQmlJSTypePropagator::generate_StoreProperty().
        const QString initialization
                = (property.contains(m_typeResolver->varType())
                                                ? u"aotContext->initSetObjectLookupAsVariant("_s
                                                : u"aotContext->initSetObjectLookup("_s)
                + indexString + u", "_s + basePointer + u')';
        recordPropertyLookup(originalScope, originalScope->property(propertyName));
        generateLookup(lookup, initialization);
        break;
    }
    case QQmlJSScope::AccessSemantics::Sequence: {
        if (propertyName != u"length"_s)
            REJECT(u"setting non-length property on a sequence type"_s);

        if (!originalScope->isListProperty())
            REJECT(u"resizing sequence types (because of missing write-back)"_s);

        // We can resize without write back on a list property because it's actually a reference.
        m_body += u"const int begin = "_s + object + u".count(&" + object + u");\n"_s;
        m_body += u"const int end = "_s + derefContentPointer(variableIn) + u";\n"_s;
        m_body += u"for (int i = begin; i < end; ++i)\n"_s;
        m_body += u"    "_s + object + u".append(&"_s + object + u", nullptr);\n"_s;
        m_body += u"for (int i = begin; i > end; --i)\n"_s;
        m_body += u"    "_s + object + u".removeLast(&"_s + object + u')'
                + u";\n"_s;
        break;
    }
    case QQmlJSScope::AccessSemantics::Value: {
        const QQmlJSRegisterContent base = registerType(baseReg);
        if (base.isStoredIn(m_typeResolver->variantMapType())) {
            m_body += generateVariantMapSetLookup(
                    registerVariable(baseReg), index, property.storedType(),
                    derefContentPointer(variableIn));
            generateWriteBack(baseReg);
            break;
        }
        const QString baseContentPointer = resolveValueTypeContentPointer(
                    originalScope, base, object,
                    u"TypeError: Value is %1 and could not be converted to an object"_s);

        if (original.contains(m_typeResolver->variantMapType())) {
            m_body += generateVariantMapSetLookup(
                    u"(*static_cast<const QVariantMap *>("_s
                            + baseContentPointer + u"))"_s, index, property.storedType(),
                    derefContentPointer(variableIn));
            generateWriteBack(baseReg);
            break;
        }

        const QString lookup = u"aotContext->setValueLookup("_s + indexString
                + u", "_s + baseContentPointer
                + u", "_s + variableIn + u')';

        // We use the asVariant lookup also for non-shadowable properties if the input can hold
        // undefined since that may be a reset. See QQmlJSTypePropagator::generate_StoreProperty().
        const QString initialization
                = (property.contains(m_typeResolver->varType())
                           ? u"aotContext->initSetValueLookupAsVariant("_s
                           : u"aotContext->initSetValueLookup("_s)
                + indexString + u", "_s + metaObject(originalScope) + u')';
        recordPropertyLookup(m_typeResolver->originalContainedType(base),
                             originalScope->property(propertyName));
        generateLookup(lookup, initialization);
        generateWriteBack(baseReg);

        break;
    }
    case QQmlJSScope::AccessSemantics::None:
        Q_UNREACHABLE();
        break;
    }

    m_body += u"}\n"_s;
}

void QQmlJSCodeGenerator::generate_LoadSuperProperty(int property)
{
    Q_UNUSED(property)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_StoreSuperProperty(int property)
{
    Q_UNUSED(property)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_Yield()
{
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_YieldStar()
{
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_Resume(int)
{
    BYTECODE_UNIMPLEMENTED();
}

QString QQmlJSCodeGenerator::initAndCall(
        int argc, int argv, const QString &callMethodTemplate, const QString &initMethodTemplate,
        QString *outVar)
{
    QString args;

    if (m_state.changedRegisterIndex() == InvalidRegister ||
            m_state.accumulatorOut().contains(m_typeResolver->voidType())) {
        args = u"nullptr"_s;
    } else {
        *outVar = u"callResult"_s;
        const QQmlJSScope::ConstPtr outType = m_state.accumulatorOut().storedType();
        m_body += outType->augmentedInternalName() + u' ' + *outVar;
        m_body += u";\n";

        args = contentPointer(m_state.accumulatorOut(), *outVar);
    }

    // We may need to convert the arguments to the function call so that they match what the
    // function expects. They are passed as void* after all. We try to convert them where they
    // are created, but if they are read as different types in multiple places, we can't.
    QString argumentPreparation;
    for (int i = 0; i < argc; ++i) {
        if (isRegisterAffectedBySideEffects(argv + i))
            REJECT<QString>(u"calling method with argument affected by side effects"_s);
        const QQmlJSRegisterContent content = registerType(argv + i);
        const QQmlJSRegisterContent read = m_state.readRegister(argv + i);
        if (read.contains(content.containedType())) {
            args += u", "_s + contentPointer(read, registerVariable(argv + i));
        } else {
            const QString var = u"arg"_s + QString::number(i);
            argumentPreparation +=
                    u"    "_s + read.storedType()->augmentedInternalName() + u' ' + var + u" = "_s
                    + conversion(content, read, consumedRegisterVariable(argv + i)) + u";\n";
            args += u", "_s + contentPointer(read, var);
        }
    }

    QString initMethod;

    if (m_state.isShadowable()) {
        initMethod = initMethodTemplate;
    } else {
        const QQmlJSMetaMethod method = m_state.accumulatorOut().methodCall();
        Q_ASSERT(!method.isConstructor());

        const QQmlJSMetaMethod::RelativeFunctionIndex relativeMethodIndex =
                method.isJavaScriptFunction() ? method.jsFunctionIndex() : method.methodIndex();
        initMethod = initMethodTemplate.arg(int(relativeMethodIndex));
    }

    return u"const auto doCall = [&]() {\n"_s
            + argumentPreparation
            + u"    void *args[] = {" + args + u"};\n"_s
            + u"    return aotContext->"_s + callMethodTemplate.arg(u"args"_s).arg(argc) + u";\n"
            + u"};\n"_s
            + u"const auto doInit = [&]() {\n"_s
            + u"    aotContext->"_s + initMethod + u";\n"
            + u"};\n"_s;
}

void QQmlJSCodeGenerator::generateMoveOutVarAfterCall(const QString &outVar)
{
    if (m_state.accumulatorVariableOut.isEmpty() || outVar.isEmpty())
        return;

    m_body += m_state.accumulatorVariableOut + u" = "_s + u"std::move(" + outVar + u");\n";
    m_body += u"aotContext->setImplicitDestructible("_s
            + m_state.accumulatorVariableOut + u");\n"_s;
}

void QQmlJSCodeGenerator::generate_CallValue(int name, int argc, int argv)
{
    Q_UNUSED(name)
    Q_UNUSED(argc)
    Q_UNUSED(argv)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_CallWithReceiver(int name, int thisObject, int argc, int argv)
{
    Q_UNUSED(name)
    Q_UNUSED(thisObject)
    Q_UNUSED(argc)
    Q_UNUSED(argv)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_CallProperty(int nameIndex, int baseReg, int argc, int argv)
{
    Q_UNUSED(nameIndex);
    Q_UNUSED(baseReg);
    Q_UNUSED(argc);
    Q_UNUSED(argv);
    REJECT(u"CallProperty"_s);
}

bool QQmlJSCodeGenerator::inlineStringMethod(const QString &name, int base, int argc, int argv)
{
    if (name != u"arg"_s || argc != 1)
        return false;

    const auto arg = [&](const QQmlJSScope::ConstPtr &type) {
        return convertStored(registerType(argv).storedType(), type, consumedRegisterVariable(argv));
    };

    const auto ret = [&](const QString &arg) {
        const QString expression = convertStored(
                    registerType(base).storedType(), m_typeResolver->stringType(),
                    consumedRegisterVariable(base)) + u".arg("_s + arg + u')';
        return conversion(
                m_typeResolver->stringType(), m_state.accumulatorOut(), expression);
    };

    const QQmlJSRegisterContent input = m_state.readRegister(argv);
    m_body += m_state.accumulatorVariableOut + u" = "_s;

    if (m_typeResolver->isNumeric(input))
        m_body += ret(arg(input.containedType()));
    else if (input.contains(m_typeResolver->boolType()))
        m_body += ret(arg(m_typeResolver->boolType()));
    else
        m_body += ret(arg(m_typeResolver->stringType()));
    m_body += u";\n"_s;
    return true;
}

bool QQmlJSCodeGenerator::inlineTranslateMethod(const QString &name, int argc, int argv)
{
    addInclude(u"QtCore/qcoreapplication.h"_s);

    const auto arg = [&](int i, const QQmlJSScope::ConstPtr &type) {
        Q_ASSERT(i < argc);
        return convertStored(registerType(argv + i).storedType(), type,
                             consumedRegisterVariable(argv + i));
    };

    const auto stringArg = [&](int i) {
        return i < argc
                ? (arg(i, m_typeResolver->stringType()) + u".toUtf8().constData()"_s)
                : u"\"\""_s;
    };

    const auto intArg = [&](int i) {
        return i < argc ? arg(i, m_typeResolver->int32Type()) : u"-1"_s;
    };

    const auto stringRet = [&](const QString &expression) {
        return conversion(
                m_typeResolver->stringType(), m_state.accumulatorOut(), expression);
    };

    const auto capture = [&]() {
        m_body += u"aotContext->captureTranslation();\n"_s;
    };

    if (name == u"QT_TRID_NOOP"_s || name == u"QT_TR_NOOP"_s) {
        Q_ASSERT(argc > 0);
        m_body += m_state.accumulatorVariableOut + u" = "_s
                + stringRet(arg(0, m_typeResolver->stringType())) + u";\n"_s;
        return true;
    }

    if (name == u"QT_TRANSLATE_NOOP"_s) {
        Q_ASSERT(argc > 1);
        m_body += m_state.accumulatorVariableOut + u" = "_s
                + stringRet(arg(1, m_typeResolver->stringType())) + u";\n"_s;
        return true;
    }

    if (name == u"qsTrId"_s) {
        capture();
        // We inline qtTrId() here because in the !QT_CONFIG(translation) case it's unavailable.
        // QCoreApplication::translate() is always available in some primitive form.
        // Also, this saves a function call.
        m_body += m_state.accumulatorVariableOut + u" = "_s
                + stringRet(u"QCoreApplication::translate(nullptr, "_s + stringArg(0) +
                            u", nullptr, "_s + intArg(1) + u")"_s) + u";\n"_s;
        return true;
    }

    if (name == u"qsTr"_s) {
        capture();
        m_body += m_state.accumulatorVariableOut + u" = "_s
                + stringRet(u"QCoreApplication::translate("_s
                            + u"aotContext->translationContext().toUtf8().constData(), "_s
                            + stringArg(0) + u", "_s + stringArg(1) + u", "_s
                            + intArg(2) + u")"_s) + u";\n"_s;
        return true;
    }

    if (name == u"qsTranslate"_s) {
        capture();
        m_body += m_state.accumulatorVariableOut + u" = "_s
                + stringRet(u"QCoreApplication::translate("_s
                            + stringArg(0) + u", "_s + stringArg(1) + u", "_s
                            + stringArg(2) + u", "_s + intArg(3) + u")"_s) + u";\n"_s;
        return true;
    }

    return false;
}

static QString maxExpression(int argc)
{
    Q_ASSERT_X(argc >= 2, Q_FUNC_INFO, "max() expects at least two arguments.");

    QString expression =
            u"[&]() { \nauto  tmpMax = (qIsNull(arg2) && qIsNull(arg1) && std::copysign(1.0, arg2) == 1) ? arg2 : ((arg2 > arg1 || std::isnan(arg2)) ? arg2 : arg1);\n"_s;
    for (int i = 2; i < argc; i++) {
        expression +=
                "\ttmpMax = (qIsNull(%1) && qIsNull(tmpMax) && std::copysign(1.0, %1) == 1) ? arg2 : ((%1 > tmpMax || std::isnan(%1)) ? %1 : tmpMax);\n"_L1
                        .arg("arg"_L1 + QString::number(i + 1));
    }
    expression += "return tmpMax;\n}()"_L1;

    return expression;
}

static QString minExpression(int argc)
{
    Q_ASSERT_X(argc >= 2, Q_FUNC_INFO, "min() expects at least two arguments.");

    QString expression =
            u"[&]() { \nauto  tmpMin = (qIsNull(arg2) && qIsNull(arg1) && std::copysign(1.0, arg2) == -1) ? arg2 : ((arg2 < arg1 || std::isnan(arg2)) ? arg2 : arg1);\n"_s;
    for (int i = 2; i < argc; i++) {
        expression +=
                "tmpMin = (qIsNull(%1) && qIsNull(tmpMin) && std::copysign(1.0, %1) == -1) ? arg2 : ((%1 < tmpMin || std::isnan(%1)) ? %1 : tmpMin);\n"_L1
                        .arg("arg"_L1 + QString::number(i + 1));
    }
    expression += "return tmpMin;\n}()"_L1;

    return expression;
}

bool QQmlJSCodeGenerator::inlineMathMethod(const QString &name, int argc, int argv)
{
    addInclude(u"cmath"_s);
    addInclude(u"limits"_s);
    addInclude(u"QtCore/qalgorithms.h"_s);
    addInclude(u"QtCore/qrandom.h"_s);
    addInclude(u"QtQml/qjsprimitivevalue.h"_s);

    // If the result is not stored, we don't need to generate any code. All the math methods are
    // conceptually pure functions.
    if (m_state.changedRegisterIndex() != Accumulator)
        return true;

    m_body += u"{\n"_s;
    for (int i = 0; i < argc; ++i) {
        m_body += u"const double arg%1 = "_s.arg(i + 1) + convertStored(
                        registerType(argv + i).storedType(),
                        m_typeResolver->realType(), consumedRegisterVariable(argv + i))
                + u";\n"_s;
    }

    const QString qNaN = u"std::numeric_limits<double>::quiet_NaN()"_s;
    const QString inf = u"std::numeric_limits<double>::infinity()"_s;
    m_body += m_state.accumulatorVariableOut + u" = "_s;

    QString expression;

    if (name == u"abs" && argc == 1) {
        expression = u"(qIsNull(arg1) ? 0 : (arg1 < 0.0 ? -arg1 : arg1))"_s;
    } else if (name == u"acos"_s && argc == 1) {
        expression = u"arg1 > 1.0 ? %1 : std::acos(arg1)"_s.arg(qNaN);
    } else if (name == u"acosh"_s && argc == 1) {
        expression = u"arg1 < 1.0 ? %1 : std::acosh(arg1)"_s.arg(qNaN);
    } else if (name == u"asin"_s && argc == 1) {
        expression = u"arg1 > 1.0 ? %1 : std::asin(arg1)"_s.arg(qNaN);
    } else if (name == u"asinh"_s && argc == 1) {
        expression = u"qIsNull(arg1) ? arg1 : std::asinh(arg1)"_s;
    } else if (name == u"atan"_s && argc == 1) {
        expression = u"qIsNull(arg1) ? arg1 : std::atan(arg1)"_s;
    } else if (name == u"atanh"_s && argc == 1) {
        expression = u"qIsNull(arg1) ? arg1 : std::atanh(arg1)"_s;
    } else if (name == u"atan2"_s) {
        // TODO: complicated
        return false;
    } else if (name == u"cbrt"_s && argc == 1) {
        expression = u"std::cbrt(arg1)"_s;
    } else if (name == u"ceil"_s && argc == 1) {
        expression = u"(arg1 < 0.0 && arg1 > -1.0) ? std::copysign(0.0, -1.0) : std::ceil(arg1)"_s;
    } else if (name == u"clz32"_s && argc == 1) {
        expression = u"qint32(qCountLeadingZeroBits(quint32(QJSNumberCoercion::toInteger(arg1))))"_s;
    } else if (name == u"cos"_s && argc == 1) {
        expression = u"std::cos(arg1)"_s;
    } else if (name == u"cosh"_s && argc == 1) {
        expression = u"std::cosh(arg1)"_s;
    } else if (name == u"exp"_s && argc == 1) {
        expression = u"std::isinf(arg1) "
                "? (std::copysign(1.0, arg1) == -1 ? 0.0 : %1) "
                ": std::exp(arg1)"_s.arg(inf);
    } else if (name == u"expm1"_s) {
        // TODO: complicated
        return false;
    } else if (name == u"floor"_s && argc == 1) {
        expression = u"std::floor(arg1)"_s;
    } else if (name == u"fround"_s && argc == 1) {
        expression = u"(std::isnan(arg1) || std::isinf(arg1) || qIsNull(arg1)) "
                "? arg1 "
                ": double(float(arg1))"_s;
    } else if (name == u"hypot"_s) {
        // TODO: complicated
        return false;
    } else if (name == u"imul"_s && argc == 2) {
        expression = u"qint32(quint32(QJSNumberCoercion::toInteger(arg1)) "
                "* quint32(QJSNumberCoercion::toInteger(arg2)))"_s;
    } else if (name == u"log"_s && argc == 1) {
        expression = u"arg1 < 0.0 ? %1 : std::log(arg1)"_s.arg(qNaN);
    } else if (name == u"log10"_s && argc == 1) {
        expression = u"arg1 < 0.0 ? %1 : std::log10(arg1)"_s.arg(qNaN);
    } else if (name == u"log1p"_s && argc == 1) {
        expression = u"arg1 < -1.0 ? %1 : std::log1p(arg1)"_s.arg(qNaN);
    } else if (name == u"log2"_s && argc == 1) {
        expression = u"arg1 < -0.0 ? %1 : std::log2(arg1)"_s.arg(qNaN);
    } else if (name == u"max"_s && argc >= 2) {
        expression = maxExpression(argc);
    } else if (name == u"min"_s && argc >= 2) {
        expression = minExpression(argc);
    } else if (name == u"pow"_s) {
        expression = u"QQmlPrivate::jsExponentiate(arg1, arg2)"_s;
    } else if (name == u"random"_s && argc == 0) {
        expression = u"QRandomGenerator::global()->generateDouble()"_s;
    } else if (name == u"round"_s && argc == 1) {
        expression = u"std::isfinite(arg1) "
                "? ((arg1 < 0.5 && arg1 >= -0.5) "
                    "? std::copysign(0.0, arg1) "
                    ": std::floor(arg1 + 0.5)) "
                ": arg1"_s;
    } else if (name == u"sign"_s && argc == 1) {
        expression = u"std::isnan(arg1) "
                "? %1 "
                ": (qIsNull(arg1) "
                    "? arg1 "
                    ": (std::signbit(arg1) ? -1.0 : 1.0))"_s.arg(qNaN);
    } else if (name == u"sin"_s && argc == 1) {
        expression = u"qIsNull(arg1) ? arg1 : std::sin(arg1)"_s;
    } else if (name == u"sinh"_s && argc == 1) {
        expression = u"qIsNull(arg1) ? arg1 : std::sinh(arg1)"_s;
    } else if (name == u"sqrt"_s && argc == 1) {
        expression = u"std::sqrt(arg1)"_s;
    } else if (name == u"tan"_s && argc == 1) {
        expression = u"qIsNull(arg1) ? arg1 : std::tan(arg1)"_s;
    } else if (name == u"tanh"_s && argc == 1) {
        expression = u"qIsNull(arg1) ? arg1 : std::tanh(arg1)"_s;
    } else if (name == u"trunc"_s && argc == 1) {
        expression = u"std::trunc(arg1)"_s;
    } else {
        return false;
    }

    m_body += conversion(m_typeResolver->realType(), m_state.accumulatorOut(), expression);

    m_body += u";\n"_s;
    m_body += u"}\n"_s;
    return true;
}

static QString messageTypeForMethod(const QString &method)
{
    if (method == u"log" || method == u"debug")
        return u"QtDebugMsg"_s;
    if (method == u"info")
        return u"QtInfoMsg"_s;
    if (method == u"warn")
        return u"QtWarningMsg"_s;
    if (method == u"error")
        return u"QtCriticalMsg"_s;
    return QString();
}

bool QQmlJSCodeGenerator::inlineConsoleMethod(const QString &name, int argc, int argv)
{
    const QString type = messageTypeForMethod(name);
    if (type.isEmpty())
        return false;

    addInclude(u"QtCore/qloggingcategory.h"_s);

    m_body += u"{\n";
    m_body += u"    bool firstArgIsCategory = false;\n";
    const QQmlJSRegisterContent firstArg = argc > 0 ? registerType(argv) : QQmlJSRegisterContent();

    // We could check whether the first argument is a QQmlLoggingCategoryBase here, and we should
    // because QQmlLoggingCategoryBase is now a builtin.
    // TODO: The run time check for firstArg is obsolete.
    const bool firstArgIsReference = argc > 0
            && firstArg.containedType()->isReferenceType();

    if (firstArgIsReference) {
        m_body += u"    QObject *firstArg = ";
        m_body += convertStored(
                    firstArg.storedType(),
                    m_typeResolver->genericType(firstArg.storedType()),
                    registerVariable(argv));
        m_body += u";\n";
    }

    m_body += u"    const QLoggingCategory *category = aotContext->resolveLoggingCategory(";
    m_body += firstArgIsReference ? u"firstArg"_sv : u"nullptr"_sv;
    m_body += u", &firstArgIsCategory);\n";
    m_body += u"    if (category && category->isEnabled(" + type + u")) {\n";

    m_body += u"        const QString message = ";

    const auto stringConversion = [&](int i) -> QString {
        const QQmlJSScope::ConstPtr read = m_state.readRegister(argv + i).storedType();
        const QQmlJSScope::ConstPtr actual = registerType(argv + i).storedType();
        if (read == m_typeResolver->stringType()) {
            return convertStored(actual, read, consumedRegisterVariable(argv + i));
        } else if (actual->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
            addInclude(u"QtQml/qjslist.h"_s);
            return u"(u'[' + QJSList(&"_s + registerVariable(argv + i)
                    + u", aotContext->engine).toString() + u']')"_s;
        } else {
            REJECT<QString>(u"converting arguments for console method to string"_s);
        }
    };

    if (argc > 0) {
        if (firstArgIsReference) {
            const QString firstArgStringConversion = convertStored(
                    registerType(argv).storedType(),
                    m_typeResolver->stringType(), registerVariable(argv));
            m_body += u"(firstArgIsCategory ? QString() : (" + firstArgStringConversion;
            if (argc > 1)
                m_body += u".append(QLatin1Char(' ')))).append(";
            else
                m_body += u"))";
        } else {
            m_body += stringConversion(0);
            if (argc > 1)
                m_body += u".append(QLatin1Char(' ')).append(";
        }

        for (int i = 1; i < argc; ++i) {
            if (i > 1)
                m_body += u".append(QLatin1Char(' ')).append("_s;
            m_body += stringConversion(i) + u')';
        }
    } else {
        m_body += u"QString()";
    }
    m_body += u";\n        ";
    generateSetInstructionPointer();
    m_body += u"        aotContext->writeToConsole(" + type + u", message, category);\n";
    m_body += u"    }\n";
    m_body += u"}\n";
    return true;
}

bool QQmlJSCodeGenerator::inlineArrayMethod(const QString &name, int base, int argc, int argv)
{
    const auto intType = m_typeResolver->int32Type();
    const auto elementType = registerType(base).storedType()->elementType();
    const auto boolType = m_typeResolver->boolType();
    const auto stringType = m_typeResolver->stringType();
    const auto baseType = registerType(base);

    const QString baseVar = registerVariable(base);
    const QString qjsListMethod = u"QJSList(&"_s + baseVar + u", aotContext->engine)."
            + name + u"(";

    addInclude(u"QtQml/qjslist.h"_s);

    if (name == u"includes" && argc > 0 && argc < 3) {
        QString call = qjsListMethod
                + convertStored(registerType(argv).storedType(), elementType,
                                consumedRegisterVariable(argv));
        if (argc == 2) {
            call += u", " + convertStored(registerType(argv + 1).storedType(), intType,
                                              consumedRegisterVariable(argv + 1));
        }
        call += u")";

        m_body += m_state.accumulatorVariableOut + u" = "_s
                + conversion(boolType, m_state.accumulatorOut(), call) + u";\n"_s;
        return true;
    }

    if (name == u"toString" || (name == u"join" && argc < 2)) {
        QString call = qjsListMethod;
        if (argc == 1) {
            call += convertStored(registerType(argv).storedType(), stringType,
                                  consumedRegisterVariable(argv));
        }
        call += u")";

        m_body += m_state.accumulatorVariableOut + u" = "_s
                + conversion(stringType, m_state.accumulatorOut(), call) + u";\n"_s;
        return true;
    }

    if (name == u"slice" && argc < 3) {
        QString call = qjsListMethod;
        for (int i = 0; i < argc; ++i) {
            if (i > 0)
                call += u", ";
            call += convertStored(registerType(argv + i).storedType(), intType,
                                   consumedRegisterVariable(argv + i));
        }
        call += u")";

        m_body += m_state.accumulatorVariableOut + u" = "_s;
        if (baseType.storedType()->isListProperty())
            m_body += conversion(m_typeResolver->qObjectListType(), m_state.accumulatorOut(), call);
        else
            m_body += conversion(baseType, m_state.accumulatorOut(), call);
        m_body += u";\n"_s;

        return true;
    }

    if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) {
        QString call = qjsListMethod
                + convertStored(registerType(argv).storedType(), elementType,
                                consumedRegisterVariable(argv));
        if (argc == 2) {
            call += u", " + convertStored(registerType(argv + 1).storedType(), intType,
                                          consumedRegisterVariable(argv + 1));
        }
        call += u")";

        m_body += m_state.accumulatorVariableOut + u" = "_s
                + conversion(intType, m_state.accumulatorOut(), call) + u";\n"_s;
        return true;
    }

    return false;
}

void QQmlJSCodeGenerator::generate_CallPropertyLookup(int index, int base, int argc, int argv)
{
    INJECT_TRACE_INFO(generate_CallPropertyLookup);

    const QQmlJSRegisterContent scopeContent = m_state.accumulatorOut().scope();
    const QQmlJSScope::ConstPtr scope = scopeContent.containedType();

    AccumulatorConverter registers(this);

    const QQmlJSRegisterContent baseType = registerType(base);
    const QString name = m_jsUnitGenerator->lookupName(index);

    if (scope == m_typeResolver->mathObject()) {
        if (inlineMathMethod(name, argc, argv))
            return;
    } else if (scope == m_typeResolver->consoleObject()) {
        if (inlineConsoleMethod(name, argc, argv))
            return;
    } else if (scope == m_typeResolver->stringType()) {
        if (inlineStringMethod(name, base, argc, argv))
            return;
    } else if (baseType.storedType()->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
        if (inlineArrayMethod(name, base, argc, argv))
            return;
    }

    if (m_state.accumulatorOut().isJavaScriptReturnValue())
        REJECT(u"call to untyped JavaScript function"_s);

    m_body += u"{\n"_s;
    QString outVar;

    if (scope->isReferenceType()) {
        const QString inputPointer = resolveQObjectPointer(
                scope, baseType, registerVariable(base),
                u"Cannot call method '%1' of %2"_s.arg(name));

        const QString initMethodTemplate = m_state.isShadowable()
                ? u"initCallObjectPropertyLookupAsVariant(%1, %2)"_s
                : u"initCallObjectPropertyLookup(%1, %2, %3)"_s;

        m_body += initAndCall(
                argc, argv,
                u"callObjectPropertyLookup(%1, %2, %3, %4)"_s.arg(index).arg(inputPointer),
                initMethodTemplate.arg(index).arg(inputPointer), &outVar);
    } else {
        const QQmlJSScope::ConstPtr originalScope
                = m_typeResolver->original(scopeContent).containedType();
        const QString inputPointer = resolveValueTypeContentPointer(
                originalScope, baseType, registerVariable(base),
                u"Cannot call method '%1' of %2"_s.arg(name));

        m_body += initAndCall(
                argc, argv,
                u"callValueLookup(%1, %2, %3, %4)"_s.arg(index).arg(inputPointer),
                u"initCallValueLookup(%1, %2, %3)"_s
                        .arg(index).arg(metaObject(originalScope)),
                &outVar);
    }

    const QString lookup = u"doCall()"_s;
    const QString initialization = u"doInit()"_s;
    const QString preparation = getLookupPreparation(m_state.accumulatorOut(), outVar, index);
    recordMethodLookup(m_typeResolver->originalContainedType(m_state.accumulatorOut().scope()),
                       originalMethod(m_state.accumulatorOut()));
    generateLookup(lookup, initialization, preparation);
    generateMoveOutVarAfterCall(outVar);

    m_body += u"}\n"_s;

    if (scope->isReferenceType())
        return;

    const QQmlJSMetaMethod method = m_state.accumulatorOut().methodCall();
    if (!method.isConst())
        generateWriteBack(base);
}

void QQmlJSCodeGenerator::generate_CallName(int name, int argc, int argv)
{
    Q_UNUSED(name);
    Q_UNUSED(argc);
    Q_UNUSED(argv);
    REJECT(u"CallName"_s);
}

void QQmlJSCodeGenerator::generate_CallPossiblyDirectEval(int argc, int argv)
{
    Q_UNUSED(argc)
    Q_UNUSED(argv)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_CallGlobalLookup(int index, int argc, int argv)
{
    Q_UNUSED(index);
    Q_UNUSED(argc);
    Q_UNUSED(argv);
    REJECT(u"CallGlobalLookup"_s);
}

void QQmlJSCodeGenerator::generate_CallQmlContextPropertyLookup(int index, int argc, int argv)
{
    INJECT_TRACE_INFO(generate_CallQmlContextPropertyLookup);

    if (m_state.accumulatorOut().scope().contains(m_typeResolver->jsGlobalObject())) {
        const QString name = m_jsUnitGenerator->stringForIndex(
                m_jsUnitGenerator->lookupNameIndex(index));
        if (inlineTranslateMethod(name, argc, argv))
            return;
    }

    if (m_state.accumulatorOut().isJavaScriptReturnValue())
        REJECT(u"call to untyped JavaScript function"_s);

    AccumulatorConverter registers(this);

    m_body += u"{\n"_s;
    QString outVar;
    m_body += initAndCall(
            argc, argv, u"callQmlContextPropertyLookup(%1, %2, %3)"_s.arg(index),
            u"initCallQmlContextPropertyLookup(%1, %2)"_s.arg(index), &outVar);

    const QString lookup = u"doCall()"_s;
    const QString initialization = u"doInit()"_s;
    const QString preparation = getLookupPreparation(m_state.accumulatorOut(), outVar, index);
    recordMethodLookup(m_typeResolver->originalContainedType(m_state.accumulatorOut().scope()),
                       originalMethod(m_state.accumulatorOut()));
    generateLookup(lookup, initialization, preparation);
    generateMoveOutVarAfterCall(outVar);

    m_body += u"}\n"_s;
}

void QQmlJSCodeGenerator::generate_CallWithSpread(int func, int thisObject, int argc, int argv)
{
    Q_UNUSED(func)
    Q_UNUSED(thisObject)
    Q_UNUSED(argc)
    Q_UNUSED(argv)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_TailCall(int func, int thisObject, int argc, int argv)
{
    Q_UNUSED(func)
    Q_UNUSED(thisObject)
    Q_UNUSED(argc)
    Q_UNUSED(argv)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_Construct(int func, int argc, int argv)
{
    INJECT_TRACE_INFO(generate_Construct);
    Q_UNUSED(func);

    const auto originalResult = originalType(m_state.accumulatorOut());

    if (originalResult.contains(m_typeResolver->dateTimeType())) {
        m_body += m_state.accumulatorVariableOut + u" = ";
        if (argc == 0) {
            m_body += conversion(
                    m_typeResolver->dateTimeType(), m_state.accumulatorOut(),
                    u"QDateTime::currentDateTime()"_s) + u";\n";
            return;
        }

        if (argc == 1 && m_state.readRegister(argv).contains(m_typeResolver->dateTimeType())) {
            m_body += conversion(
                        registerType(argv), m_state.readRegister(argv), registerVariable(argv))
                    + u";\n";
            return;
        }

        QString ctorArgs;
        constexpr int maxArgc = 7; // year, month, day, hours, minutes, seconds, milliseconds
        for (int i = 0; i < std::min(argc, maxArgc); ++i) {
            if (i > 0)
                ctorArgs += u", ";
            ctorArgs += conversion(
                    registerType(argv + i), m_state.readRegister(argv + i),
                    registerVariable(argv + i));
        }
        m_body += conversion(
                m_typeResolver->dateTimeType(), m_state.accumulatorOut(),
                u"aotContext->constructDateTime("_s + ctorArgs + u')') + u";\n";
        return;
    }

    if (originalResult.contains(m_typeResolver->variantListType())) {
        rejectIfBadArray();

        if (argc == 1 && m_state.readRegister(argv).contains(m_typeResolver->realType())) {
            addInclude(u"QtQml/qjslist.h"_s);

            const QString error = u"    aotContext->engine->throwError(QJSValue::RangeError, "_s
                    + u"QLatin1String(\"Invalid array length\"));\n"_s;

            const QString indexName = registerVariable(argv);
            const auto indexType = registerType(argv).containedType();
            if (!m_typeResolver->isNativeArrayIndex(indexType)) {
                m_body += u"if (!QJSNumberCoercion::isArrayIndex("_s + indexName + u")) {\n"_s
                        + error;
                generateReturnError();
                m_body += u"}\n"_s;
            } else if (!m_typeResolver->isUnsignedInteger(indexType)) {
                m_body += u"if ("_s + indexName + u" < 0) {\n"_s
                        + error;
                generateReturnError();
                m_body += u"}\n"_s;
            }

            m_body += m_state.accumulatorVariableOut + u" = "_s
                    + m_state.accumulatorOut().storedType()->internalName() + u"();\n"_s;
            m_body += u"QJSList(&"_s + m_state.accumulatorVariableOut
                    + u", aotContext->engine).resize("_s
                    + convertStored(
                              registerType(argv).storedType(), m_typeResolver->sizeType(),
                              consumedRegisterVariable(argv))
                    + u");\n"_s;
        } else if (!m_logger->currentFunctionHasCompileError()) {
            generateArrayInitializer(argc, argv);
        }
        return;
    }

    const QQmlJSScope::ConstPtr originalContained = originalResult.containedType();
    if (originalContained->isValueType() && originalResult.isMethodCall()) {
        const QQmlJSMetaMethod ctor = originalResult.methodCall();
        if (ctor.isJavaScriptFunction())
            REJECT(u"calling JavaScript constructor "_s + ctor.methodName());

        QList<QQmlJSRegisterContent> argumentTypes;
        QStringList arguments;
        for (int i = 0; i < argc; ++i) {
            argumentTypes.append(registerType(argv + i));
            arguments.append(consumedRegisterVariable(argv + i));
        }

        const QQmlJSScope::ConstPtr extension = originalContained->extensionType().scope;
        const QString result = generateCallConstructor(
                ctor, argumentTypes, arguments, metaType(originalContained),
                metaObject(extension ? extension : originalContained));

        m_body += m_state.accumulatorVariableOut + u" = "_s
                + conversion(m_pool->storedIn(originalResult, m_typeResolver->varType()),
                             m_state.accumulatorOut(), result)
                + u";\n"_s;

        return;
    }


    REJECT(u"Construct"_s);
}

void QQmlJSCodeGenerator::generate_ConstructWithSpread(int func, int argc, int argv)
{
    Q_UNUSED(func)
    Q_UNUSED(argc)
    Q_UNUSED(argv)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_SetUnwindHandler(int offset)
{
    Q_UNUSED(offset)
    REJECT(u"SetUnwindHandler"_s);
}

void QQmlJSCodeGenerator::generate_UnwindDispatch()
{
    REJECT(u"UnwindDispatch"_s);
}

void QQmlJSCodeGenerator::generate_UnwindToLabel(int level, int offset)
{
    Q_UNUSED(level)
    Q_UNUSED(offset)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_DeadTemporalZoneCheck(int name)
{
    Q_UNUSED(name)
    INJECT_TRACE_INFO(generate_DeadTemporalZoneCheck);
    // Nothing to do here. If we have statically asserted the dtz check in the type propagator
    // the value cannot be empty. Otherwise we can't get here.
}

void QQmlJSCodeGenerator::generate_ThrowException()
{
    INJECT_TRACE_INFO(generate_ThrowException);

    generateSetInstructionPointer();
    m_body += u"aotContext->engine->throwError("_s + conversion(
                    m_state.accumulatorIn(),
                    m_typeResolver->jsValueType(),
                    m_state.accumulatorVariableIn) + u");\n"_s;
    generateReturnError();
    m_skipUntilNextLabel = true;
    resetState();
}

void QQmlJSCodeGenerator::generate_GetException()
{
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_SetException()
{
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_CreateCallContext()
{
    INJECT_TRACE_INFO(generate_CreateCallContext);

    m_body += u"{\n"_s;
}

void QQmlJSCodeGenerator::generate_PushCatchContext(int index, int nameIndex)
{
    Q_UNUSED(index)
    Q_UNUSED(nameIndex)
    REJECT(u"PushCatchContext"_s);
}

void QQmlJSCodeGenerator::generate_PushWithContext()
{
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_PushBlockContext(int index)
{
    Q_UNUSED(index)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_CloneBlockContext()
{
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_PushScriptContext(int index)
{
    Q_UNUSED(index)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_PopScriptContext()
{
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_PopContext()
{
    INJECT_TRACE_INFO(generate_PopContext);

    // Add an empty block before the closing brace, in case there was a bare label before it.
    m_body += u"{}\n}\n"_s;
}

void QQmlJSCodeGenerator::generate_GetIterator(int iterator)
{
    INJECT_TRACE_INFO(generate_GetIterator);

    addInclude(u"QtQml/qjslist.h"_s);
    const QQmlJSRegisterContent listType = m_state.accumulatorIn();
    if (!listType.isList())
        REJECT(u"iterator on non-list type"_s);

    const QQmlJSRegisterContent iteratorType = m_state.accumulatorOut();
    if (!iteratorType.isProperty())
        REJECT(u"using non-iterator as iterator"_s);

    const QString identifier = QString::number(iteratorType.baseLookupIndex());
    QString baseName = m_state.accumulatorVariableOut.mid(2); // remove "s."
    const QString iteratorName = baseName + u"Iterator" + identifier;
    const QString listName = baseName + u"List" + identifier;

    m_body += u"QJSListFor"_s
            + (iterator == int(QQmlJS::AST::ForEachType::In) ? u"In"_s : u"Of"_s)
            + u"Iterator "_s + iteratorName + u";\n";
    m_body += m_state.accumulatorVariableOut + u" = &" + iteratorName + u";\n";

    m_body += m_state.accumulatorVariableOut + u"->init(";
    if (iterator == int(QQmlJS::AST::ForEachType::In)) {
        if (!iteratorType.isStoredIn(m_typeResolver->forInIteratorPtr()))
            REJECT(u"using non-iterator as iterator"_s);
        m_body += u"QJSList(&" + m_state.accumulatorVariableIn + u", aotContext->engine)";
    }
    m_body += u");\n";

    if (iterator == int(QQmlJS::AST::ForEachType::Of)) {
        if (!iteratorType.isStoredIn(m_typeResolver->forOfIteratorPtr()))
            REJECT(u"using non-iterator as iterator"_s);
        m_body += u"const auto &" // Rely on life time extension for const refs
                + listName + u" = " + consumedAccumulatorVariableIn();
    }
}

void QQmlJSCodeGenerator::generate_IteratorNext(int value, int offset)
{
    INJECT_TRACE_INFO(generate_IteratorNext);

    Q_ASSERT(value == m_state.changedRegisterIndex());
    const QQmlJSRegisterContent iteratorContent = m_state.accumulatorIn();
    if (!iteratorContent.isProperty())
        REJECT(u"using non-iterator as iterator"_s);

    const QQmlJSScope::ConstPtr iteratorType = iteratorContent.storedType();
    const QString listName = m_state.accumulatorVariableIn
            + u"List" + QString::number(iteratorContent.baseLookupIndex());
    QString qjsList;
    if (iteratorType == m_typeResolver->forOfIteratorPtr())
        qjsList = u"QJSList(&" + listName + u", aotContext->engine)";
    else if (iteratorType != m_typeResolver->forInIteratorPtr())
        REJECT(u"using non-iterator as iterator"_s);

    m_body += u"if (" + m_state.accumulatorVariableIn + u"->hasNext(" + qjsList + u")) {\n    ";

    // We know that this works because we can do ->next() below.
    QQmlJSRegisterContent iteratorValue = m_typeResolver->extractNonVoidFromOptionalType(
            m_typeResolver->original(m_state.changedRegister()));
    iteratorValue = m_pool->storedIn(iteratorValue, iteratorValue.containedType());

    m_body += changedRegisterVariable() + u" = "
            + conversion(
                      iteratorValue, m_state.changedRegister(),
                      m_state.accumulatorVariableIn + u"->next(" + qjsList + u')')
            + u";\n";
    m_body += u"} else {\n    ";
    m_body += changedRegisterVariable() + u" = "
            + conversion(m_typeResolver->voidType(), m_state.changedRegister(), QString());
    m_body += u";\n    ";
    generateJumpCodeWithTypeConversions(offset);
    m_body += u"\n}"_s;
}

void QQmlJSCodeGenerator::generate_IteratorNextForYieldStar(int iterator, int object, int offset)
{
    Q_UNUSED(iterator)
    Q_UNUSED(object)
    Q_UNUSED(offset)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_IteratorClose()
{
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_DestructureRestElement()
{
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_DeleteProperty(int base, int index)
{
    Q_UNUSED(base)
    Q_UNUSED(index)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_DeleteName(int name)
{
    Q_UNUSED(name)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_TypeofName(int name)
{
    Q_UNUSED(name);
    REJECT(u"TypeofName"_s);
}

void QQmlJSCodeGenerator::generate_TypeofValue()
{
    REJECT(u"TypeofValue"_s);
}

void QQmlJSCodeGenerator::generate_DeclareVar(int varName, int isDeletable)
{
    Q_UNUSED(varName)
    Q_UNUSED(isDeletable)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_DefineArray(int argc, int args)
{
    INJECT_TRACE_INFO(generate_DefineArray);

    rejectIfBadArray();
    if (!m_logger->currentFunctionHasCompileError())
        generateArrayInitializer(argc, args);
}

void QQmlJSCodeGenerator::generate_DefineObjectLiteral(int internalClassId, int argc, int args)
{
    INJECT_TRACE_INFO(generate_DefineObjectLiteral);

    const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();
    if (stored->accessSemantics() != QQmlJSScope::AccessSemantics::Value)
        REJECT(u"storing an object literal in a non-value type"_s);

    const QQmlJSScope::ConstPtr contained = m_state.accumulatorOut().containedType();

    const int classSize = m_jsUnitGenerator->jsClassSize(internalClassId);

    // This is not implemented because we cannot statically determine the type of the value and we
    // don't want to rely on QVariant::convert() since that may give different results than
    // the JavaScript coercion. We might still make it work by querying the QMetaProperty
    // for its type at run time and runtime coercing to that, but we don't know whether that
    // still pays off.
    if (argc > classSize)
        REJECT(u"non-literal keys of object literals"_s);

    Q_ASSERT(argc == classSize);

    const auto createVariantMap = [&]() {
        QString result;
        result += u"QVariantMap {\n";
        const QQmlJSScope::ConstPtr propType = m_typeResolver->varType();
        for (int i = 0; i < classSize; ++i) {
            result += u"{ "_s
                    + QQmlJSUtils::toLiteral(m_jsUnitGenerator->jsClassMember(internalClassId, i))
                    + u", "_s;
            const int currentArg = args + i;
            const QQmlJSScope::ConstPtr argType = registerType(currentArg).storedType();
            const QString consumedArg = consumedRegisterVariable(currentArg);
            result += convertStored(argType, propType, consumedArg) + u" },\n";
        }


        result += u"}";
        return result;
    };

    if (contained == m_typeResolver->varType() || contained == m_typeResolver->variantMapType()) {
        m_body += m_state.accumulatorVariableOut + u" = "_s + createVariantMap() + u";\n"_s;
        return;
    }

    if (contained == m_typeResolver->jsValueType()) {
        m_body += m_state.accumulatorVariableOut + u" = aotContext->engine->toScriptValue("_s
                + createVariantMap() + u");\n"_s;
        return;
    }

    m_body += m_state.accumulatorVariableOut + u" = "_s + stored->augmentedInternalName();
    const bool isVariantOrPrimitive = (stored == m_typeResolver->varType())
            || (stored == m_typeResolver->jsPrimitiveType());

    if (m_state.accumulatorOut().contains(stored)) {
        m_body += u"()";
    } else if (isVariantOrPrimitive) {
        m_body += u'(' + metaType(m_state.accumulatorOut().containedType()) + u')';
    } else {
        REJECT(u"storing an object literal in an unsupported container %1"_s
                               .arg(stored->internalName()));
    }
    m_body += u";\n";

    if (argc == 0)
        return;

    bool isExtension = false;
    if (!m_typeResolver->canPopulate(contained, m_typeResolver->variantMapType(), &isExtension))
        REJECT(u"storing an object literal in a non-structured value type"_s);

    const QQmlJSScope::ConstPtr accessor = isExtension
            ? contained->extensionType().scope
            : contained;

    m_body += u"{\n";
    m_body += u"    const QMetaObject *meta = ";
    if (!isExtension && isVariantOrPrimitive)
        m_body += m_state.accumulatorVariableOut + u".metaType().metaObject()";
    else
        m_body += metaObject(accessor);
    m_body += u";\n";

    for (int i = 0; i < classSize; ++i) {
        m_body += u"    {\n";
        const QString propName = m_jsUnitGenerator->jsClassMember(internalClassId, i);
        const int currentArg = args + i;
        const QQmlJSRegisterContent propType = m_state.readRegister(currentArg);
        const QQmlJSRegisterContent argType = registerType(currentArg);
        const QQmlJSMetaProperty property = contained->property(propName);
        const QString consumedArg = consumedRegisterVariable(currentArg);
        QString argument = conversion(argType, propType, consumedArg);

        if (argument == consumedArg) {
            argument = registerVariable(currentArg);
        } else {
            m_body += u"        "_s + propType.storedType()->augmentedInternalName()
                    + u" arg = "_s + argument + u";\n";
            argument = u"arg"_s;
        }

        int index = property.index();
        if (index == -1)
            continue;

        const QString indexString = QString::number(index);
        m_body += u"        void *argv[] = { %1, nullptr };\n"_s
                          .arg(contentPointer(propType, argument));
        m_body += u"        meta->property("_s + indexString;
        m_body += u").enclosingMetaObject()->d.static_metacall(reinterpret_cast<QObject *>(";
        m_body += contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut);
        m_body += u"), QMetaObject::WriteProperty, " + indexString + u", argv);\n";
        m_body += u"    }\n";
    }

    m_body += u"}\n";

}

void QQmlJSCodeGenerator::generate_CreateClass(int classIndex, int heritage, int computedNames)
{
    Q_UNUSED(classIndex)
    Q_UNUSED(heritage)
    Q_UNUSED(computedNames)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_CreateMappedArgumentsObject()
{
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_CreateUnmappedArgumentsObject()
{
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_CreateRestParameter(int argIndex)
{
    Q_UNUSED(argIndex)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_ConvertThisToObject()
{
    INJECT_TRACE_INFO(generate_ConvertThisToObject);

    m_body += changedRegisterVariable() + u" = "_s
            + conversion(m_typeResolver->qObjectType(), m_state.changedRegister(),
                         u"aotContext->thisObject()"_s)
            + u";\n"_s;
}

void QQmlJSCodeGenerator::generate_LoadSuperConstructor()
{
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_ToObject()
{
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_Jump(int offset)
{
    INJECT_TRACE_INFO(generate_Jump);

    generateJumpCodeWithTypeConversions(offset);
    m_skipUntilNextLabel = true;
    resetState();
}

void QQmlJSCodeGenerator::generate_JumpTrue(int offset)
{
    INJECT_TRACE_INFO(generate_JumpTrue);

    m_body += u"if ("_s;
    m_body += convertStored(m_state.accumulatorIn().storedType(), m_typeResolver->boolType(),
                            m_state.accumulatorVariableIn);
    m_body += u") "_s;
    generateJumpCodeWithTypeConversions(offset);
}

void QQmlJSCodeGenerator::generate_JumpFalse(int offset)
{
    INJECT_TRACE_INFO(generate_JumpFalse);

    m_body += u"if (!"_s;
    m_body += convertStored(m_state.accumulatorIn().storedType(), m_typeResolver->boolType(),
                            m_state.accumulatorVariableIn);
    m_body += u") "_s;
    generateJumpCodeWithTypeConversions(offset);
}

void QQmlJSCodeGenerator::generate_JumpNoException(int offset)
{
    INJECT_TRACE_INFO(generate_JumpNoException);

    m_body += u"if (!context->engine->hasException()) "_s;
    generateJumpCodeWithTypeConversions(offset);
}

void QQmlJSCodeGenerator::generate_JumpNotUndefined(int offset)
{
    Q_UNUSED(offset)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_CheckException()
{
    INJECT_TRACE_INFO(generate_CheckException);

    generateExceptionCheck();
}

void QQmlJSCodeGenerator::generate_CmpEqNull()
{
    INJECT_TRACE_INFO(generate_CmpEqNull);
    generateEqualityOperation(literalType(m_typeResolver->nullType()), QString(), u"equals"_s, false);
}

void QQmlJSCodeGenerator::generate_CmpNeNull()
{
    INJECT_TRACE_INFO(generate_CmlNeNull);
    generateEqualityOperation(literalType(m_typeResolver->nullType()), QString(), u"equals"_s, true);
}

QString QQmlJSCodeGenerator::getLookupPreparation(
        QQmlJSRegisterContent content, const QString &var, int lookup)
{
    if (content.contains(content.storedType()))
        return QString();

    if (content.storedType()->isOpaqueType()) {
        // Can't do meta-type lookups if the type is potentially unregistered,
        // which can be the case for mehods
        REJECT<QString>("Can't prepare lookup involving non-registered types"_L1);
    }

    if (content.isStoredIn(m_typeResolver->varType())) {
        return var + u" = QVariant(aotContext->lookupResultMetaType("_s
                + QString::number(lookup) + u"))"_s;
    }

    if (content.isStoredIn(m_typeResolver->jsPrimitiveType())) {
        return var + u" = QJSPrimitiveValue(aotContext->lookupResultMetaType("_s
                + QString::number(lookup) + u"))"_s;
    }

    // TODO: We could make sure they're compatible, for example QObject pointers.
    return QString();
}

QString QQmlJSCodeGenerator::contentPointer(QQmlJSRegisterContent content, const QString &var)
{
    const QQmlJSScope::ConstPtr stored = content.storedType();
    if (content.contains(stored))
        return u'&' + var;

    if (content.isStoredIn(m_typeResolver->varType())
            || content.isStoredIn(m_typeResolver->jsPrimitiveType())) {
        return var + u".data()"_s;
    }

    if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
        return u'&' + var;

    if (m_typeResolver->isNumeric(content.storedType())
        && content.containedType()->scopeType() == QQmlSA::ScopeType::EnumScope) {
        return u'&' + var;
    }

    if (stored->isListProperty() && content.containedType()->isListProperty())
        return u'&' + var;

    REJECT<QString>(
            u"content pointer of unsupported wrapper type "_s + content.descriptiveName());
}

QString QQmlJSCodeGenerator::contentType(QQmlJSRegisterContent content, const QString &var)
{
    const QQmlJSScope::ConstPtr stored = content.storedType();
    const QQmlJSScope::ConstPtr contained = content.containedType();
    if (contained == stored)
        return metaTypeFromType(stored);

    if (stored == m_typeResolver->varType() || stored == m_typeResolver->jsPrimitiveType())
        return var + u".metaType()"_s; // We expect the container to be initialized

    if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
        return metaType(contained);

    const QQmlJSScope::ConstPtr nonComposite = QQmlJSScope::nonCompositeBaseType(contained);
    if (m_typeResolver->isNumeric(stored) && nonComposite->scopeType() == QQmlSA::ScopeType::EnumScope)
        return metaTypeFromType(nonComposite->baseType());

    if (stored->isListProperty() && contained->isListProperty())
        return metaType(contained);

    REJECT<QString>(
            u"content type of unsupported wrapper type "_s + content.descriptiveName());
}

void QQmlJSCodeGenerator::generate_CmpEqInt(int lhsConst)
{
    INJECT_TRACE_INFO(generate_CmpEqInt);

    generateEqualityOperation(
            literalType(m_typeResolver->int32Type()), QString::number(lhsConst), u"equals"_s, false);
}

void QQmlJSCodeGenerator::generate_CmpNeInt(int lhsConst)
{
    INJECT_TRACE_INFO(generate_CmpNeInt);

    generateEqualityOperation(
            literalType(m_typeResolver->int32Type()), QString::number(lhsConst), u"equals"_s, true);
}

void QQmlJSCodeGenerator::generate_CmpEq(int lhs)
{
    INJECT_TRACE_INFO(generate_CmpEq);
    generateEqualityOperation(registerType(lhs), registerVariable(lhs), u"equals"_s, false);
}

void QQmlJSCodeGenerator::generate_CmpNe(int lhs)
{
    INJECT_TRACE_INFO(generate_CmpNe);
    generateEqualityOperation(registerType(lhs), registerVariable(lhs), u"equals"_s, true);
}

void QQmlJSCodeGenerator::generate_CmpGt(int lhs)
{
    INJECT_TRACE_INFO(generate_CmpGt);
    generateCompareOperation(lhs, u">"_s);
}

void QQmlJSCodeGenerator::generate_CmpGe(int lhs)
{
    INJECT_TRACE_INFO(generate_CmpGe);
    generateCompareOperation(lhs, u">="_s);
}

void QQmlJSCodeGenerator::generate_CmpLt(int lhs)
{
    INJECT_TRACE_INFO(generate_CmpLt);
    generateCompareOperation(lhs, u"<"_s);
}

void QQmlJSCodeGenerator::generate_CmpLe(int lhs)
{
    INJECT_TRACE_INFO(generate_CmpLe);
    generateCompareOperation(lhs, u"<="_s);
}

void QQmlJSCodeGenerator::generate_CmpStrictEqual(int lhs)
{
    INJECT_TRACE_INFO(generate_CmpStrictEqual);
    generateEqualityOperation(registerType(lhs), registerVariable(lhs), u"strictlyEquals"_s, false);
}

void QQmlJSCodeGenerator::generate_CmpStrictNotEqual(int lhs)
{
    INJECT_TRACE_INFO(generate_CmpStrictNotEqual);
    generateEqualityOperation(registerType(lhs), registerVariable(lhs), u"strictlyEquals"_s, true);
}

void QQmlJSCodeGenerator::generate_CmpIn(int lhs)
{
    Q_UNUSED(lhs)
    REJECT(u"CmpIn"_s);
}

void QQmlJSCodeGenerator::generate_CmpInstanceOf(int lhs)
{
    Q_UNUSED(lhs)
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_As(int lhs)
{
    INJECT_TRACE_INFO(generate_As);

    const QString input = registerVariable(lhs);
    const QQmlJSRegisterContent inputContent = m_state.readRegister(lhs);
    const QQmlJSRegisterContent outputContent = m_state.accumulatorOut();

    // If the originalType output is a conversion, we're supposed to check for the contained
    // type and if it doesn't match, set the result to null or undefined.
    const QQmlJSRegisterContent originalContent = originalType(outputContent);
    QQmlJSScope::ConstPtr target;
    if (originalContent.containedType()->isReferenceType())
        target = originalContent.containedType();
    else if (originalContent.isConversion())
        target = m_typeResolver->extractNonVoidFromOptionalType(originalContent).containedType();
    else if (originalContent.variant() == QQmlJSRegisterContent::Cast)
        target = originalContent.containedType();

    if (!target)
        REJECT(u"type assertion to unknown type"_s);

    const bool isTrivial = m_typeResolver->inherits(
            m_typeResolver->originalContainedType(inputContent), target);

    m_body += m_state.accumulatorVariableOut + u" = "_s;

    if (!isTrivial && target->isReferenceType()) {
        const QQmlJSScope::ConstPtr genericContained = m_typeResolver->genericType(target);
        const QString inputConversion = inputContent.storedType()->isReferenceType()
                ? input
                : convertStored(inputContent.storedType(), genericContained, input);

        if (target->isComposite()
                && m_state.accumulatorIn().isStoredIn(m_typeResolver->metaObjectType())) {
            m_body += conversion(
                        genericContained, outputContent,
                        m_state.accumulatorVariableIn + u"->cast("_s + inputConversion + u')');
        } else {
            m_body += conversion(
                        genericContained, outputContent,
                        u'(' + metaObject(target) + u")->cast("_s + inputConversion + u')');
        }
        m_body += u";\n"_s;
        return;
    }

    if (inputContent.isStoredIn(m_typeResolver->varType())
            || inputContent.isStoredIn(m_typeResolver->jsPrimitiveType())) {

        const auto source = m_typeResolver->extractNonVoidFromOptionalType(
                originalType(inputContent)).containedType();

        if (source && source == target) {
            m_body += input + u".metaType() == "_s + metaType(target)
                    + u" ? " + conversion(inputContent, outputContent, input)
                    + u" : " + conversion(
                                  literalType(m_typeResolver->voidType()), outputContent, QString());
            m_body += u";\n"_s;
            return;
        }
    }

    if (isTrivial) {
        // No actual conversion necessary. The 'as' is a no-op
        m_body += conversion(inputContent, m_state.accumulatorOut(), input) + u";\n"_s;
        return;
    }

    REJECT(u"non-trivial value type assertion"_s);
}

void QQmlJSCodeGenerator::generate_UNot()
{
    INJECT_TRACE_INFO(generate_UNot);
    generateUnaryOperation(u"!"_s);
}

void QQmlJSCodeGenerator::generate_UPlus()
{
    INJECT_TRACE_INFO(generate_UPlus);
    generateUnaryOperation(u"+"_s);
}

void QQmlJSCodeGenerator::generate_UMinus()
{
    INJECT_TRACE_INFO(generate_UMinus);
    generateUnaryOperation(u"-"_s);
}

void QQmlJSCodeGenerator::generate_UCompl()
{
    INJECT_TRACE_INFO(generate_UCompl);
    generateUnaryOperation(u"~"_s);
}

void QQmlJSCodeGenerator::generate_Increment()
{
    INJECT_TRACE_INFO(generate_Increment);
    generateInPlaceOperation(u"++"_s);
}

void QQmlJSCodeGenerator::generate_Decrement()
{
    INJECT_TRACE_INFO(generate_Decrement);
    generateInPlaceOperation(u"--"_s);
}

void QQmlJSCodeGenerator::generate_Add(int lhs)
{
    INJECT_TRACE_INFO(generate_Add);
    generateArithmeticOperation(lhs, u"+"_s);
}

void QQmlJSCodeGenerator::generate_BitAnd(int lhs)
{
    INJECT_TRACE_INFO(generate_BitAnd);
    generateArithmeticOperation(lhs, u"&"_s);
}

void QQmlJSCodeGenerator::generate_BitOr(int lhs)
{
    INJECT_TRACE_INFO(generate_BitOr);
    generateArithmeticOperation(lhs, u"|"_s);
}

void QQmlJSCodeGenerator::generate_BitXor(int lhs)
{
    INJECT_TRACE_INFO(generate_BitXor);
    generateArithmeticOperation(lhs, u"^"_s);
}

void QQmlJSCodeGenerator::generate_UShr(int lhs)
{
    INJECT_TRACE_INFO(generate_BitUShr);
    generateShiftOperation(lhs, u">>"_s);
}

void QQmlJSCodeGenerator::generate_Shr(int lhs)
{
    INJECT_TRACE_INFO(generate_Shr);
    generateShiftOperation(lhs, u">>"_s);
}

void QQmlJSCodeGenerator::generate_Shl(int lhs)
{
    INJECT_TRACE_INFO(generate_Shl);
    generateShiftOperation(lhs, u"<<"_s);
}

void QQmlJSCodeGenerator::generate_BitAndConst(int rhs)
{
    INJECT_TRACE_INFO(generate_BitAndConst);
    generateArithmeticConstOperation(rhs, u"&"_s);
}

void QQmlJSCodeGenerator::generate_BitOrConst(int rhs)
{
    INJECT_TRACE_INFO(generate_BitOrConst);
    generateArithmeticConstOperation(rhs, u"|"_s);
}

void QQmlJSCodeGenerator::generate_BitXorConst(int rhs)
{
    INJECT_TRACE_INFO(generate_BitXorConst);
    generateArithmeticConstOperation(rhs, u"^"_s);
}

void QQmlJSCodeGenerator::generate_UShrConst(int rhs)
{
    INJECT_TRACE_INFO(generate_UShrConst);
    generateArithmeticConstOperation(rhs & 0x1f, u">>"_s);
}

void QQmlJSCodeGenerator::generate_ShrConst(int rhs)
{
    INJECT_TRACE_INFO(generate_ShrConst);
    generateArithmeticConstOperation(rhs & 0x1f, u">>"_s);
}

void QQmlJSCodeGenerator::generate_ShlConst(int rhs)
{
    INJECT_TRACE_INFO(generate_ShlConst);
    generateArithmeticConstOperation(rhs & 0x1f, u"<<"_s);
}

void QQmlJSCodeGenerator::generate_Exp(int lhs)
{
    INJECT_TRACE_INFO(generate_Exp);

    const QString lhsString = conversion(
                registerType(lhs), m_state.readRegister(lhs), consumedRegisterVariable(lhs));
    const QString rhsString = conversion(
                m_state.accumulatorIn(), m_state.readAccumulator(),
                consumedAccumulatorVariableIn());

    Q_ASSERT(m_logger->currentFunctionHasCompileError() || !lhsString.isEmpty());
    Q_ASSERT(m_logger->currentFunctionHasCompileError() || !rhsString.isEmpty());

    const QQmlJSRegisterContent originalOut = originalType(m_state.accumulatorOut());
    m_body += m_state.accumulatorVariableOut + u" = "_s;
    m_body += conversion(
                originalOut, m_state.accumulatorOut(),
                u"QQmlPrivate::jsExponentiate("_s + lhsString + u", "_s + rhsString + u')');
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generate_Mul(int lhs)
{
    INJECT_TRACE_INFO(generate_Mul);
    generateArithmeticOperation(lhs, u"*"_s);
}

void QQmlJSCodeGenerator::generate_Div(int lhs)
{
    INJECT_TRACE_INFO(generate_Div);
    GeneratePragmaWarningBlock warningBlock(this);
    warningBlock.silenceDivideByZero();
    generateArithmeticOperation(lhs, u"/"_s);
}

void QQmlJSCodeGenerator::generate_Mod(int lhs)
{
    INJECT_TRACE_INFO(generate_Mod);

    const auto lhsVar = convertStored(
                registerType(lhs).storedType(), m_typeResolver->jsPrimitiveType(),
                consumedRegisterVariable(lhs));
    const auto rhsVar = convertStored(
                m_state.accumulatorIn().storedType(), m_typeResolver->jsPrimitiveType(),
                consumedAccumulatorVariableIn());
    Q_ASSERT(m_logger->currentFunctionHasCompileError() || !lhsVar.isEmpty());
    Q_ASSERT(m_logger->currentFunctionHasCompileError() || !rhsVar.isEmpty());

    m_body += m_state.accumulatorVariableOut;
    m_body += u" = "_s;
    m_body += conversion(m_typeResolver->jsPrimitiveType(), m_state.accumulatorOut(),
                       u'(' + lhsVar + u" % "_s + rhsVar + u')');
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generate_Sub(int lhs)
{
    INJECT_TRACE_INFO(generate_Sub);
    generateArithmeticOperation(lhs, u"-"_s);
}

void QQmlJSCodeGenerator::generate_InitializeBlockDeadTemporalZone(int firstReg, int count)
{
    Q_UNUSED(firstReg)
    Q_UNUSED(count)
    // Ignore. We reject uninitialized values anyway.
}

void QQmlJSCodeGenerator::generate_ThrowOnNullOrUndefined()
{
    BYTECODE_UNIMPLEMENTED();
}

void QQmlJSCodeGenerator::generate_GetTemplateObject(int index)
{
    Q_UNUSED(index)
    BYTECODE_UNIMPLEMENTED();
}

QV4::Moth::ByteCodeHandler::Verdict QQmlJSCodeGenerator::startInstruction(
        QV4::Moth::Instr::Type type)
{
    m_state.State::operator=(nextStateFromAnnotations(m_state, m_annotations));
    const auto accumulatorIn = m_state.registers.find(Accumulator);
    if (accumulatorIn != m_state.registers.end()
            && isTypeStorable(m_typeResolver, accumulatorIn.value().content.storedType())) {
        QQmlJSRegisterContent content = accumulatorIn.value().content;
        m_state.accumulatorVariableIn = m_registerVariables.value(content).variableName;
        Q_ASSERT(!m_state.accumulatorVariableIn.isEmpty());
    } else {
        m_state.accumulatorVariableIn.clear();
    }

    auto labelIt = m_labels.constFind(currentInstructionOffset());
    if (labelIt != m_labels.constEnd()) {
        m_body += *labelIt + u":;\n"_s;
        m_skipUntilNextLabel = false;
    } else if (m_skipUntilNextLabel && !instructionManipulatesContext(type)) {
        return SkipInstruction;
    }

    if (m_state.changedRegisterIndex() == Accumulator)
        m_state.accumulatorVariableOut = changedRegisterVariable();
    else
        m_state.accumulatorVariableOut.clear();

    // If the accumulator type is valid, we want an accumulator variable.
    // If not, we don't want one.
    Q_ASSERT(m_state.changedRegisterIndex() == Accumulator
             || m_state.accumulatorVariableOut.isEmpty());
    Q_ASSERT(m_state.changedRegisterIndex() != Accumulator
             || !m_state.accumulatorVariableOut.isEmpty()
             || !isTypeStorable(m_typeResolver, m_state.changedRegister().storedType()));

    // If the instruction has no side effects and doesn't write any register, it's dead.
    // We might still need the label, though, and the source code comment.
    if (!m_state.hasInternalSideEffects() && changedRegisterVariable().isEmpty()) {
        generateJumpCodeWithTypeConversions(0);
        return SkipInstruction;
    }

    return ProcessInstruction;
}

void QQmlJSCodeGenerator::endInstruction(QV4::Moth::Instr::Type)
{
    if (!m_skipUntilNextLabel)
        generateJumpCodeWithTypeConversions(0);
    m_pool->clearTemporaries();
}

void QQmlJSCodeGenerator::generateSetInstructionPointer()
{
    m_body += u"aotContext->setInstructionPointer("_s
        + QString::number(nextInstructionOffset()) + u");\n"_s;
}

void QQmlJSCodeGenerator::generateExceptionCheck()
{
    m_body += u"if (aotContext->engine->hasError()) {\n"_s;
    generateReturnError();
    m_body += u"}\n"_s;
}

void QQmlJSCodeGenerator::generateEqualityOperation(
        QQmlJSRegisterContent lhsContent, QQmlJSRegisterContent rhsContent,
        const QString &lhsName, const QString &rhsName, const QString &function, bool invert)
{
    const bool lhsIsOptional = m_typeResolver->isOptionalType(lhsContent);
    const bool rhsIsOptional = m_typeResolver->isOptionalType(rhsContent);

    const QQmlJSScope::ConstPtr rhsContained = rhsIsOptional
            ? m_typeResolver->extractNonVoidFromOptionalType(rhsContent).containedType()
            : rhsContent.containedType();

    const QQmlJSScope::ConstPtr lhsContained = lhsIsOptional
            ? m_typeResolver->extractNonVoidFromOptionalType(lhsContent).containedType()
            : lhsContent.containedType();

    const bool isStrict = function == "strictlyEquals"_L1;
    const bool strictlyComparableWithVar
            = isStrict && canStrictlyCompareWithVar(m_typeResolver, lhsContained, rhsContained);
    auto isComparable = [&]() {
        if (m_typeResolver->isPrimitive(lhsContent) && m_typeResolver->isPrimitive(rhsContent))
            return true;
        if (m_typeResolver->isNumeric(lhsContent) && m_typeResolver->isNumeric(rhsContent))
            return true;
        if (m_typeResolver->isNumeric(lhsContent) && rhsContent.isEnumeration())
            return true;
        if (m_typeResolver->isNumeric(rhsContent) && lhsContent.isEnumeration())
            return true;
        if (strictlyComparableWithVar)
            return true;
        if (canCompareWithQObject(m_typeResolver, lhsContained, rhsContained))
            return true;
        if (canCompareWithQUrl(m_typeResolver, lhsContained, rhsContained))
            return true;
        return false;
    };

    const auto retrieveOriginal = [this](QQmlJSRegisterContent content) {
        const auto contained = content.containedType();
        const auto originalContent = originalType(content);
        const auto containedOriginal = originalContent.containedType();

        if (originalContent.isStoredIn(m_typeResolver->genericType(containedOriginal))) {
            // The original type doesn't need any wrapping.
            return originalContent;
        } else if (contained == containedOriginal) {
            if (originalContent.isConversion()) {
                // The original conversion origins are more accurate
                return m_pool->storedIn(originalContent, content.storedType());
            }
        } else if (m_typeResolver->canHold(contained, containedOriginal)) {
            return m_pool->storedIn(originalContent, content.storedType());
        }

        return content;
    };

    const QQmlJSScope::ConstPtr lhsType = lhsContent.storedType();
    const QQmlJSScope::ConstPtr rhsType = rhsContent.storedType();

    if (!isComparable()) {
        QQmlJSRegisterContent lhsOriginal = retrieveOriginal(lhsContent);
        QQmlJSRegisterContent rhsOriginal = retrieveOriginal(rhsContent);
        if (lhsOriginal.containedType() != lhsContent.containedType()
                || lhsOriginal.storedType() != lhsType
                || rhsOriginal.containedType() != rhsContent.containedType()
                || rhsOriginal.storedType() != rhsType) {
            // If either side is simply a wrapping of a specific type into a more general one, we
            // can compare the original types instead. You can't nest wrappings after all.
            generateEqualityOperation(lhsOriginal, rhsOriginal,
                                      conversion(lhsType, lhsOriginal, lhsName),
                                      conversion(rhsType, rhsOriginal, rhsName),
                                      function, invert);
            return;
        }

        REJECT(u"incomparable types %1 and %2"_s.arg(
                rhsContent.descriptiveName(), lhsContent.descriptiveName()));
    }

    if (strictlyComparableWithVar) {
        // Determine which side is holding a storable type
        if (!lhsName.isEmpty() && rhsName.isEmpty()) {
            // lhs register holds var type and rhs is not storable
            generateVariantEqualityComparison(rhsContent, lhsName, invert);
            return;
        }

        if (!rhsName.isEmpty() && lhsName.isEmpty()) {
            // lhs content is not storable and rhs is var type
            generateVariantEqualityComparison(lhsContent, rhsName, invert);
            return;
        }

        if (lhsContent.contains(m_typeResolver->varType())) {
            generateVariantEqualityComparison(rhsContent, rhsName, lhsName, invert);
            return;
        }

        if (rhsContent.contains(m_typeResolver->varType())) {
            generateVariantEqualityComparison(lhsContent, lhsName, rhsName, invert);
            return;
        }

        // It shouldn't be possible to get here because optional null should be stored in
        // QJSPrimitiveValue, not in QVariant. But let's rather be safe than sorry.
        REJECT(u"comparison of optional null"_s);
    }

    const auto comparison = [&]() -> QString {
        const auto primitive = m_typeResolver->jsPrimitiveType();
        const QString sign = invert ? u" != "_s : u" == "_s;

        if (lhsType == rhsType && lhsType != primitive && lhsType != m_typeResolver->varType()) {

            // Straight forward comparison of equal types,
            // except QJSPrimitiveValue which has two comparison functions.

            if (isTypeStorable(m_typeResolver, lhsType))
                return lhsName + sign + rhsName;

            // null === null and undefined === undefined
            return invert ? u"false"_s : u"true"_s;
        }

        if (canCompareWithQObject(m_typeResolver, lhsType, rhsType)) {
            // Comparison of QObject-derived with nullptr or different QObject-derived.
            return (isTypeStorable(m_typeResolver, lhsType) ? lhsName : u"nullptr"_s)
                    + sign
                    + (isTypeStorable(m_typeResolver, rhsType) ? rhsName : u"nullptr"_s);
        }

        if (canCompareWithQObject(m_typeResolver, lhsContained, rhsContained)) {
            // Comparison of optional QObject-derived with nullptr or different QObject-derived.
            // Mind that null == undefined but null !== undefined
            // Therefore the isStrict dance.

            QString result;
            if (isStrict) {
                if (lhsIsOptional) {
                    if (rhsIsOptional) {
                        // If both are invalid we're fine
                        result += u"(!"_s
                                + lhsName + u".isValid() && !"_s
                                + rhsName + u".isValid()) || "_s;
                    }

                    result += u'(' + lhsName + u".isValid() && "_s;
                } else {
                    result += u'(';
                }

                if (rhsIsOptional) {
                    result += rhsName + u".isValid() && "_s;
                }
            } else {
                result += u'(';
            }

            // We do not implement comparison with explicit undefined, yet. Only with null.
            Q_ASSERT(lhsType != m_typeResolver->voidType());
            Q_ASSERT(rhsType != m_typeResolver->voidType());

            const auto resolvedName = [&](const QString name) -> QString {
                // If isStrict we check validity already before.
                const QString content = u"*static_cast<QObject **>("_s + name + u".data())"_s;
                return isStrict
                        ? content
                        : u'(' + name + u".isValid() ? "_s + content + u" : nullptr)"_s;
            };

            const QString lhsResolved = lhsIsOptional ? resolvedName(lhsName) : lhsName;
            const QString rhsResolved = rhsIsOptional ? resolvedName(rhsName) : rhsName;

            return (invert ? u"!("_s : u"("_s) + result
                    + (isTypeStorable(m_typeResolver, lhsType) ? lhsResolved : u"nullptr"_s)
                    + u" == "_s
                    + (isTypeStorable(m_typeResolver, rhsType) ? rhsResolved : u"nullptr"_s)
                    + u"))"_s;
        }

        if ((m_typeResolver->isUnsignedInteger(rhsType)
                    && m_typeResolver->isUnsignedInteger(lhsType))
                || (m_typeResolver->isSignedInteger(rhsType)
                    && m_typeResolver->isSignedInteger(lhsType))) {
            // Both integers of same signedness: Let the C++ compiler perform the type promotion
            return lhsName + sign + rhsName;
        }

        if (rhsType == m_typeResolver->boolType() && m_typeResolver->isIntegral(lhsType)) {
            // Integral and bool: We can promote the bool to the integral type
            return lhsName + sign + convertStored(rhsType, lhsType, rhsName);
        }

        if (lhsType == m_typeResolver->boolType() && m_typeResolver->isIntegral(rhsType)) {
            // Integral and bool: We can promote the bool to the integral type
            return convertStored(lhsType, rhsType, lhsName) + sign + rhsName;
        }

        if (m_typeResolver->isNumeric(lhsType) && m_typeResolver->isNumeric(rhsType)) {
            // Both numbers: promote them to double
            return convertStored(lhsType, m_typeResolver->realType(), lhsName)
                    + sign
                    + convertStored(rhsType, m_typeResolver->realType(), rhsName);
        }

        // If none of the above matches, we have to use QJSPrimitiveValue
        return (invert ? u"!"_s : QString())
                + convertStored(lhsType, primitive, lhsName)
                + u'.' + function + u'(' + convertStored(rhsType, primitive, rhsName) + u')';
    };

    m_body += m_state.accumulatorVariableOut + u" = "_s;
    m_body += conversion(m_typeResolver->boolType(), m_state.accumulatorOut(), comparison());
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generateCompareOperation(int lhs, const QString &cppOperator)
{
    m_body += m_state.accumulatorVariableOut + u" = "_s;

    const auto lhsType = registerType(lhs);
    const QQmlJSScope::ConstPtr compareType =
            m_typeResolver->isNumeric(lhsType) && m_typeResolver->isNumeric(m_state.accumulatorIn())
                ? m_typeResolver->merge(lhsType.storedType(), m_state.accumulatorIn().storedType())
                : m_typeResolver->jsPrimitiveType();

    m_body += conversion(
                m_typeResolver->boolType(), m_state.accumulatorOut(),
                convertStored(registerType(lhs).storedType(), compareType,
                              consumedRegisterVariable(lhs))
                    + u' ' + cppOperator + u' '
                    + convertStored(m_state.accumulatorIn().storedType(), compareType,
                                    consumedAccumulatorVariableIn()));
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generateArithmeticOperation(int lhs, const QString &cppOperator)
{
    generateArithmeticOperation(
                conversion(registerType(lhs), m_state.readRegister(lhs),
                           consumedRegisterVariable(lhs)),
                conversion(m_state.accumulatorIn(), m_state.readAccumulator(),
                           consumedAccumulatorVariableIn()),
                cppOperator);
}

void QQmlJSCodeGenerator::generateShiftOperation(int lhs, const QString &cppOperator)
{
    generateArithmeticOperation(
                conversion(registerType(lhs), m_state.readRegister(lhs),
                           consumedRegisterVariable(lhs)),
                u'(' + conversion(m_state.accumulatorIn(), m_state.readAccumulator(),
                           consumedAccumulatorVariableIn()) + u" & 0x1f)"_s,
                cppOperator);
}

void QQmlJSCodeGenerator::generateArithmeticOperation(
        const QString &lhs, const QString &rhs, const QString &cppOperator)
{
    Q_ASSERT(m_logger->currentFunctionHasCompileError() || !lhs.isEmpty());
    Q_ASSERT(m_logger->currentFunctionHasCompileError() || !rhs.isEmpty());

    const QQmlJSRegisterContent originalOut = originalType(m_state.accumulatorOut());
    m_body += m_state.accumulatorVariableOut;
    m_body += u" = "_s;
    const QString explicitCast
            = originalOut.isStoredIn(m_typeResolver->stringType())
                ? originalOut.storedType()->internalName()
                : QString();
    m_body += conversion(
                originalOut, m_state.accumulatorOut(),
                explicitCast + u'(' + lhs + u' ' + cppOperator + u' ' + rhs + u')');
    m_body += u";\n"_s;
}

void QQmlJSCodeGenerator::generateArithmeticConstOperation(int rhsConst, const QString &cppOperator)
{
    generateArithmeticOperation(
                conversion(m_state.accumulatorIn(), m_state.readAccumulator(),
                           consumedAccumulatorVariableIn()),
                conversion(literalType(m_typeResolver->int32Type()),
                           m_state.readAccumulator(), QString::number(rhsConst)),
                cppOperator);
}

void QQmlJSCodeGenerator::generateUnaryOperation(const QString &cppOperator)
{
    const auto var = conversion(m_state.accumulatorIn(),
                                originalType(m_state.readAccumulator()),
                                consumedAccumulatorVariableIn());

    if (var == m_state.accumulatorVariableOut) {
        m_body += m_state.accumulatorVariableOut + u" = "_s + cppOperator + var + u";\n"_s;
        return;
    }

    const auto originalResult = originalType(m_state.accumulatorOut());
    if (m_state.accumulatorOut() == originalResult) {
        m_body += m_state.accumulatorVariableOut + u" = "_s + var + u";\n"_s;
        m_body += m_state.accumulatorVariableOut + u" = "_s
                + cppOperator + m_state.accumulatorVariableOut + u";\n"_s;
        return;
    }

    m_body += m_state.accumulatorVariableOut + u" = "_s + conversion(
                originalResult, m_state.accumulatorOut(), cppOperator + var) + u";\n"_s;
}

void QQmlJSCodeGenerator::generateInPlaceOperation(const QString &cppOperator)
{
    {
        // If actually in place, we cannot consume the variable.
        const QString var = conversion(m_state.accumulatorIn(), m_state.readAccumulator(),
                                       m_state.accumulatorVariableIn);
        if (var == m_state.accumulatorVariableOut) {
            m_body += cppOperator + var + u";\n"_s;
            return;
        }
    }

    const QString var = conversion(m_state.accumulatorIn(), m_state.readAccumulator(),
                                   consumedAccumulatorVariableIn());

    const auto originalResult = originalType(m_state.accumulatorOut());
    if (m_state.accumulatorOut() == originalResult) {
        m_body += m_state.accumulatorVariableOut + u" = "_s + var + u";\n"_s;
        m_body += cppOperator + m_state.accumulatorVariableOut + u";\n"_s;
        return;
    }

    m_body += u"{\n"_s;
    m_body += u"auto converted = "_s + var + u";\n"_s;
    m_body += m_state.accumulatorVariableOut + u" = "_s + conversion(
                originalResult, m_state.accumulatorOut(), u'('
                + cppOperator + u"converted)"_s)  + u";\n"_s;
    m_body += u"}\n"_s;
}

void QQmlJSCodeGenerator::generateLookup(const QString &lookup, const QString &initialization,
                                        const QString &resultPreparation)
{
    m_body += u"#ifndef QT_NO_DEBUG\n"_s;
    generateSetInstructionPointer();
    m_body += u"#endif\n"_s;

    if (!resultPreparation.isEmpty())
        m_body += resultPreparation + u";\n"_s;
    m_body += u"while (!"_s + lookup + u") {\n"_s;

    m_body += u"#ifdef QT_NO_DEBUG\n"_s;
    generateSetInstructionPointer();
    m_body += u"#endif\n"_s;

    m_body += initialization + u";\n"_s;
    generateExceptionCheck();
    if (!resultPreparation.isEmpty())
        m_body += resultPreparation + u";\n"_s;
    m_body += u"}\n"_s;
}

void QQmlJSCodeGenerator::generateJumpCodeWithTypeConversions(int relativeOffset)
{
    QString conversionCode;
    const int absoluteOffset = nextInstructionOffset() + relativeOffset;
    const auto annotation = m_annotations.find(absoluteOffset);
    if (static_cast<InstructionAnnotations::const_iterator>(annotation) != m_annotations.constEnd()) {
        const auto &conversions = annotation->second.typeConversions;

        for (auto regIt = conversions.constBegin(), regEnd = conversions.constEnd();
             regIt != regEnd; ++regIt) {
            const QQmlJSRegisterContent targetType = regIt.value().content;
            if (!targetType.isValid() || !isTypeStorable(m_typeResolver, targetType.storedType()))
                continue;

            const int registerIndex = regIt.key();
            const auto variable = m_registerVariables.constFind(targetType);

            if (variable == m_registerVariables.constEnd())
                continue;

            QQmlJSRegisterContent currentType;
            QString currentVariable;
            if (registerIndex == m_state.changedRegisterIndex()) {
                currentVariable = changedRegisterVariable();
                if (variable->variableName == currentVariable)
                    continue;

                currentType = m_state.changedRegister();

                // TODO: We can std::move the changed register in some cases here but it needs be
                // done carefully (QTBUG-141920).
            } else {
                const auto it = m_state.registers.find(registerIndex);
                if (it == m_state.registers.end()
                        || variable->variableName == registerVariable(registerIndex)) {
                    continue;
                }

                currentType = it.value().content;
                currentVariable = consumedRegisterVariable(registerIndex);
            }

            // Actually == here. We want the jump code also for equal types
            if (currentType == targetType)
                continue;

            conversionCode += variable->variableName;
            conversionCode += u" = "_s;
            conversionCode += conversion(currentType, targetType, currentVariable);
            conversionCode += u";\n"_s;
        }
    }

    if (relativeOffset) {
        auto labelIt = m_labels.find(absoluteOffset);
        if (labelIt == m_labels.end())
            labelIt = m_labels.insert(absoluteOffset, u"label_%1"_s.arg(m_labels.size()));
        conversionCode += u"    goto "_s + *labelIt + u";\n"_s;
    }

    m_body += u"{\n"_s + conversionCode + u"}\n"_s;
}

QString QQmlJSCodeGenerator::registerVariable(int index) const
{
    QQmlJSRegisterContent content = registerType(index);
    const auto it = m_registerVariables.constFind(content);
    if (it != m_registerVariables.constEnd())
        return it->variableName;

    return QString();
}

QString QQmlJSCodeGenerator::lookupVariable(int lookupIndex) const
{
    for (auto it = m_registerVariables.constBegin(), end = m_registerVariables.constEnd(); it != end; ++it) {
        if (it.key().resultLookupIndex() == lookupIndex)
            return it->variableName;
    }
    return QString();
}

QString QQmlJSCodeGenerator::consumedRegisterVariable(int index) const
{
    const QString var = registerVariable(index);
    if (var.isEmpty() || !shouldMoveRegister(index))
        return var;
    return u"std::move(" + var + u")";
}

QString QQmlJSCodeGenerator::consumedAccumulatorVariableIn() const
{
    return shouldMoveRegister(Accumulator)
            ? u"std::move(" + m_state.accumulatorVariableIn + u")"
            : m_state.accumulatorVariableIn;
}

QString QQmlJSCodeGenerator::changedRegisterVariable() const
{
    QQmlJSRegisterContent changedRegister = m_state.changedRegister();

    const QQmlJSScope::ConstPtr storedType = changedRegister.storedType();
    if (storedType.isNull())
        return QString();

    return m_registerVariables.value(changedRegister).variableName;
}

QQmlJSRegisterContent QQmlJSCodeGenerator::registerType(int index) const
{
    auto it = m_state.registers.find(index);
    if (it != m_state.registers.end())
        return it.value().content;

    return QQmlJSRegisterContent();
}

QQmlJSRegisterContent QQmlJSCodeGenerator::lookupType(int lookupIndex) const
{
    auto it = m_state.lookups.find(lookupIndex);
    if (it != m_state.lookups.end())
        return it.value().content;

    return QQmlJSRegisterContent();
}

bool QQmlJSCodeGenerator::shouldMoveRegister(int index) const
{
    return m_state.canMoveReadRegister(index)
            && !m_typeResolver->isTriviallyCopyable(m_state.readRegister(index).storedType());
}

QString QQmlJSCodeGenerator::conversion(
        QQmlJSRegisterContent from, QQmlJSRegisterContent to, const QString &variable)
{
    const QQmlJSScope::ConstPtr contained = to.containedType();

    // If from is QJSPrimitiveValue and to contains a primitive we coerce using QJSPrimitiveValue
    if (from.isStoredIn(m_typeResolver->jsPrimitiveType()) && m_typeResolver->isPrimitive(to)) {

        QString primitive = [&]() -> QString {
            if (contained == m_typeResolver->jsPrimitiveType())
                return variable;

            const QString conversion = variable + u".to<QJSPrimitiveValue::%1>()"_s;
            if (contained == m_typeResolver->boolType())
                return conversion.arg(u"Boolean"_s);
            if (m_typeResolver->isIntegral(to))
                return conversion.arg(u"Integer"_s);
            if (m_typeResolver->isNumeric(to))
                return conversion.arg(u"Double"_s);
            if (contained == m_typeResolver->stringType())
                return conversion.arg(u"String"_s);
            REJECT<QString>(
                    u"Conversion of QJSPrimitiveValue to "_s + contained->internalName());
        }();

        if (primitive.isEmpty())
            return primitive;

        return convertStored(m_typeResolver->jsPrimitiveType(), to.storedType(), primitive);
    }

    if (to.isStoredIn(contained)
            || m_typeResolver->isNumeric(to.storedType())
            || to.storedType()->isReferenceType()
            || from.contains(contained)) {
        // If:
        // * the output is not actually wrapped at all, or
        // * the output is stored in a numeric type (as there are no internals to a number), or
        // * the output is a QObject pointer, or
        // * we merely wrap the value into a new container,
        // we can convert by stored type.
        return convertStored(from.storedType(), to.storedType(), variable);
    } else {
        return convertContained(from, to, variable);
    }
}

QString QQmlJSCodeGenerator::convertStored(
        const QQmlJSScope::ConstPtr &from, const QQmlJSScope::ConstPtr &to, const QString &variable)
{
    // TODO: most values can be moved, which is much more efficient with the common types.
    //       add a move(from, to, variable) function that implements the moves.
    Q_ASSERT(!to->isComposite()); // We cannot directly convert to composites.

    const auto jsValueType = m_typeResolver->jsValueType();
    const auto varType = m_typeResolver->varType();
    const auto jsPrimitiveType = m_typeResolver->jsPrimitiveType();
    const auto boolType = m_typeResolver->boolType();

    auto zeroBoolOrInt = [&](const QQmlJSScope::ConstPtr &to) {
        if (to == boolType)
            return u"false"_s;
        if (m_typeResolver->isSignedInteger(to))
            return u"0"_s;
        if (m_typeResolver->isUnsignedInteger(to))
            return u"0u"_s;
        return QString();
    };

    if (from == m_typeResolver->voidType()) {
        if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
            return u"static_cast<"_s + to->internalName() + u" *>(nullptr)"_s;
        const QString zero = zeroBoolOrInt(to);
        if (!zero.isEmpty())
            return zero;
        if (to == m_typeResolver->floatType())
            return u"std::numeric_limits<float>::quiet_NaN()"_s;
        if (to == m_typeResolver->realType())
            return u"std::numeric_limits<double>::quiet_NaN()"_s;
        if (to == m_typeResolver->stringType())
            return QQmlJSUtils::toLiteral(u"undefined"_s);
        if (to == m_typeResolver->varType())
            return u"QVariant()"_s;
        if (to == m_typeResolver->jsValueType())
            return u"QJSValue();"_s;
        if (to == m_typeResolver->jsPrimitiveType())
            return u"QJSPrimitiveValue()"_s;
        if (from == to)
            return QString();
    }

    if (from == m_typeResolver->nullType()) {
        if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
            return u"static_cast<"_s + to->internalName() + u" *>(nullptr)"_s;
        if (to == jsValueType)
            return u"QJSValue(QJSValue::NullValue)"_s;
        if (to == jsPrimitiveType)
            return u"QJSPrimitiveValue(QJSPrimitiveNull())"_s;
        if (to == varType)
            return u"QVariant::fromValue<std::nullptr_t>(nullptr)"_s;
        const QString zero = zeroBoolOrInt(to);
        if (!zero.isEmpty())
            return zero;
        if (to == m_typeResolver->floatType())
            return u"0.0f"_s;
        if (to == m_typeResolver->realType())
            return u"0.0"_s;
        if (to == m_typeResolver->stringType())
            return QQmlJSUtils::toLiteral(u"null"_s);
        if (from == to)
            return QString();
        REJECT<QString>(u"Conversion from null to %1"_s.arg(to->internalName()));
    }

    if (from == to)
        return variable;

    if (from->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
        if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
            // Compare internalName here. The same C++ type can be exposed muliple times in
            // different QML types. However, the C++ names have to be unique. We can always
            // static_cast to those.

            for (QQmlJSScope::ConstPtr base = from; base; base = base->baseType()) {
                // We still have to cast as other execution paths may result in different types.
                if (base->internalName() == to->internalName())
                    return u"static_cast<"_s + to->internalName() + u" *>("_s + variable + u')';
            }
            for (QQmlJSScope::ConstPtr base = to; base; base = base->baseType()) {
                if (base->internalName() == from->internalName())
                    return u"static_cast<"_s + to->internalName() + u" *>("_s + variable + u')';
            }
        } else if (to == m_typeResolver->boolType()) {
            return u'(' + variable + u" != nullptr)"_s;
        }
    }

    auto isJsValue = [&](const QQmlJSScope::ConstPtr &candidate) {
        return candidate == jsValueType || candidate->isScript();
    };

    if (isJsValue(from) && isJsValue(to))
        return variable;

    const auto isBoolOrNumber = [&](const QQmlJSScope::ConstPtr &type) {
        return m_typeResolver->isNumeric(type)
                || type == m_typeResolver->boolType()
                || type->scopeType() == QQmlSA::ScopeType::EnumScope;
    };

    if (from == m_typeResolver->realType() || from == m_typeResolver->floatType()) {
        if (to == m_typeResolver->int64Type() || to == m_typeResolver->uint64Type()) {
            return to->internalName() + u"(QJSNumberCoercion::roundTowards0("_s
                    + variable + u"))"_s;
        }

        if (m_typeResolver->isSignedInteger(to))
            return u"QJSNumberCoercion::toInteger("_s + variable + u')';
        if (m_typeResolver->isUnsignedInteger(to))
            return u"uint(QJSNumberCoercion::toInteger("_s + variable + u"))"_s;
        if (to == m_typeResolver->boolType())
            return u"[](double moved){ return moved && !std::isnan(moved); }("_s + variable + u')';
    }

    if (isBoolOrNumber(from) && isBoolOrNumber(to))
        return to->internalName() + u'(' + variable + u')';


    if (from == jsPrimitiveType) {
        if (to == boolType)
            return variable + u".toBoolean()"_s;
        if (to == m_typeResolver->int64Type() || to == m_typeResolver->uint64Type())
            return u"%1(%2.toDouble())"_s.arg(to->internalName(), variable);
        if (m_typeResolver->isIntegral(to))
            return u"%1(%2.toInteger())"_s.arg(to->internalName(), variable);
        if (m_typeResolver->isNumeric(to))
            return u"%1(%2.toDouble())"_s.arg(to->internalName(), variable);
        if (to == m_typeResolver->stringType())
            return variable + u".toString()"_s;
        if (to == jsValueType)
            return u"QJSValue(QJSPrimitiveValue("_s + variable + u"))"_s;
        if (to == varType)
            return variable + u".toVariant()"_s;
        if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
            return u"static_cast<"_s + to->internalName() + u" *>(nullptr)"_s;
    }

    if (isJsValue(from)) {
        if (to == jsPrimitiveType)
            return variable + u".toPrimitive()"_s;
        if (to == varType)
            return variable + u".toVariant(QJSValue::RetainJSObjects)"_s;
        return u"qjsvalue_cast<"_s + castTargetName(to) + u">("_s + variable + u')';
    }

    if (to == jsPrimitiveType) {
        // null and undefined have been handled above already
        Q_ASSERT(from != m_typeResolver->nullType());
        Q_ASSERT(from != m_typeResolver->voidType());

        if (from == m_typeResolver->boolType()
                || from == m_typeResolver->int32Type()
                || from == m_typeResolver->realType()
                || from == m_typeResolver->stringType()) {
            return u"QJSPrimitiveValue("_s + variable + u')';
        } else if (from == m_typeResolver->int16Type()
                   || from == m_typeResolver->int8Type()
                   || from == m_typeResolver->uint16Type()
                   || from == m_typeResolver->uint8Type()) {
            return u"QJSPrimitiveValue(int("_s + variable + u"))"_s;
        } else if (m_typeResolver->isNumeric(from)) {
            return u"QJSPrimitiveValue(double("_s + variable + u"))"_s;
        }
    }

    if (to == jsValueType)
        return u"aotContext->engine->toScriptValue("_s + variable + u')';

    if (from == varType) {
        if (to == m_typeResolver->listPropertyType())
            return u"QQmlListReference("_s + variable + u", aotContext->qmlEngine())"_s;
        return u"aotContext->engine->fromVariant<"_s + castTargetName(to) + u">("_s
                + variable + u')';
    }

    if (to == varType)
        return u"QVariant::fromValue("_s + variable + u')';

    if (from == m_typeResolver->urlType() && to == m_typeResolver->stringType())
        return variable + u".toString()"_s;

    if (from == m_typeResolver->stringType() && to == m_typeResolver->urlType())
        return u"QUrl("_s + variable + u')';

    if (from == m_typeResolver->byteArrayType() && to == m_typeResolver->stringType())
        return u"QString::fromUtf8("_s + variable + u')';

    if (from == m_typeResolver->stringType() && to == m_typeResolver->byteArrayType())
        return variable + u".toUtf8()"_s;

    for (const auto &originType : {
         m_typeResolver->dateTimeType(),
         m_typeResolver->dateType(),
         m_typeResolver->timeType()}) {
        if (from == originType) {
            for (const auto &targetType : {
                 m_typeResolver->dateTimeType(),
                 m_typeResolver->dateType(),
                 m_typeResolver->timeType(),
                 m_typeResolver->stringType(),
                 m_typeResolver->realType()}) {
                if (to == targetType) {
                    return u"aotContext->engine->coerceValue<%1, %2>(%3)"_s.arg(
                                originType->internalName(), targetType->internalName(), variable);
                }
            }
            break;
        }
    }

    const auto retrieveFromPrimitive = [&](
            const QQmlJSScope::ConstPtr &type, const QString &expression) -> QString
    {
        if (type == m_typeResolver->boolType())
            return expression + u".toBoolean()"_s;
        if (m_typeResolver->isSignedInteger(type))
            return expression + u".toInteger()"_s;
        if (m_typeResolver->isUnsignedInteger(type))
            return u"uint("_s + expression + u".toInteger())"_s;
        if (type == m_typeResolver->realType())
            return expression + u".toDouble()"_s;
        if (type == m_typeResolver->floatType())
            return u"float("_s + expression + u".toDouble())"_s;
        if (type == m_typeResolver->stringType())
            return expression + u".toString()"_s;
        return QString();
    };

    if (!retrieveFromPrimitive(from, u"x"_s).isEmpty()) {
        const QString retrieve = retrieveFromPrimitive(
                    to, convertStored(from, m_typeResolver->jsPrimitiveType(), variable));
        if (!retrieve.isEmpty())
            return retrieve;
    }

    if (from->isReferenceType() && to == m_typeResolver->stringType()) {
        return u"aotContext->engine->coerceValue<"_s + castTargetName(from) + u", "
                + castTargetName(to) + u">("_s + variable + u')';
    }

    // Any value type is a non-null JS 'object' and therefore coerces to true.
    if (to == m_typeResolver->boolType()) {
        // All the interesting cases are already handled above:
        Q_ASSERT(from != m_typeResolver->nullType());
        Q_ASSERT(from != m_typeResolver->voidType());
        Q_ASSERT(retrieveFromPrimitive(from, u"x"_s).isEmpty());
        Q_ASSERT(!isBoolOrNumber(from));

        return u"true"_s;
    }

    if (m_typeResolver->areEquivalentLists(from, to))
        return variable;

    if (from->isListProperty()
            && to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
            && to->elementType()->isReferenceType()
            && !to->isListProperty()) {
        return variable + u".toList<"_s + to->internalName() + u">()"_s;
    }

    bool isExtension = false;
    if (m_typeResolver->canPopulate(to, from, &isExtension)) {
        REJECT<QString>(
                u"populating "_s + to->internalName() + u" from "_s + from->internalName());
    } else if (const auto ctor = m_typeResolver->selectConstructor(to, from, &isExtension);
               ctor.isValid()) {
        const auto argumentTypes = ctor.parameters();
        const QString argument = convertStored(from, argumentTypes[0].type(), variable);
        if (isExtension) {
            // We typically use private inheritance for the foreign/extension trick.
            // Therefore we need to jump through some hoops to extract the actual value here.
            return u"[](auto &&arg) { "
                    "%1 wrapper(std::forward<decltype(arg)>(arg)); "
                    "return %2(reinterpret_cast<%2 &>(wrapper)); "
                    "}(%3)"_s.arg(
                           to->extensionType().scope->internalName(), to->internalName(), argument);
        }

        return u"%1(%2)"_s.arg(to->internalName(), argument);
    }

    if (to == m_typeResolver->stringType()
            && from->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
        addInclude(u"QtQml/qjslist.h"_s);

        // Extend the life time of whatever variable is across the call to toString().
        // variable may be an rvalue.
        return u"[&](auto &&l){ return QJSList(&l, aotContext->engine).toString(); }("_s
                + variable + u')';
    }

    // TODO: add more conversions

    REJECT<QString>(
            u"conversion from "_s + from->internalName() + u" to "_s + to->internalName());
}

QString QQmlJSCodeGenerator::convertContained(QQmlJSRegisterContent from, QQmlJSRegisterContent to, const QString &variable)
{
    const QQmlJSScope::ConstPtr containedFrom = from.containedType();
    const QQmlJSScope::ConstPtr containedTo = to.containedType();

    // Those should be handled before, by convertStored().
    Q_ASSERT(!to.storedType()->isReferenceType());
    Q_ASSERT(!to.isStoredIn(containedTo));
    Q_ASSERT(containedFrom != containedTo);

    if (!to.isStoredIn(m_typeResolver->varType())
            && !to.isStoredIn(m_typeResolver->jsPrimitiveType())) {
        REJECT<QString>(u"internal conversion into unsupported wrapper type."_s);
    }

    bool isExtension = false;
    if (m_typeResolver->canPopulate(containedTo, containedFrom, &isExtension)) {
        REJECT<QString>(u"populating "_s + containedTo->internalName()
                                + u" from "_s + containedFrom->internalName());
    } else if (const auto ctor = m_typeResolver->selectConstructor(
                containedTo, containedFrom, &isExtension); ctor.isValid()) {
        return generateCallConstructor(
                ctor, {from}, {variable}, metaType(containedTo),
                metaObject(isExtension ? containedTo->extensionType().scope : containedTo));
    }

    const auto originalFrom = originalType(from);
    const auto containedOriginalFrom = originalFrom.containedType();
    if (containedFrom != containedOriginalFrom
            && m_typeResolver->canHold(containedFrom, containedOriginalFrom)) {
        // If from is simply a wrapping of a specific type into a more general one, we can convert
        // the original type instead. You can't nest wrappings after all.
        return conversion(m_pool->storedIn(originalFrom, from.storedType()), to, variable);
    }

    if (m_typeResolver->isPrimitive(containedFrom) && m_typeResolver->isPrimitive(containedTo)) {
        const QQmlJSRegisterContent intermediate
                = m_pool->storedIn(from, m_typeResolver->jsPrimitiveType());
        return conversion(intermediate, to, conversion(from, intermediate, variable));
    }

    REJECT<QString>(
            u"internal conversion with incompatible or ambiguous types: %1 -> %2"_s
                    .arg(from.descriptiveName(), to.descriptiveName()));
}

void QQmlJSCodeGenerator::reject(const QString &thing)
{
    addError(u"Cannot generate efficient code for %1"_s.arg(thing));
}


void QQmlJSCodeGenerator::skip(const QString &thing)
{
    addSkip(u"Skipped code generation for %1"_s.arg(thing));
}

QQmlJSCodeGenerator::AccumulatorConverter::AccumulatorConverter(QQmlJSCodeGenerator *generator)
    : accumulatorOut(generator->m_state.accumulatorOut())
    , accumulatorVariableIn(generator->m_state.accumulatorVariableIn)
    , accumulatorVariableOut(generator->m_state.accumulatorVariableOut)
    , generator(generator)
{
    if (accumulatorVariableOut.isEmpty())
        return;

    const QQmlJSTypeResolver *resolver = generator->m_typeResolver;
    const QQmlJSScope::ConstPtr origContained = resolver->originalContainedType(accumulatorOut);
    const QQmlJSRegisterContent storage = accumulatorOut.storage();
    const QQmlJSScope::ConstPtr stored = storage.containedType();
    const QQmlJSScope::ConstPtr origStored = resolver->original(storage).containedType();


    // If the stored type differs or if we store in QVariant and the contained type differs,
    // then we have to use a temporary ...
    if (origStored != stored
            || (origContained != accumulatorOut.containedType() && stored == resolver->varType())) {

        const bool storable = isTypeStorable(resolver, origStored);
        generator->m_state.accumulatorVariableOut = storable ? u"retrieved"_s : QString();
        generator->m_state.setRegister(Accumulator, generator->originalType(accumulatorOut));
        generator->m_body += u"{\n"_s;
        if (storable) {
            generator->m_body += origStored->augmentedInternalName() + u' '
                    + generator->m_state.accumulatorVariableOut + u";\n";
        }
    } else if (generator->m_state.accumulatorVariableIn == generator->m_state.accumulatorVariableOut
               && generator->m_state.readsRegister(Accumulator)
               && generator->m_state.accumulatorOut().isStoredIn(resolver->varType())) {
        // If both m_state.accumulatorIn and m_state.accumulatorOut are QVariant, we will need to
        // prepare the output QVariant, and afterwards use the input variant. Therefore we need to
        // move the input out of the way first.
        generator->m_state.accumulatorVariableIn
                = generator->m_state.accumulatorVariableIn + u"_moved"_s;
        generator->m_body += u"{\n"_s;
        generator->m_body += u"QVariant "_s + generator->m_state.accumulatorVariableIn
                + u" = std::move("_s + generator->m_state.accumulatorVariableOut + u");\n"_s;
    }
}

QQmlJSCodeGenerator::AccumulatorConverter::~AccumulatorConverter()
{
    if (accumulatorVariableOut != generator->m_state.accumulatorVariableOut) {
        generator->m_body += accumulatorVariableOut + u" = "_s + generator->conversion(
                    generator->m_state.accumulatorOut(), accumulatorOut,
                    u"std::move("_s + generator->m_state.accumulatorVariableOut + u')') + u";\n"_s;
        generator->m_body += u"}\n"_s;
        generator->m_state.setRegister(Accumulator, accumulatorOut);
        generator->m_state.accumulatorVariableOut = accumulatorVariableOut;
    } else if (accumulatorVariableIn != generator->m_state.accumulatorVariableIn) {
        generator->m_body += u"}\n"_s;
        generator->m_state.accumulatorVariableIn = accumulatorVariableIn;
    }
}


QT_END_NAMESPACE

QQmlJSCodeGenerator::GeneratePragmaWarningBlock::GeneratePragmaWarningBlock(QQmlJSCodeGenerator *generator)
    : m_generator(generator)
{
    m_generator->m_body += u"QT_WARNING_PUSH\n"_s;
}

QQmlJSCodeGenerator::GeneratePragmaWarningBlock::~GeneratePragmaWarningBlock()
{
    m_generator->m_body += u"QT_WARNING_POP\n"_s;
}

void QQmlJSCodeGenerator::GeneratePragmaWarningBlock::silenceDivideByZero()
{
    // currently only needed with MSVC, if we need it on more compilers, we
    // should add a proper macro in Qt itself
    m_generator->m_body += u"QT_WARNING_DISABLE_MSVC(4723) // potential divide by 0\n"_s;
}

void QQmlJSCodeGenerator::recordPropertyLookup(const QQmlJSScope::ConstPtr &base,
                                               const QQmlJSMetaProperty &property)
{
    if (!m_noAotValidation)
        m_lookupSignaturesRecorder.recordPropertyLookup(base, property);
}

void QQmlJSCodeGenerator::recordMethodLookup(const QQmlJSScope::ConstPtr &base,
                                             const QQmlJSMetaMethod &method)
{
    if (!m_noAotValidation)
        m_lookupSignaturesRecorder.recordMethodLookup(base, method);
}

void QQmlJSCodeGenerator::recordEnumKeyLookup(const QQmlJSScope::ConstPtr &base,
                                              const QQmlJSMetaEnum &metaEnum,
                                              const QString &keyName)
{
    if (!m_noAotValidation)
        m_lookupSignaturesRecorder.recordEnumKeyLookup(base, metaEnum, keyName);
}
