aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler/qqmljscodegenerator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlcompiler/qqmljscodegenerator.cpp')
-rw-r--r--src/qmlcompiler/qqmljscodegenerator.cpp4354
1 files changed, 4354 insertions, 0 deletions
diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp
new file mode 100644
index 0000000000..9f609f39fb
--- /dev/null
+++ b/src/qmlcompiler/qqmljscodegenerator.cpp
@@ -0,0 +1,4354 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+#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'; \
+ }
+
+
+static bool isTypeStorable(const QQmlJSTypeResolver *resolver, const QQmlJSScope::ConstPtr &type)
+{
+ return !type.isNull()
+ && !resolver->equals(type, resolver->nullType())
+ && !resolver->equals(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, BasicBlocks basicBlocks,
+ InstructionAnnotations annotations)
+ : QQmlJSCompilePass(unitGenerator, typeResolver, logger, basicBlocks, annotations)
+ , m_context(compilerContext)
+{}
+
+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"[](auto *aotContext) { static const auto t = QQmlPrivate::compositeListMetaType("
+ "aotContext->compilationUnit, QStringLiteral(\""_s
+ + elementName
+ + u"\")); return t; }(aotContext)"_s;
+}
+
+QString QQmlJSCodeGenerator::compositeMetaType(const QString &elementName) const
+{
+ return u"[](auto *aotContext) { static const auto t = QQmlPrivate::compositeMetaType("
+ "aotContext->compilationUnit, QStringLiteral(\""_s
+ + elementName
+ + u"\")); return t; }(aotContext)"_s;
+}
+
+QString QQmlJSCodeGenerator::metaObject(const QQmlJSScope::ConstPtr &objectType)
+{
+ if (objectType->isComposite()) {
+ const QString name = m_typeResolver->nameForType(objectType);
+ if (name.isEmpty()) {
+ reject(u"retrieving the metaObject of a composite type without an element name."_s);
+ return QString();
+ }
+ 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->isComposite()) {
+ const QString name = m_typeResolver->nameForType(type);
+ if (!name.isEmpty())
+ return compositeMetaType(name);
+ }
+
+ if (type->isListProperty() && type->valueType()->isComposite()) {
+ const QString name = m_typeResolver->nameForType(type->valueType());
+ if (!name.isEmpty())
+ return compositeListMetaType(name);
+ }
+
+ return m_typeResolver->equals(m_typeResolver->genericType(type), type)
+ ? metaTypeFromType(type)
+ : metaTypeFromName(type);
+}
+
+QQmlJSAotFunction QQmlJSCodeGenerator::run(const Function *function,
+ QQmlJS::DiagnosticMessage *error,
+ bool basicBlocksValidationFailed)
+{
+ m_function = function;
+ m_error = error;
+
+ QHash<int, int> numRegisterVariablesPerIndex;
+
+ const auto addVariable
+ = [&](int registerIndex, int lookupIndex, const QQmlJSScope::ConstPtr &seenType) {
+ // Don't generate any variables for registers that are initialized with undefined.
+ if (registerIndex == InvalidRegister || !isTypeStorable(m_typeResolver, seenType))
+ return;
+
+ const RegisterVariablesKey key = { seenType->internalName(), registerIndex, lookupIndex };
+
+
+ const auto oldSize = m_registerVariables.size();
+ auto &e = m_registerVariables[key];
+ if (m_registerVariables.size() != oldSize) {
+ e.variableName = u"r%1_%2"_s
+ .arg(registerIndex)
+ .arg(numRegisterVariablesPerIndex[registerIndex]++);
+ e.storedType = m_typeResolver->comparableType(seenType);
+ }
+ ++e.numTracked;
+ };
+
+QT_WARNING_PUSH
+QT_WARNING_DISABLE_CLANG("-Wrange-loop-analysis")
+ for (const auto &annotation : m_annotations) {
+ addVariable(annotation.second.changedRegisterIndex,
+ annotation.second.changedRegister.resultLookupIndex(),
+ annotation.second.changedRegister.storedType());
+ for (auto it = annotation.second.typeConversions.begin(),
+ end = annotation.second.typeConversions.end();
+ it != end; ++it) {
+ addVariable(
+ it.key(), it.value().content.resultLookupIndex(),
+ it.value().content.storedType());
+ }
+ }
+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));
+
+ const QByteArray byteCode = function->code;
+ decode(byteCode.constData(), static_cast<uint>(byteCode.size()));
+
+ 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);
+
+ for (auto registerIt = m_registerVariables.cbegin(), registerEnd = m_registerVariables.cend();
+ registerIt != registerEnd; ++registerIt) {
+
+ const int registerIndex = registerIt.key().registerIndex;
+ const bool registerIsArgument = isArgument(registerIndex);
+
+ result.code += registerIt.key().internalName;
+
+ const QQmlJSScope::ConstPtr storedType = registerIt->storedType;
+ const bool isPointer
+ = (storedType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference);
+ if (isPointer)
+ result.code += u" *"_s;
+ else
+ result.code += u' ';
+
+ if (!registerIsArgument
+ && registerIndex != Accumulator
+ && registerIndex != This
+ && !m_typeResolver->registerIsStoredIn(
+ function->registerTypes[registerIndex - firstRegisterIndex()],
+ m_typeResolver->voidType())) {
+ result.code += registerIt->variableName + u" = "_s;
+ result.code += convertStored(m_typeResolver->voidType(), storedType, QString());
+ } else if (registerIsArgument && m_typeResolver->registerIsStoredIn(
+ argumentType(registerIndex), storedType)) {
+ const int argumentIndex = registerIndex - FirstArgument;
+ const QQmlJSRegisterContent argument
+ = m_function->argumentTypes[argumentIndex];
+ const QQmlJSRegisterContent original
+ = m_typeResolver->original(argument);
+
+ const bool needsConversion = argument != original;
+ 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.
+ result.code += u'&';
+ }
+
+ result.code += registerIt->variableName + u" = "_s;
+
+ const QString originalValue
+ = u"(*static_cast<"_s + castTargetName(original.storedType())
+ + u"*>(argv["_s + QString::number(argumentIndex + 1) + u"]))"_s;
+
+ if (needsConversion)
+ result.code += conversion(original, argument, originalValue);
+ else
+ result.code += originalValue;
+ } else {
+ result.code += registerIt->variableName;
+ }
+ result.code += u";\n"_s;
+ }
+
+ result.code += m_body;
+
+
+ QString signature
+ = u" struct { QV4::ExecutableCompilationUnit *compilationUnit; } c { unit };\n"
+ " const auto *aotContext = &c;\n"
+ " Q_UNUSED(aotContext);\n"_s;
+
+ if (function->returnType.isValid()) {
+ signature += u" argTypes[0] = %1;\n"_s.arg(
+ metaType(m_typeResolver->containedType(function->returnType)));
+ } 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 = 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() || m_typeResolver->registerContains(ret, m_typeResolver->voidType()))
+ return;
+
+ m_body += u"if (argv[0]) {\n"_s;
+
+ const auto contained = m_typeResolver->containedType(ret);
+ 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 (m_typeResolver->equals(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(m_typeResolver->containedType(ret)) + 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;
+
+ if (in.isEmpty()) {
+ if (m_typeResolver->equals(m_state.accumulatorIn().storedType(),
+ m_typeResolver->voidType())) {
+ m_body += signalUndefined;
+
+ }
+ } else if (m_typeResolver->registerIsStoredIn(
+ m_state.accumulatorIn(), m_typeResolver->varType())) {
+ m_body += u" if (!"_s + in + u".isValid())\n"_s;
+ m_body += u" "_s + signalUndefined;
+ } else if (m_typeResolver->registerIsStoredIn(
+ m_state.accumulatorIn(), m_typeResolver->jsPrimitiveType())) {
+ m_body += u" if ("_s + in + u".type() == QJSPrimitiveValue::Undefined)\n"_s;
+ m_body += u" "_s + signalUndefined;
+ } else if (m_typeResolver->registerIsStoredIn(
+ m_state.accumulatorIn(), m_typeResolver->jsValueType())) {
+ m_body += u" if ("_s + in + u".isUndefined())\n"_s;
+ m_body += u" "_s + signalUndefined;
+ }
+
+ if (m_typeResolver->registerContains(
+ m_function->returnType, m_typeResolver->voidType())) {
+ m_body += u"}\n"_s;
+ return;
+ }
+
+ const auto contained = m_typeResolver->containedType(m_function->returnType);
+ const auto stored = m_function->returnType.storedType();
+ if (m_typeResolver->equals(contained, stored)
+ || (contained->isReferenceType() && stored->isReferenceType())) {
+ m_body += u" *static_cast<"_s
+ + stored->augmentedInternalName()
+ + u" *>(argv[0]) = "_s
+ + conversion(m_state.accumulatorIn(), m_function->returnType,
+ consumedAccumulatorVariableIn())
+ + u";\n"_s;
+ } else if (m_typeResolver->registerContains(m_state.accumulatorIn(), contained)) {
+ m_body += u" const QMetaType returnType = "_s + contentType(m_state.accumulatorIn(), in)
+ + u";\n"_s;
+ m_body += u" returnType.destruct(argv[0]);\n"_s;
+ m_body += u" returnType.construct(argv[0], "_s
+ + contentPointer(m_state.accumulatorIn(), in) + u");\n"_s;
+ } else {
+ m_body += u" const auto converted = "_s
+ + conversion(m_state.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);
+ return;
+ }
+ m_body += conversion(contained, changed, input) + u";\n"_s;
+}
+
+void QQmlJSCodeGenerator::generate_LoadReg(int reg)
+{
+ INJECT_TRACE_INFO(generate_LoadReg);
+
+ 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"
+ 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.
+ 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)
+ BYTECODE_UNIMPLEMENTED();
+}
+
+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 + m_state.accumulatorVariableOut + u", "_s
+ + metaTypeFromType(m_state.accumulatorOut().storedType()) + u')';
+ const QString initialization = u"aotContext->initLoadGlobalLookup("_s
+ + QString::number(index) + u')';
+ generateLookup(lookup, initialization);
+}
+
+void QQmlJSCodeGenerator::generate_LoadQmlContextPropertyLookup(int index)
+{
+ INJECT_TRACE_INFO(generate_LoadQmlContextPropertyLookup);
+
+ AccumulatorConverter registers(this);
+
+ const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
+ const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
+ if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::JavaScriptGlobal) {
+ m_body += m_state.accumulatorVariableOut + u" = "_s
+ + conversion(
+ m_typeResolver->original(m_state.accumulatorOut()), 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();
+ const QQmlJSScope::ConstPtr scope = m_state.accumulatorOut().scopeType();
+ const QQmlJSScope::ConstPtr stored = m_state.accumulatorOut().storedType();
+ if (isProperty) {
+ const auto lookupType = contentType(m_state.accumulatorOut(), m_state.accumulatorVariableOut);
+
+ 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", "_s
+ + lookupType + u')';
+ const QString preparation = getLookupPreparation(
+ m_state.accumulatorOut(), m_state.accumulatorVariableOut, index);
+
+ 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::ScopeProperty:
+ case QQmlJSRegisterContent::ExtensionScopeProperty: {
+ // 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::ScopeMethod:
+ case QQmlJSRegisterContent::ExtensionScopeMethod:
+ reject(u"assignment to scope method"_s);
+ break;
+ 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()
+ && !m_typeResolver->registerIsStoredIn(baseType, m_typeResolver->stringType())) {
+ reject(u"LoadElement with non-list base type "_s + baseType.descriptiveName());
+ return;
+ }
+
+ const QString voidAssignment = u" "_s + m_state.accumulatorVariableOut + u" = "_s +
+ conversion(m_typeResolver->globalType(m_typeResolver->voidType()),
+ m_state.accumulatorOut(), QString()) + u";\n"_s;
+
+ QString indexName = m_state.accumulatorVariableIn;
+ QQmlJSScope::ConstPtr indexType;
+ if (m_typeResolver->isNumeric(m_state.accumulatorIn())) {
+ indexType = m_typeResolver->containedType(m_state.accumulatorIn());
+ } else if (m_state.accumulatorIn().isConversion()) {
+ const auto target = m_typeResolver->extractNonVoidFromOptionalType(m_state.accumulatorIn());
+ if (m_typeResolver->isNumeric(target)) {
+ indexType = target;
+ 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);
+ return;
+ }
+ }
+
+ AccumulatorConverter registers(this);
+ 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 (m_typeResolver->registerIsStoredIn(baseType, m_typeResolver->listPropertyType())) {
+ // Our QQmlListProperty only keeps plain QObject*.
+ const auto elementType = m_typeResolver->globalType(m_typeResolver->qObjectType());
+
+ 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(elementType, m_state.accumulatorOut(),
+ baseName + u".at(&"_s + baseName + u", "_s
+ + indexName + u')') + u";\n"_s;
+ m_body += u"else\n"_s
+ + voidAssignment;
+ return;
+ }
+
+ const auto elementType = m_typeResolver->valueType(baseType);
+
+ QString access = baseName + u".at("_s + indexName + u')';
+
+ // TODO: Once we get a char type in QML, use it here.
+ if (m_typeResolver->registerIsStoredIn(baseType, m_typeResolver->stringType()))
+ access = u"QString("_s + access + u")"_s;
+ else if (m_state.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 = m_typeResolver->containedType(registerType(index));
+
+ if (!m_typeResolver->isNumeric(indexType) || !baseType.isList()) {
+ reject(u"StoreElement with non-list base type or non-numeric arguments"_s);
+ return;
+ }
+
+ if (baseType.storedType()->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence) {
+ reject(u"indirect StoreElement"_s);
+ return;
+ }
+
+ const QString baseName = registerVariable(base);
+ const QString indexName = registerVariable(index);
+
+ const auto valueType = m_typeResolver->valueType(baseType);
+ const auto elementType = m_typeResolver->globalType(m_typeResolver->genericType(
+ m_typeResolver->containedType(valueType)));
+
+ 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 (m_typeResolver->registerIsStoredIn(baseType, 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 (m_state.isRegisterAffectedBySideEffects(base))
+ reject(u"LoadElement on a sequence 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 (enumMember.isEmpty()) {
+ // 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.
+ reject(u"Lookup of enum metatype"_s);
+ return;
+ }
+
+ // 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;
+ 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;
+ 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::ScopeModulePrefix:
+ break;
+ case QQmlJSRegisterContent::ScopeAttached: {
+ 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);
+ break;
+ case QQmlJSRegisterContent::MetaType: {
+ if (!m_typeResolver->registerIsStoredIn(
+ m_state.accumulatorOut(), 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(
+ const QQmlJSRegisterContent &nonStorableContent, const QString &registerName, bool invert)
+{
+ const auto nonStorableType = m_typeResolver->containedType(nonStorableContent);
+ QQmlJSScope::ConstPtr comparedType =
+ m_typeResolver->equals(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)
+ + (m_typeResolver->equals(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
+ + (m_typeResolver->equals(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 (m_typeResolver->equals(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(
+ const 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 auto contained = storableContent.isEnumeration()
+ ? storableContent.storedType()
+ : m_typeResolver->containedType(storableContent);
+
+ if (contained->isReferenceType()) {
+ const QQmlJSRegisterContent comparable
+ = m_typeResolver->builtinType(m_typeResolver->qObjectType());
+ m_body += m_state.accumulatorVariableOut + u" = "_s + (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");\n";
+ return;
+ }
+
+ if (m_typeResolver->isPrimitive(contained)) {
+ const QQmlJSRegisterContent comparable
+ = m_typeResolver->builtinType(m_typeResolver->jsPrimitiveType());
+ m_body += m_state.accumulatorVariableOut + u" = "_s + (invert ? u"!"_s : QString())
+ + conversion(storableContent, comparable, typedRegisterName)
+ + u".strictlyEquals("_s
+ + conversion(m_typeResolver->varType(), comparable, varRegisterName) + 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 value = stored->valueType();
+ Q_ASSERT(value);
+
+ QStringList initializer;
+ for (int i = 0; i < argc; ++i) {
+ initializer += convertStored(
+ registerType(argv + i).storedType(), value,
+ 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 = m_state.isRegisterAffectedBySideEffects(registerIndex);
+
+ for (QQmlJSRegisterContent writeBack = registerType(registerIndex);
+ !writeBack.storedType()->isReferenceType();) {
+ 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();
+ if (lookupIndex == -1) {
+ // 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.
+ reject(u"write-back of non-lookup"_s);
+ break;
+ }
+
+ const QString writeBackIndexString = QString::number(lookupIndex);
+
+ const QQmlJSRegisterContent::ContentVariant variant = writeBack.variant();
+ if (variant == QQmlJSRegisterContent::ScopeProperty
+ || variant == QQmlJSRegisterContent::ExtensionScopeProperty) {
+ const QString lookup = u"aotContext->writeBackScopeObjectPropertyLookup("_s
+ + writeBackIndexString
+ + u", "_s + contentPointer(writeBack, writeBackRegister) + u')';
+ const QString initialization
+ = u"aotContext->initLoadScopeObjectPropertyLookup("_s
+ + writeBackIndexString
+ + u", "_s + contentType(writeBack, writeBackRegister) + u')';
+ 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 (!outerContent.isValid()) {
+ // If the lookup doesn't exist, it was killed by state merge.
+ reject(u"write-back of lookup across jumps or merges."_s);
+ break;
+ }
+
+ Q_ASSERT(!outerRegister.isEmpty());
+
+ switch (writeBack.variant()) {
+ case QQmlJSRegisterContent::ScopeProperty:
+ case QQmlJSRegisterContent::ExtensionScopeProperty:
+ Q_UNREACHABLE();
+ case QQmlJSRegisterContent::ObjectProperty:
+ case QQmlJSRegisterContent::ExtensionObjectProperty:
+ if (writeBack.scopeType()->isReferenceType()) {
+ const QString lookup = u"aotContext->writeBackObjectLookup("_s
+ + writeBackIndexString
+ + u", "_s + outerRegister
+ + u", "_s + contentPointer(writeBack, writeBackRegister) + u')';
+ const QString initialization = u"aotContext->initGetObjectLookup("_s
+ + writeBackIndexString
+ + u", "_s + outerRegister
+ + u", "_s + contentType(writeBack, writeBackRegister) + u')';
+ 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", "_s + contentType(writeBack, writeBackRegister) + u')';
+ 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, const QQmlJSRegisterContent &actual,
+ const QString &variable, const QString &errorMessage)
+{
+ const QQmlJSScope::ConstPtr scope = required;
+ const QQmlJSScope::ConstPtr input = m_typeResolver->containedType(actual);
+ if (QQmlJSUtils::searchBaseAndExtensionTypes(input,
+ [&](const QQmlJSScope::ConstPtr &base) {
+ return m_typeResolver->equals(base, scope);
+ })) {
+ return false;
+ }
+
+ if (!m_typeResolver->canHold(input, scope)) {
+ reject(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 (m_typeResolver->equals(actual.storedType(), 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(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::resolveValueTypeContentPointer(
+ const QQmlJSScope::ConstPtr &required, const 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, const 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);
+}
+
+void QQmlJSCodeGenerator::generate_GetLookupHelper(int index)
+{
+ if (m_state.accumulatorOut().isMethod()) {
+ reject(u"lookup of function property."_s);
+ return;
+ }
+
+ if (m_typeResolver->equals(m_state.accumulatorOut().scopeType(), 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::ObjectModulePrefix);
+ // 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 QQmlJSScope::ConstPtr scope = m_state.accumulatorOut().scopeType();
+ const bool isReferenceType = scope->isReferenceType();
+
+ switch (m_state.accumulatorOut().variant()) {
+ case QQmlJSRegisterContent::ObjectAttached: {
+ 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::ScopeAttached:
+ case QQmlJSRegisterContent::Singleton:
+ case QQmlJSRegisterContent::Script:
+ case QQmlJSRegisterContent::MetaType: {
+ generateTypeLookup(index);
+ return;
+ }
+ default:
+ break;
+ }
+
+ Q_ASSERT(m_state.accumulatorOut().isProperty());
+
+ if (m_typeResolver->registerIsStoredIn(accumulatorIn, m_typeResolver->jsValueType())) {
+ reject(u"lookup in QJSValue"_s);
+ } else if (isReferenceType) {
+ const QString inputPointer = resolveQObjectPointer(
+ scope, 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 = u"aotContext->initGetObjectLookup("_s
+ + indexString + u", "_s + inputPointer
+ + 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);
+ } else if ((scope->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
+ || m_typeResolver->equals(scope, 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(
+ m_typeResolver->globalType(m_typeResolver->sizeType()),
+ 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
+ || m_typeResolver->equals(stored, m_typeResolver->stringType())) {
+ m_body += m_state.accumulatorVariableOut + u" = "_s
+ + conversion(m_typeResolver->globalType(m_typeResolver->sizeType()),
+ m_state.accumulatorOut(),
+ 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 (m_typeResolver->registerIsStoredIn(accumulatorIn,
+ m_typeResolver->variantMapType())) {
+ QString mapLookup = m_state.accumulatorVariableIn + u"["_s
+ + QQmlJSUtils::toLiteral(m_jsUnitGenerator->lookupName(index)) + u"]"_s;
+ m_body += m_state.accumulatorVariableOut + u" = "_s;
+ m_body += conversion(m_typeResolver->globalType(m_typeResolver->varType()),
+ m_state.accumulatorOut(), mapLookup);
+ m_body += u";\n"_s;
+ } 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, accumulatorIn, m_state.accumulatorVariableIn,
+ u"Cannot read property '%1' of %2"_s.arg(
+ m_jsUnitGenerator->lookupName(index)));
+
+ 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) + 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_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 (m_typeResolver->equals(accumulatorIn.storedType(), 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 (m_typeResolver->equals(accumulatorIn.storedType(), 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::ObjectEnum) {
+ // Nothing
+ } else if (m_typeResolver->equals(accumulatorIn.storedType(), m_typeResolver->jsValueType())) {
+ m_body += u"if (%1.isNull() || %1.isUndefined())\n"_s.arg(accumulatorVarIn);
+ generateJumpCodeWithTypeConversions(offset);
+ } 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);
+}
+
+QString QQmlJSCodeGenerator::setLookupPreparation(
+ const QQmlJSRegisterContent &content, const QString &arg, int lookup)
+{
+ if (m_typeResolver->registerContains(content, content.storedType()))
+ return QString();
+
+ if (m_typeResolver->registerIsStoredIn(content, m_typeResolver->varType())) {
+ return u"const QMetaType argType = aotContext->lookupResultMetaType("_s
+ + QString::number(lookup) + u");\n"_s
+ + u"if (argType.isValid())\n "_s + arg + u".convert(argType)";
+ }
+ // TODO: We could make sure they're compatible, for example QObject pointers.
+ return QString();
+}
+
+
+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 specific = m_state.readAccumulator();
+ Q_ASSERT(specific.isConversion());
+ const QQmlJSScope::ConstPtr originalScope
+ = m_typeResolver->originalType(specific.conversionResultScope());
+
+ if (specific.storedType().isNull()) {
+ reject(u"SetLookup. Could not find property "
+ + m_jsUnitGenerator->lookupName(index)
+ + u" on type "
+ + originalScope->internalName());
+ return;
+ }
+
+ // Choose a container that can hold both, the "in" accumulator and what we actually want.
+ // If the types are all the same because we can all store them as verbatim C++ types,
+ // the container will also be that type.
+
+ QQmlJSRegisterContent property = specific;
+ if (!m_typeResolver->equals(specific.storedType(), valueType)) {
+ if (m_typeResolver->isPrimitive(specific.storedType())
+ && m_typeResolver->isPrimitive(valueType)) {
+ // Preferably store in QJSPrimitiveValue since we need the content pointer below.
+ property = property.storedIn(m_typeResolver->jsPrimitiveType());
+ } else {
+ property = property.storedIn(m_typeResolver->merge(specific.storedType(), valueType));
+ }
+ }
+
+ const QString object = registerVariable(baseReg);
+ m_body += u"{\n"_s;
+ QString variableIn;
+ QString variableInType;
+ QString preparation;
+ QString argType;
+ if (!m_typeResolver->registerContains(
+ m_state.accumulatorIn(), m_typeResolver->containedType(property))) {
+ m_body += u"auto converted = "_s
+ + conversion(m_state.accumulatorIn(), property, consumedAccumulatorVariableIn())
+ + u";\n"_s;
+ variableIn = contentPointer(property, u"converted"_s);
+ variableInType = contentType(property, u"converted"_s);
+ preparation = setLookupPreparation(property, u"converted"_s, index);
+ if (preparation.isEmpty())
+ argType = contentType(property, u"converted"_s);
+ else
+ argType = u"argType"_s;
+ } else {
+ variableIn = contentPointer(property, m_state.accumulatorVariableIn);
+ variableInType = contentType(property, m_state.accumulatorVariableIn);
+ argType = variableInType;
+ }
+
+ 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')';
+ const QString initialization = u"aotContext->initSetObjectLookup("_s
+ + indexString + u", "_s + basePointer + u", "_s + argType + u')';
+ generateLookup(lookup, initialization, preparation);
+ break;
+ }
+ case QQmlJSScope::AccessSemantics::Sequence: {
+ const QString propertyName = m_jsUnitGenerator->lookupName(index);
+ if (propertyName != u"length"_s) {
+ reject(u"setting non-length property on a sequence type"_s);
+ break;
+ }
+
+ if (!originalScope->isListProperty()) {
+ reject(u"resizing sequence types (because of missing write-back)"_s);
+ break;
+ }
+
+ // 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
+ + (variableIn.startsWith(u'&') ? variableIn.mid(1) : (u'*' + 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);
+ const QString baseContentPointer = resolveValueTypeContentPointer(
+ originalScope, base, object,
+ u"TypeError: Value is %1 and could not be converted to an object"_s);
+
+ const QString lookup = u"aotContext->setValueLookup("_s + indexString
+ + u", "_s + baseContentPointer
+ + u", "_s + variableIn + u')';
+ const QString initialization = u"aotContext->initSetValueLookup("_s
+ + indexString + u", "_s + metaObject(originalScope)
+ + u", "_s + argType + u')';
+
+ generateLookup(lookup, initialization, preparation);
+ 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::argumentsList(int argc, int argv, QString *outVar)
+{
+ QString types;
+ QString args;
+
+ if (m_state.changedRegisterIndex() == InvalidRegister ||
+ m_typeResolver->registerContains(
+ m_state.accumulatorOut(), m_typeResolver->voidType())) {
+ types = u"QMetaType()"_s;
+ args = u"nullptr"_s;
+ } else {
+ *outVar = u"callResult"_s;
+ const QQmlJSScope::ConstPtr outType = m_state.accumulatorOut().storedType();
+ m_body += outType->augmentedInternalName() + u' ' + *outVar;
+ if (!m_typeResolver->registerContains(m_state.accumulatorOut(), outType)) {
+ if (m_typeResolver->equals(outType, m_typeResolver->varType())
+ || m_typeResolver->equals(outType, m_typeResolver->jsPrimitiveType())) {
+ m_body += u'('
+ + metaType(m_typeResolver->containedType(m_state.accumulatorOut()))
+ + u')';
+ }
+ }
+ m_body += u";\n";
+
+ args = contentPointer(m_state.accumulatorOut(), *outVar);
+ types = contentType(m_state.accumulatorOut(), *outVar);
+ }
+
+ for (int i = 0; i < argc; ++i) {
+ const QQmlJSRegisterContent content = registerType(argv + i);
+ const QString var = registerVariable(argv + i);
+ args += u", "_s + contentPointer(content, var);
+ types += u", "_s + contentType(content, var);
+ }
+
+ return u"void *args[] = { "_s + args + u" };\n"_s
+ + u"const QMetaType types[] = { "_s + types + u" };\n"_s;
+}
+
+void QQmlJSCodeGenerator::generateMoveOutVar(const QString &outVar)
+{
+ if (m_state.accumulatorVariableOut.isEmpty() || outVar.isEmpty())
+ return;
+
+ m_body += m_state.accumulatorVariableOut + u" = "_s;
+ m_body += u"std::move(" + outVar + u");\n";
+}
+
+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(m_typeResolver->containedType(input)));
+ else if (m_typeResolver->registerContains(input, 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 for internalName == "QQmlLoggingCategory" here, but we don't want to
+ // because QQmlLoggingCategory is not a builtin. Tying the specific internal name and
+ // intheritance hierarchy in here would be fragile.
+ // TODO: We could drop the check for firstArg in some cases if we made some base class
+ // of QQmlLoggingCategory a builtin.
+ const bool firstArgIsReference = argc > 0
+ && m_typeResolver->containedType(firstArg)->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" : u"nullptr";
+ 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 stored = m_state.readRegister(argv + i).storedType();
+ if (m_typeResolver->equals(stored, m_typeResolver->stringType())) {
+ return convertStored(
+ registerType(argv + i).storedType(),
+ m_typeResolver->stringType(), consumedRegisterVariable(argv + i));
+ } else if (stored->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(u"converting arguments for console method to string"_s);
+ return QString();
+ }
+ };
+
+ 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 valueType = registerType(base).storedType()->valueType();
+ 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(), valueType,
+ 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")";
+
+ const auto outType = baseType.storedType()->isListProperty()
+ ? m_typeResolver->globalType(m_typeResolver->qObjectListType())
+ : baseType;
+
+ m_body += m_state.accumulatorVariableOut + u" = "_s
+ + conversion(outType, m_state.accumulatorOut(), call) + u";\n"_s;
+ return true;
+ }
+
+ if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) {
+ QString call = qjsListMethod
+ + convertStored(registerType(argv).storedType(), valueType,
+ 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);
+
+ if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::JavaScriptReturnValue)
+ reject(u"call to untyped JavaScript function"_s);
+
+ const QQmlJSScope::ConstPtr scope = m_state.accumulatorOut().scopeType();
+
+ AccumulatorConverter registers(this);
+
+ const QQmlJSRegisterContent baseType = registerType(base);
+ const QString name = m_jsUnitGenerator->lookupName(index);
+
+ if (m_typeResolver->equals(scope, m_typeResolver->mathObject())) {
+ if (inlineMathMethod(name, argc, argv))
+ return;
+ } else if (m_typeResolver->equals(scope, m_typeResolver->consoleObject())) {
+ if (inlineConsoleMethod(name, argc, argv))
+ return;
+ } else if (m_typeResolver->equals(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 (!scope->isReferenceType()) {
+ // This is possible, once we establish the right kind of lookup for it
+ reject(u"call to property '%1' of %2"_s.arg(name, baseType.descriptiveName()));
+ }
+
+ const QString inputPointer = resolveQObjectPointer(
+ scope, baseType, registerVariable(base),
+ u"Cannot call method '%1' of %2"_s.arg(name));
+
+ const QString indexString = QString::number(index);
+
+ m_body += u"{\n"_s;
+
+ QString outVar;
+ m_body += argumentsList(argc, argv, &outVar);
+ const QString lookup = u"aotContext->callObjectPropertyLookup("_s + indexString
+ + u", "_s + inputPointer
+ + u", args, types, "_s + QString::number(argc) + u')';
+ const QString initialization = u"aotContext->initCallObjectPropertyLookup("_s
+ + indexString + u')';
+ generateLookup(lookup, initialization);
+ generateMoveOutVar(outVar);
+
+ m_body += u"}\n"_s;
+}
+
+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().variant() == QQmlJSRegisterContent::JavaScriptReturnValue)
+ reject(u"call to untyped JavaScript function"_s);
+
+ if (m_typeResolver->equals(m_state.accumulatorOut().scopeType(),
+ m_typeResolver->jsGlobalObject())) {
+ const QString name = m_jsUnitGenerator->stringForIndex(
+ m_jsUnitGenerator->lookupNameIndex(index));
+ if (inlineTranslateMethod(name, argc, argv))
+ return;
+ }
+
+ AccumulatorConverter registers(this);
+
+ const QString indexString = QString::number(index);
+
+ m_body += u"{\n"_s;
+ QString outVar;
+ m_body += argumentsList(argc, argv, &outVar);
+ const QString lookup = u"aotContext->callQmlContextPropertyLookup("_s + indexString
+ + u", args, types, "_s + QString::number(argc) + u')';
+ const QString initialization = u"aotContext->initCallQmlContextPropertyLookup("_s
+ + indexString + u')';
+ generateLookup(lookup, initialization);
+ generateMoveOutVar(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 original = m_typeResolver->original(m_state.accumulatorOut());
+
+ if (m_typeResolver->registerContains(original, 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_typeResolver->registerContains(
+ m_state.readRegister(argv), 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 (m_typeResolver->registerContains(original, m_typeResolver->variantListType())) {
+ rejectIfBadArray();
+
+ if (argc == 1
+ && m_typeResolver->registerContains(
+ m_state.readRegister(argv), 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 = m_typeResolver->containedType(registerType(argv));
+ 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_error->isValid()) {
+ generateArrayInitializer(argc, argv);
+ }
+ 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"SetUnwindHandlerh"_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->globalType(
+ 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);
+ return;
+ }
+
+ const QString identifier = QString::number(iteratorType.baseLookupIndex());
+ const QString iteratorName = m_state.accumulatorVariableOut + u"Iterator" + identifier;
+ const QString listName = m_state.accumulatorVariableOut + 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 (!m_typeResolver->equals(iteratorType.storedType(), 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 (!m_typeResolver->equals(iteratorType.storedType(), 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);
+ return;
+ }
+
+ const QQmlJSScope::ConstPtr iteratorType = iteratorContent.storedType();
+ const QString iteratorTypeName = iteratorType->internalName();
+ const QString listName = m_state.accumulatorVariableIn
+ + u"List" + QString::number(iteratorContent.baseLookupIndex());
+ QString qjsList;
+ if (m_typeResolver->equals(iteratorType, m_typeResolver->forOfIteratorPtr()))
+ qjsList = u"QJSList(&" + listName + u", aotContext->engine)";
+ else if (!m_typeResolver->equals(iteratorType, m_typeResolver->forInIteratorPtr()))
+ reject(u"using non-iterator as iterator"_s);
+
+ m_body += u"if (" + m_state.accumulatorVariableIn + u"->hasNext(" + qjsList + u")) {\n ";
+ m_body += changedRegisterVariable() + u" = "
+ + conversion(
+ m_typeResolver->valueType(iteratorContent),
+ 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_error->isValid())
+ 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);
+ return;
+ }
+
+ const QQmlJSScope::ConstPtr contained = m_typeResolver->containedType(m_state.accumulatorOut());
+
+ const int classSize = m_jsUnitGenerator->jsClassSize(internalClassId);
+ Q_ASSERT(argc >= classSize);
+
+ if (m_typeResolver->equals(contained, m_typeResolver->varType())
+ || m_typeResolver->equals(contained, m_typeResolver->variantMapType())) {
+
+ m_body += m_state.accumulatorVariableOut + u" = QVariantMap {\n";
+ const QQmlJSScope::ConstPtr propType = m_typeResolver->varType();
+ for (int i = 0; i < classSize; ++i) {
+ m_body += 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);
+ m_body += convertStored(argType, propType, consumedArg) + u" },\n";
+ }
+
+ for (int i = classSize; i < argc; i += 3) {
+ const int nameArg = args + i + 1;
+ m_body += u"{ "_s
+ + conversion(
+ registerType(nameArg),
+ m_typeResolver->globalType(m_typeResolver->stringType()),
+ consumedRegisterVariable(nameArg))
+ + u", "_s;
+
+ const int valueArg = args + i + 2;
+ m_body += convertStored(
+ registerType(valueArg).storedType(),
+ propType,
+ consumedRegisterVariable(valueArg))
+ + u" },\n";
+ }
+
+ m_body += u"};\n";
+ return;
+ }
+
+ m_body += m_state.accumulatorVariableOut + u" = "_s + stored->augmentedInternalName();
+ const bool isVariantOrPrimitive = m_typeResolver->equals(stored, m_typeResolver->varType())
+ || m_typeResolver->equals(stored, m_typeResolver->jsPrimitiveType());
+
+ if (m_typeResolver->registerContains(m_state.accumulatorOut(), stored)) {
+ m_body += u"()";
+ } else if (isVariantOrPrimitive) {
+ m_body += u'(' + metaType(m_typeResolver->containedType(m_state.accumulatorOut())) + 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" auto arg = "_s + argument + u";\n";
+ argument = u"arg"_s;
+ }
+
+ int index = property.index();
+ if (index == -1)
+ continue;
+
+ m_body += u" void *argv[] = { %1, nullptr };\n"_s
+ .arg(contentPointer(propType, argument));
+ m_body += u" meta->d.static_metacall(reinterpret_cast<QObject *>(";
+ m_body += contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut);
+ m_body += u"), QMetaObject::WriteProperty, ";
+ m_body += QString::number(index) + u", argv);\n";
+ m_body += u" }\n";
+ }
+
+ // 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);
+
+ 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(
+ m_typeResolver->globalType(m_typeResolver->nullType()), QString(), u"equals"_s, false);
+}
+
+void QQmlJSCodeGenerator::generate_CmpNeNull()
+{
+ INJECT_TRACE_INFO(generate_CmlNeNull);
+ generateEqualityOperation(
+ m_typeResolver->globalType(m_typeResolver->nullType()), QString(), u"equals"_s, true);
+}
+
+QString QQmlJSCodeGenerator::getLookupPreparation(
+ const QQmlJSRegisterContent &content, const QString &var, int lookup)
+{
+ if (m_typeResolver->registerContains(content, content.storedType()))
+ return QString();
+
+ if (m_typeResolver->registerIsStoredIn(content, m_typeResolver->varType())) {
+ return var + u" = QVariant(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(const QQmlJSRegisterContent &content, const QString &var)
+{
+ const QQmlJSScope::ConstPtr stored = content.storedType();
+ if (m_typeResolver->registerContains(content, stored))
+ return u'&' + var;
+
+ if (m_typeResolver->registerIsStoredIn(content, m_typeResolver->varType())
+ || m_typeResolver->registerIsStoredIn(content, m_typeResolver->jsPrimitiveType())) {
+ return var + u".data()"_s;
+ }
+
+ if (stored->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
+ return u'&' + var;
+
+ if (m_typeResolver->isNumeric(content.storedType())
+ && m_typeResolver->containedType(content)->scopeType() == QQmlSA::ScopeType::EnumScope) {
+ return u'&' + var;
+ }
+
+ if (stored->isListProperty() && m_typeResolver->containedType(content)->isListProperty())
+ return u'&' + var;
+
+ reject(u"content pointer of unsupported wrapper type "_s + content.descriptiveName());
+ return QString();
+}
+
+QString QQmlJSCodeGenerator::contentType(const QQmlJSRegisterContent &content, const QString &var)
+{
+ const QQmlJSScope::ConstPtr stored = content.storedType();
+ const QQmlJSScope::ConstPtr contained = m_typeResolver->containedType(content);
+ if (m_typeResolver->equals(contained, stored))
+ return metaTypeFromType(stored);
+
+ if (m_typeResolver->equals(stored, m_typeResolver->varType())
+ || m_typeResolver->registerIsStoredIn(content, 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(u"content type of unsupported wrapper type "_s + content.descriptiveName());
+ return QString();
+}
+
+void QQmlJSCodeGenerator::generate_CmpEqInt(int lhsConst)
+{
+ INJECT_TRACE_INFO(generate_CmpEqInt);
+
+ generateEqualityOperation(
+ m_typeResolver->globalType(m_typeResolver->int32Type()), QString::number(lhsConst),
+ u"equals"_s, false);
+}
+
+void QQmlJSCodeGenerator::generate_CmpNeInt(int lhsConst)
+{
+ INJECT_TRACE_INFO(generate_CmpNeInt);
+
+ generateEqualityOperation(
+ m_typeResolver->globalType(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 original 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 = m_typeResolver->original(outputContent);
+ const QQmlJSScope::ConstPtr target = originalContent.storedType()->isReferenceType()
+ ? m_typeResolver->containedType(originalContent)
+ : m_typeResolver->extractNonVoidFromOptionalType(originalContent);
+
+ if (!target) {
+ reject(u"type assertion to unknown type"_s);
+ return;
+ }
+
+ 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_typeResolver->equals(
+ m_state.accumulatorIn().storedType(), 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 (m_typeResolver->registerIsStoredIn(inputContent, m_typeResolver->varType())
+ || m_typeResolver->registerIsStoredIn(inputContent, m_typeResolver->jsPrimitiveType())) {
+
+ const auto source = m_typeResolver->extractNonVoidFromOptionalType(
+ m_typeResolver->original(inputContent));
+
+ if (source && m_typeResolver->equals(source, target)) {
+ m_body += input + u".metaType() == "_s + metaType(target)
+ + u" ? " + conversion(inputContent, outputContent, input)
+ + u" : " + conversion(
+ m_typeResolver->globalType(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_error->isValid() || !lhsString.isEmpty());
+ Q_ASSERT(m_error->isValid() || !rhsString.isEmpty());
+
+ const QQmlJSRegisterContent originalOut = m_typeResolver->original(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);
+ 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_error->isValid() || !lhsVar.isEmpty());
+ Q_ASSERT(m_error->isValid() || !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())) {
+ const QQmlJSRegisterContent &content = accumulatorIn.value().content;
+ m_state.accumulatorVariableIn = m_registerVariables.value(RegisterVariablesKey {
+ content.storedType()->internalName(),
+ Accumulator,
+ content.resultLookupIndex()
+ }).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.hasSideEffects() && changedRegisterVariable().isEmpty()) {
+ generateJumpCodeWithTypeConversions(0);
+ return SkipInstruction;
+ }
+
+ return ProcessInstruction;
+}
+
+void QQmlJSCodeGenerator::endInstruction(QV4::Moth::Instr::Type)
+{
+ if (!m_skipUntilNextLabel)
+ generateJumpCodeWithTypeConversions(0);
+}
+
+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(
+ const QQmlJSRegisterContent &lhsContent, const 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 auto rhsContained = rhsIsOptional
+ ? m_typeResolver->extractNonVoidFromOptionalType(rhsContent)
+ : m_typeResolver->containedType(rhsContent);
+
+ const auto lhsContained = lhsIsOptional
+ ? m_typeResolver->extractNonVoidFromOptionalType(lhsContent)
+ : m_typeResolver->containedType(lhsContent);
+
+ 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) && 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](const QQmlJSRegisterContent &content) {
+ const auto contained = m_typeResolver->containedType(content);
+ const auto original = m_typeResolver->original(content);
+ const auto containedOriginal = m_typeResolver->containedType(original);
+
+ if (m_typeResolver->equals(
+ m_typeResolver->genericType(containedOriginal), original.storedType())) {
+ // The original type doesn't need any wrapping.
+ return original;
+ } else if (m_typeResolver->equals(contained, containedOriginal)) {
+ if (original.isConversion()) {
+ // The original conversion origins are more accurate
+ return original.storedIn(content.storedType());
+ }
+ } else if (m_typeResolver->canHold(contained, containedOriginal)) {
+ return original.storedIn(content.storedType());
+ }
+
+ return content;
+ };
+
+ if (!isComparable()) {
+ QQmlJSRegisterContent lhsOriginal = retrieveOriginal(lhsContent);
+ QQmlJSRegisterContent rhsOriginal = retrieveOriginal(rhsContent);
+ if (lhsOriginal != lhsContent || rhsOriginal != rhsContent) {
+ // 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(lhsContent.storedType(), lhsOriginal, lhsName),
+ conversion(rhsContent.storedType(), rhsOriginal, rhsName),
+ function, invert);
+ return;
+ }
+
+ reject(u"incomparable types %1 and %2"_s.arg(
+ rhsContent.descriptiveName(), lhsContent.descriptiveName()));
+ }
+
+ const QQmlJSScope::ConstPtr lhsType = lhsContent.storedType();
+ const QQmlJSScope::ConstPtr rhsType = rhsContent.storedType();
+
+ 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 (m_typeResolver->registerContains(lhsContent, m_typeResolver->varType())) {
+ generateVariantEqualityComparison(rhsContent, rhsName, lhsName, invert);
+ return;
+ }
+
+ if (m_typeResolver->registerContains(rhsContent, 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 (m_typeResolver->equals(lhsType, rhsType)
+ && !m_typeResolver->equals(lhsType, primitive)
+ && !m_typeResolver->equals(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(!m_typeResolver->equals(lhsType, m_typeResolver->voidType()));
+ Q_ASSERT(!m_typeResolver->equals(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 (m_typeResolver->equals(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 (m_typeResolver->equals(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, 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_error->isValid() || !lhs.isEmpty());
+ Q_ASSERT(m_error->isValid() || !rhs.isEmpty());
+
+ const QQmlJSRegisterContent originalOut = m_typeResolver->original(m_state.accumulatorOut());
+ m_body += m_state.accumulatorVariableOut;
+ m_body += u" = "_s;
+ m_body += conversion(
+ originalOut, m_state.accumulatorOut(),
+ 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(m_typeResolver->globalType(m_typeResolver->int32Type()),
+ m_state.readAccumulator(), QString::number(rhsConst)),
+ cppOperator);
+}
+
+void QQmlJSCodeGenerator::generateUnaryOperation(const QString &cppOperator)
+{
+ const auto var = conversion(m_state.accumulatorIn(),
+ m_typeResolver->original(m_state.readAccumulator()),
+ consumedAccumulatorVariableIn());
+
+ if (var == m_state.accumulatorVariableOut) {
+ m_body += m_state.accumulatorVariableOut + u" = "_s + cppOperator + var + u";\n"_s;
+ return;
+ }
+
+ const auto original = m_typeResolver->original(m_state.accumulatorOut());
+ if (m_state.accumulatorOut() == original) {
+ 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(
+ original, 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 original = m_typeResolver->original(m_state.accumulatorOut());
+ if (m_state.accumulatorOut() == original) {
+ 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(
+ original, 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(RegisterVariablesKey {
+ targetType.storedType()->internalName(),
+ registerIndex,
+ targetType.resultLookupIndex()
+ });
+
+ 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();
+ currentVariable = u"std::move("_s + currentVariable + u')';
+ } 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
+{
+ const QQmlJSRegisterContent &content = registerType(index);
+ const auto it = m_registerVariables.constFind(RegisterVariablesKey {
+ content.storedType()->internalName(),
+ index,
+ content.resultLookupIndex()
+ });
+ 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().lookupIndex == 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
+{
+ const QQmlJSRegisterContent &changedRegister = m_state.changedRegister();
+
+ const QQmlJSScope::ConstPtr storedType = changedRegister.storedType();
+ if (storedType.isNull())
+ return QString();
+
+ return m_registerVariables.value(RegisterVariablesKey {
+ storedType->internalName(),
+ m_state.changedRegisterIndex(),
+ changedRegister.resultLookupIndex()
+ }).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(
+ const QQmlJSRegisterContent &from, const QQmlJSRegisterContent &to, const QString &variable)
+{
+ const QQmlJSScope::ConstPtr contained = m_typeResolver->containedType(to);
+
+ // If from is QJSPrimitiveValue and to contains a primitive we coerce using QJSPrimitiveValue
+ if (m_typeResolver->registerIsStoredIn(from, m_typeResolver->jsPrimitiveType())
+ && m_typeResolver->isPrimitive(to)) {
+
+ QString primitive = [&]() {
+ if (m_typeResolver->equals(contained, m_typeResolver->jsPrimitiveType()))
+ return variable;
+
+ const QString conversion = variable + u".to<QJSPrimitiveValue::%1>()"_s;
+ if (m_typeResolver->equals(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 (m_typeResolver->equals(contained, m_typeResolver->stringType()))
+ return conversion.arg(u"String"_s);
+ reject(u"Conversion of QJSPrimitiveValue to "_s + contained->internalName());
+ return QString();
+ }();
+
+ if (primitive.isEmpty())
+ return primitive;
+
+ return convertStored(m_typeResolver->jsPrimitiveType(), to.storedType(), primitive);
+ }
+
+ if (m_typeResolver->registerIsStoredIn(to, contained)
+ || m_typeResolver->isNumeric(to.storedType())
+ || to.storedType()->isReferenceType()
+ || m_typeResolver->registerContains(from, 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 (m_typeResolver->equals(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 (m_typeResolver->equals(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 (m_typeResolver->equals(to, m_typeResolver->floatType()))
+ return u"std::numeric_limits<float>::quiet_NaN()"_s;
+ if (m_typeResolver->equals(to, m_typeResolver->realType()))
+ return u"std::numeric_limits<double>::quiet_NaN()"_s;
+ if (m_typeResolver->equals(to, m_typeResolver->stringType()))
+ return QQmlJSUtils::toLiteral(u"undefined"_s);
+ if (m_typeResolver->equals(to, m_typeResolver->varType()))
+ return u"QVariant()"_s;
+ if (m_typeResolver->equals(to, m_typeResolver->jsValueType()))
+ return u"QJSValue();"_s;
+ if (m_typeResolver->equals(to, m_typeResolver->jsPrimitiveType()))
+ return u"QJSPrimitiveValue()"_s;
+ if (m_typeResolver->equals(from, to))
+ return QString();
+ }
+
+ if (m_typeResolver->equals(from, m_typeResolver->nullType())) {
+ if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
+ return u"static_cast<"_s + to->internalName() + u" *>(nullptr)"_s;
+ if (m_typeResolver->equals(to, jsValueType))
+ return u"QJSValue(QJSValue::NullValue)"_s;
+ if (m_typeResolver->equals(to, jsPrimitiveType))
+ return u"QJSPrimitiveValue(QJSPrimitiveNull())"_s;
+ if (m_typeResolver->equals(to, varType))
+ return u"QVariant::fromValue<std::nullptr_t>(nullptr)"_s;
+ const QString zero = zeroBoolOrInt(to);
+ if (!zero.isEmpty())
+ return zero;
+ if (m_typeResolver->equals(to, m_typeResolver->floatType()))
+ return u"0.0f"_s;
+ if (m_typeResolver->equals(to, m_typeResolver->realType()))
+ return u"0.0"_s;
+ if (m_typeResolver->equals(to, m_typeResolver->stringType()))
+ return QQmlJSUtils::toLiteral(u"null"_s);
+ if (m_typeResolver->equals(from, to))
+ return QString();
+ reject(u"Conversion from null to %1"_s.arg(to->internalName()));
+ }
+
+ if (m_typeResolver->equals(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 (m_typeResolver->equals(to, m_typeResolver->boolType())) {
+ return u'(' + variable + u" != nullptr)"_s;
+ }
+ }
+
+ auto isJsValue = [&](const QQmlJSScope::ConstPtr &candidate) {
+ return m_typeResolver->equals(candidate, jsValueType) || candidate->isScript();
+ };
+
+ if (isJsValue(from) && isJsValue(to))
+ return variable;
+
+ const auto isBoolOrNumber = [&](const QQmlJSScope::ConstPtr &type) {
+ return m_typeResolver->isNumeric(m_typeResolver->globalType(type))
+ || m_typeResolver->equals(type, m_typeResolver->boolType())
+ || type->scopeType() == QQmlSA::ScopeType::EnumScope;
+ };
+
+ if (m_typeResolver->equals(from, m_typeResolver->realType())
+ || m_typeResolver->equals(from, m_typeResolver->floatType())) {
+ 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 (m_typeResolver->equals(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 (m_typeResolver->equals(from, jsPrimitiveType)) {
+ if (m_typeResolver->equals(to, m_typeResolver->realType()))
+ return variable + u".toDouble()"_s;
+ if (m_typeResolver->equals(to, boolType))
+ return variable + u".toBoolean()"_s;
+ if (m_typeResolver->equals(to, m_typeResolver->int64Type())
+ || m_typeResolver->equals(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->equals(to, m_typeResolver->stringType()))
+ return variable + u".toString()"_s;
+ if (m_typeResolver->equals(to, jsValueType))
+ return u"QJSValue(QJSPrimitiveValue("_s + variable + u"))"_s;
+ if (m_typeResolver->equals(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 (m_typeResolver->equals(to, jsPrimitiveType))
+ return variable + u".toPrimitive()"_s;
+ if (m_typeResolver->equals(to, varType))
+ return variable + u".toVariant(QJSValue::RetainJSObjects)"_s;
+ return u"qjsvalue_cast<"_s + castTargetName(to) + u">("_s + variable + u')';
+ }
+
+ if (m_typeResolver->equals(to, jsPrimitiveType)) {
+ // null and undefined have been handled above already
+ Q_ASSERT(!m_typeResolver->equals(from, m_typeResolver->nullType()));
+ Q_ASSERT(!m_typeResolver->equals(from, m_typeResolver->voidType()));
+
+ if (m_typeResolver->equals(from, m_typeResolver->boolType())
+ || m_typeResolver->equals(from, m_typeResolver->int32Type())
+ || m_typeResolver->equals(from, m_typeResolver->realType())
+ || m_typeResolver->equals(from, m_typeResolver->stringType())) {
+ return u"QJSPrimitiveValue("_s + variable + u')';
+ } else if (m_typeResolver->equals(from, m_typeResolver->int16Type())
+ || m_typeResolver->equals(from, m_typeResolver->int8Type())
+ || m_typeResolver->equals(from, m_typeResolver->uint16Type())
+ || m_typeResolver->equals(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 (m_typeResolver->equals(to, jsValueType))
+ return u"aotContext->engine->toScriptValue("_s + variable + u')';
+
+ if (m_typeResolver->equals(from, varType)) {
+ if (m_typeResolver->equals(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 (m_typeResolver->equals(to, varType))
+ return u"QVariant::fromValue("_s + variable + u')';
+
+ if (m_typeResolver->equals(from, m_typeResolver->urlType())
+ && m_typeResolver->equals(to, m_typeResolver->stringType())) {
+ return variable + u".toString()"_s;
+ }
+
+ if (m_typeResolver->equals(from, m_typeResolver->stringType())
+ && m_typeResolver->equals(to, m_typeResolver->urlType())) {
+ return u"QUrl("_s + variable + u')';
+ }
+
+ if (m_typeResolver->equals(from, m_typeResolver->byteArrayType())
+ && m_typeResolver->equals(to, m_typeResolver->stringType())) {
+ return u"QString::fromUtf8("_s + variable + u')';
+ }
+
+ if (m_typeResolver->equals(from, m_typeResolver->stringType())
+ && m_typeResolver->equals(to, m_typeResolver->byteArrayType())) {
+ return variable + u".toUtf8()"_s;
+ }
+
+ for (const auto &originType : {
+ m_typeResolver->dateTimeType(),
+ m_typeResolver->dateType(),
+ m_typeResolver->timeType()}) {
+ if (m_typeResolver->equals(from, originType)) {
+ for (const auto &targetType : {
+ m_typeResolver->dateTimeType(),
+ m_typeResolver->dateType(),
+ m_typeResolver->timeType(),
+ m_typeResolver->stringType(),
+ m_typeResolver->realType()}) {
+ if (m_typeResolver->equals(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 (m_typeResolver->equals(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 (m_typeResolver->equals(type, m_typeResolver->realType()))
+ return expression + u".toDouble()"_s;
+ if (m_typeResolver->equals(type, m_typeResolver->floatType()))
+ return u"float("_s + expression + u".toDouble())"_s;
+ if (m_typeResolver->equals(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() && m_typeResolver->equals(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 (m_typeResolver->equals(to, m_typeResolver->boolType())) {
+ // All the interesting cases are already handled above:
+ Q_ASSERT(!m_typeResolver->equals(from, m_typeResolver->nullType()));
+ Q_ASSERT(!m_typeResolver->equals(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->valueType()->isReferenceType()
+ && !to->isListProperty()) {
+ return variable + u".toList<"_s + to->internalName() + u">()"_s;
+ }
+
+ bool isExtension = false;
+ if (m_typeResolver->canPopulate(to, from, &isExtension)) {
+ reject(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();
+ return (isExtension ? to->extensionType().scope->internalName() : to->internalName())
+ + u"("_s + convertStored(from, argumentTypes[0].type(), variable) + u")"_s;
+ }
+
+ if (m_typeResolver->equals(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(u"conversion from "_s + from->internalName() + u" to "_s + to->internalName());
+ return QString();
+}
+
+QString QQmlJSCodeGenerator::convertContained(const QQmlJSRegisterContent &from, const QQmlJSRegisterContent &to, const QString &variable)
+{
+ const QQmlJSScope::ConstPtr containedFrom = m_typeResolver->containedType(from);
+ const QQmlJSScope::ConstPtr containedTo = m_typeResolver->containedType(to);
+
+ // Those should be handled before, by convertStored().
+ Q_ASSERT(!to.storedType()->isReferenceType());
+ Q_ASSERT(!m_typeResolver->registerIsStoredIn(to, containedTo));
+ Q_ASSERT(!m_typeResolver->isIntegral(from.storedType()));
+ Q_ASSERT(!m_typeResolver->equals(containedFrom, containedTo));
+
+ if (!m_typeResolver->registerIsStoredIn(to, m_typeResolver->varType()) &&
+ !m_typeResolver->registerIsStoredIn(to, m_typeResolver->jsPrimitiveType())) {
+ reject(u"internal conversion into unsupported wrapper type."_s);
+ return QString();
+ }
+
+ bool isExtension = false;
+ if (m_typeResolver->canPopulate(containedTo, containedFrom, &isExtension)) {
+ reject(u"populating "_s + containedTo->internalName()
+ + u" from "_s + containedFrom->internalName());
+ return QString();
+ } else if (const auto ctor = m_typeResolver->selectConstructor(
+ containedTo, containedFrom, &isExtension); ctor.isValid()) {
+ const auto argumentTypes = ctor.parameters();
+ const QQmlJSScope::ConstPtr argumentType = argumentTypes[0].type();
+
+ // We need to store the converted argument in a temporary
+ // because it might not be an lvalue.
+
+ QString input;
+ QString argPointer;
+
+ if (m_typeResolver->equals(argumentType, containedFrom)) {
+ input = variable;
+ argPointer = contentPointer(from, u"arg"_s);
+ } else {
+ const QQmlJSRegisterContent argument
+ = m_typeResolver->globalType(argumentType)
+ .storedIn(m_typeResolver->genericType(argumentType));
+ input = conversion(from, argument, variable);
+ argPointer = contentPointer(argument, u"arg"_s);
+ }
+
+ return u"[&](){ auto arg = " + input
+ + u"; return aotContext->constructValueType("_s + metaType(containedTo)
+ + u", "_s + metaObject(
+ isExtension ? containedTo->extensionType().scope : containedTo)
+ + u", "_s + QString::number(int(ctor.constructorIndex()))
+ + u", "_s + argPointer + u"); }()"_s;
+ }
+
+ const auto originalFrom = m_typeResolver->original(from);
+ const auto containedOriginalFrom = m_typeResolver->containedType(originalFrom);
+ if (!m_typeResolver->equals(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(originalFrom.storedIn(from.storedType()), to, variable);
+ }
+
+ if (m_typeResolver->isPrimitive(containedFrom) && m_typeResolver->isPrimitive(containedTo)) {
+ const QQmlJSRegisterContent intermediate = from.storedIn(m_typeResolver->jsPrimitiveType());
+ return conversion(intermediate, to, conversion(from, intermediate, variable));
+ }
+
+ reject(u"internal conversion with incompatible or ambiguous types: %1 -> %2"_s
+ .arg(from.descriptiveName(), to.descriptiveName()));
+ return QString();
+}
+
+void QQmlJSCodeGenerator::reject(const QString &thing)
+{
+ setError(u"Cannot generate efficient code 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 QQmlJSScope::ConstPtr stored = accumulatorOut.storedType();
+ const QQmlJSScope::ConstPtr origStored = resolver->originalType(stored);
+
+ // If the stored type differs or if we store in QVariant and the contained type differs,
+ // then we have to use a temporary ...
+ if (!resolver->equals(origStored, stored)
+ || (!resolver->equals(origContained, resolver->containedType(accumulatorOut))
+ && resolver->equals(stored, resolver->varType()))) {
+
+ const bool storable = isTypeStorable(resolver, origStored);
+ generator->m_state.accumulatorVariableOut = storable ? u"retrieved"_s : QString();
+ generator->m_state.setRegister(Accumulator, resolver->original(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)
+ && resolver->registerIsStoredIn(
+ generator->m_state.accumulatorOut(), 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