aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler/qqmljstypepropagator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qmlcompiler/qqmljstypepropagator.cpp')
-rw-r--r--src/qmlcompiler/qqmljstypepropagator.cpp2863
1 files changed, 2863 insertions, 0 deletions
diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp
new file mode 100644
index 0000000000..d7a7d68d9f
--- /dev/null
+++ b/src/qmlcompiler/qqmljstypepropagator.cpp
@@ -0,0 +1,2863 @@
+// 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 "qqmljsscope_p.h"
+#include "qqmljstypepropagator_p.h"
+
+#include "qqmljsutils_p.h"
+#include "qqmlsa_p.h"
+
+#include <private/qv4compilerscanfunctions_p.h>
+
+#include <QtQmlCompiler/private/qqmlsasourcelocation_p.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+/*!
+ * \internal
+ * \class QQmlJSTypePropagator
+ *
+ * QQmlJSTypePropagator is the initial pass that performs the type inference and
+ * annotates every register in use at any instruction with the possible types it
+ * may hold. This includes information on how and in what scope the values are
+ * retrieved. These annotations may be used by further compile passes for
+ * refinement or code generation.
+ */
+
+QQmlJSTypePropagator::QQmlJSTypePropagator(const QV4::Compiler::JSUnitGenerator *unitGenerator,
+ const QQmlJSTypeResolver *typeResolver,
+ QQmlJSLogger *logger, BasicBlocks basicBlocks,
+ InstructionAnnotations annotations,
+ QQmlSA::PassManager *passManager)
+ : QQmlJSCompilePass(unitGenerator, typeResolver, logger, basicBlocks, annotations),
+ m_passManager(passManager)
+{
+}
+
+QQmlJSCompilePass::BlocksAndAnnotations QQmlJSTypePropagator::run(
+ const Function *function, QQmlJS::DiagnosticMessage *error)
+{
+ m_function = function;
+ m_error = error;
+ m_returnType = m_function->returnType;
+
+ do {
+ // Reset the error if we need to do another pass
+ if (m_state.needsMorePasses)
+ *m_error = QQmlJS::DiagnosticMessage();
+
+ m_prevStateAnnotations = m_state.annotations;
+ m_state = PassState();
+ m_state.annotations = m_annotations;
+ m_state.State::operator=(initialState(m_function));
+
+ reset();
+ decode(m_function->code.constData(), static_cast<uint>(m_function->code.size()));
+
+ // If we have found unresolved backwards jumps, we need to start over with a fresh state.
+ // Mind that m_jumpOriginRegisterStateByTargetInstructionOffset is retained in that case.
+ // This means that we won't start over for the same reason again.
+ } while (m_state.needsMorePasses);
+
+ return { std::move(m_basicBlocks), std::move(m_state.annotations) };
+}
+
+#define INSTR_PROLOGUE_NOT_IMPLEMENTED() \
+ setError(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__))); \
+ return;
+
+#define INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE() \
+ m_logger->log(u"Instruction \"%1\" not implemented"_s.arg(QString::fromUtf8(__func__)), \
+ qmlCompiler, QQmlJS::SourceLocation()); \
+ return;
+
+void QQmlJSTypePropagator::generate_ret_SAcheck()
+{
+ if (!m_function->isProperty)
+ return;
+ QQmlSA::PassManagerPrivate::get(m_passManager)
+ ->analyzeBinding(QQmlJSScope::createQQmlSAElement(m_function->qmlScope),
+ QQmlJSScope::createQQmlSAElement(
+ m_typeResolver->containedType(m_state.accumulatorIn())),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
+void QQmlJSTypePropagator::generate_Ret()
+{
+ if (m_passManager != nullptr)
+ generate_ret_SAcheck();
+
+ if (m_function->isSignalHandler) {
+ // Signal handlers cannot return anything.
+ } else if (m_typeResolver->registerContains(
+ m_state.accumulatorIn(), m_typeResolver->voidType())) {
+ // You can always return undefined.
+ } else if (!m_returnType.isValid() && m_state.accumulatorIn().isValid()) {
+ setError(u"function without return type annotation returns %1. This may prevent proper "_s
+ u"compilation to Cpp."_s.arg(m_state.accumulatorIn().descriptiveName()));
+
+ if (m_function->isFullyTyped) {
+ // Do not complain if the function didn't have a valid annotation in the first place.
+ m_logger->log(u"Function without return type annotation returns %1"_s.arg(
+ m_typeResolver->containedTypeName(m_state.accumulatorIn(), true)),
+ qmlIncompatibleType, getCurrentBindingSourceLocation());
+ }
+ return;
+ } else if (!canConvertFromTo(m_state.accumulatorIn(), m_returnType)) {
+ setError(u"cannot convert from %1 to %2"_s
+ .arg(m_state.accumulatorIn().descriptiveName(),
+ m_returnType.descriptiveName()));
+
+ m_logger->log(u"Cannot assign binding of type %1 to %2"_s.arg(
+ m_typeResolver->containedTypeName(m_state.accumulatorIn(), true),
+ m_typeResolver->containedTypeName(m_returnType, true)),
+ qmlIncompatibleType, getCurrentBindingSourceLocation());
+ return;
+ }
+
+ if (m_returnType.isValid()) {
+ // We need to preserve any possible undefined value as that resets the property.
+ if (m_typeResolver->canHoldUndefined(m_state.accumulatorIn()))
+ addReadAccumulator(m_state.accumulatorIn());
+ else
+ addReadAccumulator(m_returnType);
+ }
+
+ m_state.setHasSideEffects(true);
+ m_state.skipInstructionsUntilNextJumpTarget = true;
+}
+
+void QQmlJSTypePropagator::generate_Debug()
+{
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_LoadConst(int index)
+{
+ auto encodedConst = m_jsUnitGenerator->constant(index);
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->typeForConst(encodedConst)));
+}
+
+void QQmlJSTypePropagator::generate_LoadZero()
+{
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->int32Type()));
+}
+
+void QQmlJSTypePropagator::generate_LoadTrue()
+{
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->boolType()));
+}
+
+void QQmlJSTypePropagator::generate_LoadFalse()
+{
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->boolType()));
+}
+
+void QQmlJSTypePropagator::generate_LoadNull()
+{
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->nullType()));
+}
+
+void QQmlJSTypePropagator::generate_LoadUndefined()
+{
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->voidType()));
+}
+
+void QQmlJSTypePropagator::generate_LoadInt(int)
+{
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->int32Type()));
+}
+
+void QQmlJSTypePropagator::generate_MoveConst(int constIndex, int destTemp)
+{
+ auto encodedConst = m_jsUnitGenerator->constant(constIndex);
+ setRegister(destTemp, m_typeResolver->globalType(m_typeResolver->typeForConst(encodedConst)));
+}
+
+void QQmlJSTypePropagator::generate_LoadReg(int reg)
+{
+ // Do not re-track the register. We're not manipulating it.
+ m_state.setIsRename(true);
+ const QQmlJSRegisterContent content = checkedInputRegister(reg);
+ m_state.addReadRegister(reg, content);
+ m_state.setRegister(Accumulator, content);
+}
+
+void QQmlJSTypePropagator::generate_StoreReg(int reg)
+{
+ // Do not re-track the register. We're not manipulating it.
+ m_state.setIsRename(true);
+ m_state.addReadAccumulator(m_state.accumulatorIn());
+ m_state.setRegister(reg, m_state.accumulatorIn());
+}
+
+void QQmlJSTypePropagator::generate_MoveReg(int srcReg, int destReg)
+{
+ Q_ASSERT(destReg != InvalidRegister);
+ // Do not re-track the register. We're not manipulating it.
+ m_state.setIsRename(true);
+ const QQmlJSRegisterContent content = checkedInputRegister(srcReg);
+ m_state.addReadRegister(srcReg, content);
+ m_state.setRegister(destReg, content);
+}
+
+void QQmlJSTypePropagator::generate_LoadImport(int index)
+{
+ Q_UNUSED(index)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_LoadLocal(int index)
+{
+ Q_UNUSED(index);
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType()));
+}
+
+void QQmlJSTypePropagator::generate_StoreLocal(int index)
+{
+ Q_UNUSED(index)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_LoadScopedLocal(int scope, int index)
+{
+ Q_UNUSED(scope)
+ Q_UNUSED(index)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_StoreScopedLocal(int scope, int index)
+{
+ Q_UNUSED(scope)
+ Q_UNUSED(index)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_LoadRuntimeString(int stringId)
+{
+ Q_UNUSED(stringId)
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->stringType()));
+ // m_state.accumulatorOut.m_state.value = m_jsUnitGenerator->stringForIndex(stringId);
+}
+
+void QQmlJSTypePropagator::generate_MoveRegExp(int regExpId, int destReg)
+{
+ Q_UNUSED(regExpId)
+ Q_UNUSED(destReg)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_LoadClosure(int value)
+{
+ Q_UNUSED(value)
+ // TODO: Check the function at index and see whether it's a generator to return another type
+ // instead.
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->functionType()));
+}
+
+void QQmlJSTypePropagator::generate_LoadName(int nameIndex)
+{
+ const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
+ setAccumulator(m_typeResolver->scopedType(m_function->qmlScope, name));
+ if (!m_state.accumulatorOut().isValid())
+ setError(u"Cannot find name "_s + name);
+}
+
+void QQmlJSTypePropagator::generate_LoadGlobalLookup(int index)
+{
+ generate_LoadName(m_jsUnitGenerator->lookupNameIndex(index));
+}
+
+QQmlJS::SourceLocation QQmlJSTypePropagator::getCurrentSourceLocation() const
+{
+ Q_ASSERT(m_function->sourceLocations);
+ const auto &entries = m_function->sourceLocations->entries;
+
+ auto item = std::lower_bound(entries.begin(), entries.end(), currentInstructionOffset(),
+ [](auto entry, uint offset) { return entry.offset < offset; });
+ Q_ASSERT(item != entries.end());
+ auto location = item->location;
+
+ return location;
+}
+
+QQmlJS::SourceLocation QQmlJSTypePropagator::getCurrentBindingSourceLocation() const
+{
+ Q_ASSERT(m_function->sourceLocations);
+ const auto &entries = m_function->sourceLocations->entries;
+
+ Q_ASSERT(!entries.isEmpty());
+ return combine(entries.constFirst().location, entries.constLast().location);
+}
+
+void QQmlJSTypePropagator::handleUnqualifiedAccess(const QString &name, bool isMethod) const
+{
+ auto location = getCurrentSourceLocation();
+
+ if (m_function->qmlScope->isInCustomParserParent()) {
+ // Only ignore custom parser based elements if it's not Connections.
+ if (m_function->qmlScope->baseType().isNull()
+ || m_function->qmlScope->baseType()->internalName() != u"QQmlConnections"_s)
+ return;
+ }
+
+ if (isMethod) {
+ if (isCallingProperty(m_function->qmlScope, name))
+ return;
+ } else if (propertyResolution(m_function->qmlScope, name) != PropertyMissing) {
+ return;
+ }
+
+ std::optional<QQmlJSFixSuggestion> suggestion;
+
+ auto childScopes = m_function->qmlScope->childScopes();
+ for (qsizetype i = 0; i < m_function->qmlScope->childScopes().size(); i++) {
+ auto &scope = childScopes[i];
+ if (location.offset > scope->sourceLocation().offset) {
+ if (i + 1 < childScopes.size()
+ && childScopes.at(i + 1)->sourceLocation().offset < location.offset)
+ continue;
+ if (scope->childScopes().size() == 0)
+ continue;
+
+ const auto jsId = scope->childScopes().first()->jsIdentifier(name);
+
+ if (jsId.has_value() && jsId->kind == QQmlJSScope::JavaScriptIdentifier::Injected) {
+ const QQmlJSScope::JavaScriptIdentifier id = jsId.value();
+
+ QQmlJS::SourceLocation fixLocation = id.location;
+ Q_UNUSED(fixLocation)
+ fixLocation.length = 0;
+
+ const auto handler = m_typeResolver->signalHandlers()[id.location];
+
+ QString fixString = handler.isMultiline ? u"function("_s : u"("_s;
+ const auto parameters = handler.signalParameters;
+ for (int numParams = parameters.size(); numParams > 0; --numParams) {
+ fixString += parameters.at(parameters.size() - numParams);
+ if (numParams > 1)
+ fixString += u", "_s;
+ }
+
+ fixString += handler.isMultiline ? u") "_s : u") => "_s;
+
+ suggestion = QQmlJSFixSuggestion {
+ name + u" is accessible in this scope because you are handling a signal"
+ " at %1:%2. Use a function instead.\n"_s
+ .arg(id.location.startLine)
+ .arg(id.location.startColumn),
+ fixLocation,
+ fixString
+ };
+ suggestion->setAutoApplicable();
+ }
+ break;
+ }
+ }
+
+ // Might be a delegate just missing a required property.
+ // This heuristic does not recognize all instances of this occurring but should be sufficient
+ // protection against wrongly suggesting to add an id to the view to access the model that way
+ // which is very misleading
+ if (name == u"model" || name == u"index") {
+ if (QQmlJSScope::ConstPtr parent = m_function->qmlScope->parentScope(); !parent.isNull()) {
+ const auto bindings = parent->ownPropertyBindings(u"delegate"_s);
+
+ for (auto it = bindings.first; it != bindings.second; it++) {
+ if (!it->hasObject())
+ continue;
+ if (it->objectType() == m_function->qmlScope) {
+ suggestion = QQmlJSFixSuggestion {
+ name + " is implicitly injected into this delegate."
+ " Add a required property instead."_L1,
+ m_function->qmlScope->sourceLocation()
+ };
+ };
+
+ break;
+ }
+ }
+ }
+
+ if (!suggestion.has_value()) {
+ for (QQmlJSScope::ConstPtr scope = m_function->qmlScope; !scope.isNull();
+ scope = scope->parentScope()) {
+ if (scope->hasProperty(name)) {
+ const QString id = m_function->addressableScopes.id(scope, m_function->qmlScope);
+
+ QQmlJS::SourceLocation fixLocation = location;
+ fixLocation.length = 0;
+ suggestion = QQmlJSFixSuggestion{
+ name
+ + " is a member of a parent element.\n You can qualify the access "
+ "with its id to avoid this warning.\n"_L1,
+ fixLocation, (id.isEmpty() ? u"<id>."_s : (id + u'.'))
+ };
+
+ if (id.isEmpty())
+ suggestion->setHint("You first have to give the element an id"_L1);
+ else
+ suggestion->setAutoApplicable();
+ }
+ }
+ }
+
+ if (!suggestion.has_value() && !m_function->addressableScopes.componentsAreBound()
+ && m_function->addressableScopes.existsAnywhereInDocument(name)) {
+ const QLatin1String replacement = "pragma ComponentBehavior: Bound"_L1;
+ QQmlJSFixSuggestion bindComponents {
+ "Set \"%1\" in order to use IDs from outer components in nested components."_L1
+ .arg(replacement),
+ QQmlJS::SourceLocation(0, 0, 1, 1),
+ replacement + '\n'_L1
+ };
+ bindComponents.setAutoApplicable();
+ suggestion = bindComponents;
+ }
+
+ if (!suggestion.has_value()) {
+ if (auto didYouMean =
+ QQmlJSUtils::didYouMean(name,
+ m_function->qmlScope->properties().keys()
+ + m_function->qmlScope->methods().keys(),
+ location);
+ didYouMean.has_value()) {
+ suggestion = didYouMean;
+ }
+ }
+
+ m_logger->log(QLatin1String("Unqualified access"), qmlUnqualified, location, true, true,
+ suggestion);
+}
+
+void QQmlJSTypePropagator::checkDeprecated(QQmlJSScope::ConstPtr scope, const QString &name,
+ bool isMethod) const
+{
+ Q_ASSERT(!scope.isNull());
+ auto qmlScope = QQmlJSScope::findCurrentQMLScope(scope);
+ if (qmlScope.isNull())
+ return;
+
+ QList<QQmlJSAnnotation> annotations;
+
+ QQmlJSMetaMethod method;
+
+ if (isMethod) {
+ const QVector<QQmlJSMetaMethod> methods = qmlScope->methods(name);
+ if (methods.isEmpty())
+ return;
+ method = methods.constFirst();
+ annotations = method.annotations();
+ } else {
+ QQmlJSMetaProperty property = qmlScope->property(name);
+ if (!property.isValid())
+ return;
+ annotations = property.annotations();
+ }
+
+ auto deprecationAnn = std::find_if(
+ annotations.constBegin(), annotations.constEnd(),
+ [](const QQmlJSAnnotation &annotation) { return annotation.isDeprecation(); });
+
+ if (deprecationAnn == annotations.constEnd())
+ return;
+
+ QQQmlJSDeprecation deprecation = deprecationAnn->deprecation();
+
+ QString descriptor = name;
+ if (isMethod)
+ descriptor += u'(' + method.parameterNames().join(u", "_s) + u')';
+
+ QString message = QStringLiteral("%1 \"%2\" is deprecated")
+ .arg(isMethod ? u"Method"_s : u"Property"_s)
+ .arg(descriptor);
+
+ if (!deprecation.reason.isEmpty())
+ message.append(QStringLiteral(" (Reason: %1)").arg(deprecation.reason));
+
+ m_logger->log(message, qmlDeprecated, getCurrentSourceLocation());
+}
+
+// Only to be called once a lookup has already failed
+QQmlJSTypePropagator::PropertyResolution QQmlJSTypePropagator::propertyResolution(
+ QQmlJSScope::ConstPtr scope, const QString &propertyName) const
+{
+ auto property = scope->property(propertyName);
+ if (!property.isValid())
+ return PropertyMissing;
+
+ QString errorType;
+ if (property.type().isNull())
+ errorType = u"found"_s;
+ else if (!property.type()->isFullyResolved())
+ errorType = u"fully resolved"_s;
+ else
+ return PropertyFullyResolved;
+
+ Q_ASSERT(!errorType.isEmpty());
+
+ m_logger->log(
+ u"Type \"%1\" of property \"%2\" not %3. This is likely due to a missing dependency entry or a type not being exposed declaratively."_s
+ .arg(property.typeName(), propertyName, errorType),
+ qmlUnresolvedType, getCurrentSourceLocation());
+
+ return PropertyTypeUnresolved;
+}
+
+bool QQmlJSTypePropagator::isCallingProperty(QQmlJSScope::ConstPtr scope, const QString &name) const
+{
+ auto property = scope->property(name);
+ if (!property.isValid())
+ return false;
+
+ QString propertyType = u"Property"_s;
+
+ auto methods = scope->methods(name);
+
+ QString errorType;
+ if (!methods.isEmpty()) {
+ errorType = u"shadowed by a property."_s;
+ switch (methods.first().methodType()) {
+ case QQmlJSMetaMethodType::Signal:
+ propertyType = u"Signal"_s;
+ break;
+ case QQmlJSMetaMethodType::Slot:
+ propertyType = u"Slot"_s;
+ break;
+ case QQmlJSMetaMethodType::Method:
+ propertyType = u"Method"_s;
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+ } else if (m_typeResolver->equals(property.type(), m_typeResolver->varType())) {
+ errorType =
+ u"a variant property. It may or may not be a method. Use a regular function instead."_s;
+ } else if (m_typeResolver->equals(property.type(), m_typeResolver->jsValueType())) {
+ errorType =
+ u"a QJSValue property. It may or may not be a method. Use a regular Q_INVOKABLE instead."_s;
+ } else {
+ errorType = u"not a method"_s;
+ }
+
+ m_logger->log(u"%1 \"%2\" is %3"_s.arg(propertyType, name, errorType), qmlUseProperFunction,
+ getCurrentSourceLocation(), true, true, {});
+
+ return true;
+}
+
+
+void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup_SAcheck(const QString &name)
+{
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
+ QQmlJSScope::createQQmlSAElement(m_function->qmlScope), name,
+ QQmlJSScope::createQQmlSAElement(m_function->qmlScope),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
+
+
+void QQmlJSTypePropagator::generate_LoadQmlContextPropertyLookup(int index)
+{
+ // LoadQmlContextPropertyLookup does not use accumulatorIn. It always refers to the scope.
+ // Any import namespaces etc. are handled via LoadProperty or GetLookup.
+
+ const int nameIndex = m_jsUnitGenerator->lookupNameIndex(index);
+ const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
+
+ setAccumulator(m_typeResolver->scopedType(m_function->qmlScope, name, index));
+
+ if (!m_state.accumulatorOut().isValid() && m_typeResolver->isPrefix(name)) {
+ const QQmlJSRegisterContent inType = m_typeResolver->globalType(m_function->qmlScope);
+ setAccumulator(QQmlJSRegisterContent::create(
+ m_typeResolver->voidType(), nameIndex, QQmlJSRegisterContent::ScopeModulePrefix,
+ m_typeResolver->containedType(inType)));
+ return;
+ }
+
+ checkDeprecated(m_function->qmlScope, name, false);
+
+ if (!m_state.accumulatorOut().isValid()) {
+ setError(u"Cannot access value for name "_s + name);
+ handleUnqualifiedAccess(name, false);
+ return;
+ }
+
+ const QQmlJSScope::ConstPtr outStored
+ = m_typeResolver->genericType(m_state.accumulatorOut().storedType());
+
+ if (outStored.isNull()) {
+ // It should really be valid.
+ // We get the generic type from aotContext->loadQmlContextPropertyIdLookup().
+ setError(u"Cannot determine generic type for "_s + name);
+ return;
+ }
+
+ if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectById
+ && !outStored->isReferenceType()) {
+ setError(u"Cannot retrieve a non-object type by ID: "_s + name);
+ return;
+ }
+
+ if (m_passManager != nullptr)
+ generate_LoadQmlContextPropertyLookup_SAcheck(name);
+
+ if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ScopeAttached)
+ m_attachedContext = QQmlJSScope::ConstPtr();
+}
+
+void QQmlJSTypePropagator::generate_StoreNameCommon_SAcheck(const QQmlJSRegisterContent &in, const QString &name)
+{
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
+ QQmlJSScope::createQQmlSAElement(m_function->qmlScope), name,
+ QQmlJSScope::createQQmlSAElement(m_typeResolver->containedType(in)),
+ QQmlJSScope::createQQmlSAElement(m_function->qmlScope),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
+
+/*!
+ \internal
+ As far as type propagation is involved, StoreNameSloppy and
+ StoreNameStrict are completely the same
+ StoreNameStrict is rejecting a few writes (where the variable was not
+ defined before) that would work in a sloppy context in JS, but the
+ compiler would always reject this. And for type propagation, this does
+ not matter at all.
+ \a nameIndex is the index in the string table corresponding to
+ the name which we are storing
+ */
+void QQmlJSTypePropagator::generate_StoreNameCommon(int nameIndex)
+{
+ const QString name = m_jsUnitGenerator->stringForIndex(nameIndex);
+ const QQmlJSRegisterContent type = m_typeResolver->scopedType(m_function->qmlScope, name);
+ const QQmlJSRegisterContent in = m_state.accumulatorIn();
+
+ if (!type.isValid()) {
+ handleUnqualifiedAccess(name, false);
+ setError(u"Cannot find name "_s + name);
+ return;
+ }
+
+ if (!type.isProperty()) {
+ QString message = type.isMethod() ? u"Cannot assign to method %1"_s
+ : u"Cannot assign to non-property %1"_s;
+ // The interpreter treats methods as read-only properties in its error messages
+ // and we lack a better fitting category. We might want to revisit this later.
+ m_logger->log(message.arg(name), qmlReadOnlyProperty,
+ getCurrentSourceLocation());
+ setError(u"Cannot assign to non-property "_s + name);
+ return;
+ }
+
+ if (!type.isWritable()) {
+ setError(u"Can't assign to read-only property %1"_s.arg(name));
+
+ m_logger->log(u"Cannot assign to read-only property %1"_s.arg(name), qmlReadOnlyProperty,
+ getCurrentSourceLocation());
+
+ return;
+ }
+
+ if (!canConvertFromTo(in, type)) {
+ setError(u"cannot convert from %1 to %2"_s
+ .arg(in.descriptiveName(), type.descriptiveName()));
+ }
+
+ if (m_passManager != nullptr)
+ generate_StoreNameCommon_SAcheck(in, name);
+
+
+ if (m_typeResolver->canHoldUndefined(in) && !m_typeResolver->canHoldUndefined(type)) {
+ if (m_typeResolver->registerIsStoredIn(in, m_typeResolver->voidType()))
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->varType()));
+ else
+ addReadAccumulator(in);
+ } else {
+ addReadAccumulator(type);
+ }
+
+ m_state.setHasSideEffects(true);
+}
+
+void QQmlJSTypePropagator::generate_StoreNameSloppy(int nameIndex)
+{
+ return generate_StoreNameCommon(nameIndex);
+}
+
+void QQmlJSTypePropagator::generate_StoreNameStrict(int name)
+{
+ return generate_StoreNameCommon(name);
+}
+
+bool QQmlJSTypePropagator::checkForEnumProblems(
+ const QQmlJSRegisterContent &base, const QString &propertyName)
+{
+ if (base.isEnumeration()) {
+ const auto metaEn = base.enumeration();
+ if (!metaEn.hasKey(propertyName)) {
+ auto fixSuggestion = QQmlJSUtils::didYouMean(propertyName, metaEn.keys(),
+ getCurrentSourceLocation());
+ const QString error = u"\"%1\" is not an entry of enum \"%2\"."_s
+ .arg(propertyName, metaEn.name());
+ setError(error);
+ m_logger->log(
+ error, qmlMissingEnumEntry, getCurrentSourceLocation(), true, true,
+ fixSuggestion);
+ return true;
+ }
+ } else if (base.variant() == QQmlJSRegisterContent::MetaType) {
+ const QQmlJSMetaEnum metaEn = base.scopeType()->enumeration(propertyName);
+ if (metaEn.isValid() && !metaEn.isScoped() && !metaEn.isQml()) {
+ const QString error
+ = u"You cannot access unscoped enum \"%1\" from here."_s.arg(propertyName);
+ setError(error);
+ m_logger->log(error, qmlRestrictedType, getCurrentSourceLocation());
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void QQmlJSTypePropagator::generate_LoadElement(int base)
+{
+ const auto fallback = [&]() {
+ const auto jsValue = m_typeResolver->globalType(m_typeResolver->jsValueType());
+ addReadAccumulator(jsValue);
+ addReadRegister(base, jsValue);
+ setAccumulator(jsValue);
+ };
+
+ const QQmlJSRegisterContent baseRegister = m_state.registers[base].content;
+ if (!baseRegister.isList()
+ && !m_typeResolver->registerContains(baseRegister, m_typeResolver->stringType())) {
+ fallback();
+ return;
+ }
+ addReadRegister(base, baseRegister);
+
+ if (m_typeResolver->isNumeric(m_state.accumulatorIn())) {
+ const auto contained = m_typeResolver->containedType(m_state.accumulatorIn());
+ if (m_typeResolver->isSignedInteger(contained))
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->sizeType()));
+ else if (m_typeResolver->isUnsignedInteger(contained))
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->uint32Type()));
+ else
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->realType()));
+ } else if (m_typeResolver->isNumeric(m_typeResolver->extractNonVoidFromOptionalType(
+ m_state.accumulatorIn()))) {
+ addReadAccumulator(m_state.accumulatorIn());
+ } else {
+ fallback();
+ return;
+ }
+
+ // We can end up with undefined.
+ setAccumulator(m_typeResolver->merge(
+ m_typeResolver->valueType(baseRegister),
+ m_typeResolver->globalType(m_typeResolver->voidType())));
+}
+
+void QQmlJSTypePropagator::generate_StoreElement(int base, int index)
+{
+ const QQmlJSRegisterContent baseRegister = m_state.registers[base].content;
+ const QQmlJSRegisterContent indexRegister = checkedInputRegister(index);
+
+ if (!baseRegister.isList()
+ || !m_typeResolver->isNumeric(indexRegister)) {
+ const auto jsValue = m_typeResolver->globalType(m_typeResolver->jsValueType());
+ addReadAccumulator(jsValue);
+ addReadRegister(base, jsValue);
+ addReadRegister(index, jsValue);
+
+ // Writing to a JS array can have side effects all over the place since it's
+ // passed by reference.
+ m_state.setHasSideEffects(true);
+ return;
+ }
+
+ const auto contained = m_typeResolver->containedType(indexRegister);
+ if (m_typeResolver->isSignedInteger(contained))
+ addReadRegister(index, m_typeResolver->globalType(m_typeResolver->int32Type()));
+ else if (m_typeResolver->isUnsignedInteger(contained))
+ addReadRegister(index, m_typeResolver->globalType(m_typeResolver->uint32Type()));
+ else
+ addReadRegister(index, m_typeResolver->globalType(m_typeResolver->realType()));
+
+ addReadRegister(base, baseRegister);
+ addReadAccumulator(m_typeResolver->valueType(baseRegister));
+
+ // If we're writing a QQmlListProperty backed by a container somewhere else,
+ // that has side effects.
+ // If we're writing to a list retrieved from a property, that _should_ have side effects,
+ // but currently the QML engine doesn't implement them.
+ // TODO: Figure out the above and accurately set the flag.
+ m_state.setHasSideEffects(true);
+}
+
+void QQmlJSTypePropagator::propagatePropertyLookup_SAcheck(const QString &propertyName)
+{
+ const bool isAttached =
+ m_state.accumulatorIn().variant() == QQmlJSRegisterContent::ObjectAttached;
+
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
+ QQmlJSScope::createQQmlSAElement(
+ m_typeResolver->containedType(m_state.accumulatorIn())),
+ propertyName,
+ QQmlJSScope::createQQmlSAElement(isAttached ? m_attachedContext
+ : m_function->qmlScope),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
+
+void QQmlJSTypePropagator::propagatePropertyLookup(const QString &propertyName, int lookupIndex)
+{
+ setAccumulator(
+ m_typeResolver->memberType(
+ m_state.accumulatorIn(),
+ m_state.accumulatorIn().isImportNamespace()
+ ? m_jsUnitGenerator->stringForIndex(m_state.accumulatorIn().importNamespace())
+ + u'.' + propertyName
+ : propertyName, lookupIndex));
+
+ if (!m_state.accumulatorOut().isValid()) {
+ if (m_typeResolver->isPrefix(propertyName)) {
+ Q_ASSERT(m_state.accumulatorIn().isValid());
+ addReadAccumulator(m_state.accumulatorIn());
+ setAccumulator(QQmlJSRegisterContent::create(
+ m_state.accumulatorIn().storedType(),
+ m_jsUnitGenerator->getStringId(propertyName),
+ QQmlJSRegisterContent::ObjectModulePrefix,
+ m_typeResolver->containedType(m_state.accumulatorIn())));
+ return;
+ }
+ if (m_state.accumulatorIn().isImportNamespace())
+ m_logger->log(u"Type not found in namespace"_s, qmlUnresolvedType,
+ getCurrentSourceLocation());
+ } else if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::Singleton
+ && m_state.accumulatorIn().variant() == QQmlJSRegisterContent::ObjectModulePrefix) {
+ m_logger->log(
+ u"Cannot access singleton as a property of an object. Did you want to access an attached object?"_s,
+ qmlAccessSingleton, getCurrentSourceLocation());
+ setAccumulator(QQmlJSRegisterContent());
+ } else if (m_state.accumulatorOut().isEnumeration()) {
+ switch (m_state.accumulatorIn().variant()) {
+ case QQmlJSRegisterContent::ExtensionObjectEnum:
+ case QQmlJSRegisterContent::MetaType:
+ case QQmlJSRegisterContent::ObjectAttached:
+ case QQmlJSRegisterContent::ObjectEnum:
+ case QQmlJSRegisterContent::ObjectModulePrefix:
+ case QQmlJSRegisterContent::ScopeAttached:
+ case QQmlJSRegisterContent::ScopeModulePrefix:
+ case QQmlJSRegisterContent::Singleton:
+ break; // OK, can look up enums on that thing
+ default:
+ setAccumulator(QQmlJSRegisterContent());
+ }
+ }
+
+ if (!m_state.accumulatorOut().isValid()) {
+ if (checkForEnumProblems(m_state.accumulatorIn(), propertyName))
+ return;
+
+ setError(u"Cannot load property %1 from %2."_s
+ .arg(propertyName, m_state.accumulatorIn().descriptiveName()));
+
+ const QString typeName = m_typeResolver->containedTypeName(m_state.accumulatorIn(), true);
+
+ if (typeName == u"QVariant")
+ return;
+ if (m_state.accumulatorIn().isList() && propertyName == u"length")
+ return;
+
+ auto baseType = m_typeResolver->containedType(m_state.accumulatorIn());
+ // Warn separately when a property is only not found because of a missing type
+
+ if (propertyResolution(baseType, propertyName) != PropertyMissing)
+ return;
+
+ if (baseType->isScript())
+ return;
+
+ std::optional<QQmlJSFixSuggestion> fixSuggestion;
+
+ if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->properties().keys(),
+ getCurrentSourceLocation());
+ suggestion.has_value()) {
+ fixSuggestion = suggestion;
+ }
+
+ if (!fixSuggestion.has_value()
+ && m_state.accumulatorIn().variant() == QQmlJSRegisterContent::MetaType) {
+ QStringList enumKeys;
+ for (const QQmlJSMetaEnum &metaEnum :
+ m_state.accumulatorIn().scopeType()->enumerations())
+ enumKeys << metaEnum.keys();
+
+ if (auto suggestion =
+ QQmlJSUtils::didYouMean(propertyName, enumKeys, getCurrentSourceLocation());
+ suggestion.has_value()) {
+ fixSuggestion = suggestion;
+ }
+ }
+
+ m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(propertyName).arg(typeName),
+ qmlMissingProperty, getCurrentSourceLocation(), true, true, fixSuggestion);
+ return;
+ }
+
+ if (m_state.accumulatorOut().isMethod() && m_state.accumulatorOut().method().size() != 1) {
+ setError(u"Cannot determine overloaded method on loadProperty"_s);
+ return;
+ }
+
+ if (m_state.accumulatorOut().isProperty()) {
+ const QQmlJSScope::ConstPtr mathObject
+ = m_typeResolver->jsGlobalObject()->property(u"Math"_s).type();
+ if (m_typeResolver->registerContains(m_state.accumulatorIn(), mathObject)) {
+ QQmlJSMetaProperty prop;
+ prop.setPropertyName(propertyName);
+ prop.setTypeName(u"double"_s);
+ prop.setType(m_typeResolver->realType());
+ setAccumulator(
+ QQmlJSRegisterContent::create(
+ m_typeResolver->realType(), prop, m_state.accumulatorIn().resultLookupIndex(), lookupIndex,
+ QQmlJSRegisterContent::GenericObjectProperty, mathObject)
+ );
+
+ return;
+ }
+
+ if (m_typeResolver->registerContains(
+ m_state.accumulatorOut(), m_typeResolver->voidType())) {
+ setError(u"Type %1 does not have a property %2 for reading"_s
+ .arg(m_state.accumulatorIn().descriptiveName(), propertyName));
+ return;
+ }
+
+ if (!m_state.accumulatorOut().property().type()) {
+ m_logger->log(
+ QString::fromLatin1("Type of property \"%2\" not found").arg(propertyName),
+ qmlMissingType, getCurrentSourceLocation());
+ }
+ }
+
+ if (m_passManager != nullptr)
+ propagatePropertyLookup_SAcheck(propertyName);
+
+ if (m_state.accumulatorOut().variant() == QQmlJSRegisterContent::ObjectAttached)
+ m_attachedContext = m_typeResolver->containedType(m_state.accumulatorIn());
+
+ switch (m_state.accumulatorOut().variant()) {
+ case QQmlJSRegisterContent::ObjectEnum:
+ case QQmlJSRegisterContent::ExtensionObjectEnum:
+ case QQmlJSRegisterContent::Singleton:
+ // For reading enums or singletons, we don't need to access anything, unless it's an
+ // import namespace. Then we need the name.
+ if (m_state.accumulatorIn().isImportNamespace())
+ addReadAccumulator(m_state.accumulatorIn());
+ break;
+ default:
+ addReadAccumulator(m_state.accumulatorIn());
+ break;
+ }
+}
+
+void QQmlJSTypePropagator::generate_LoadProperty(int nameIndex)
+{
+ propagatePropertyLookup(m_jsUnitGenerator->stringForIndex(nameIndex));
+}
+
+void QQmlJSTypePropagator::generate_LoadOptionalProperty(int name, int offset)
+{
+ Q_UNUSED(name);
+ Q_UNUSED(offset);
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_GetLookup(int index)
+{
+ propagatePropertyLookup(m_jsUnitGenerator->lookupName(index), index);
+}
+
+void QQmlJSTypePropagator::generate_GetOptionalLookup(int index, int offset)
+{
+ Q_UNUSED(offset);
+ saveRegisterStateForJump(offset);
+ propagatePropertyLookup(m_jsUnitGenerator->lookupName(index), index);
+}
+
+void QQmlJSTypePropagator::generate_StoreProperty_SAcheck(const QString propertyName, const QQmlJSRegisterContent &callBase)
+{
+ const bool isAttached = callBase.variant() == QQmlJSRegisterContent::ObjectAttached;
+
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeWrite(
+ QQmlJSScope::createQQmlSAElement(m_typeResolver->containedType(callBase)),
+ propertyName,
+ QQmlJSScope::createQQmlSAElement(
+ m_typeResolver->containedType(m_state.accumulatorIn())),
+ QQmlJSScope::createQQmlSAElement(isAttached ? m_attachedContext
+ : m_function->qmlScope),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
+
+void QQmlJSTypePropagator::generate_StoreProperty(int nameIndex, int base)
+{
+ auto callBase = m_state.registers[base].content;
+ const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
+
+ QQmlJSRegisterContent property = m_typeResolver->memberType(callBase, propertyName);
+ if (!property.isProperty()) {
+ setError(u"Type %1 does not have a property %2 for writing"_s
+ .arg(callBase.descriptiveName(), propertyName));
+ return;
+ }
+
+ if (property.storedType().isNull()) {
+ setError(u"Cannot determine type for property %1 of type %2"_s.arg(
+ propertyName, callBase.descriptiveName()));
+ return;
+ }
+
+ if (!property.isWritable() && !property.storedType()->isListProperty()) {
+ setError(u"Can't assign to read-only property %1"_s.arg(propertyName));
+
+ m_logger->log(u"Cannot assign to read-only property %1"_s.arg(propertyName),
+ qmlReadOnlyProperty, getCurrentSourceLocation());
+
+ return;
+ }
+
+ if (!canConvertFromTo(m_state.accumulatorIn(), property)) {
+ setError(u"cannot convert from %1 to %2"_s
+ .arg(m_state.accumulatorIn().descriptiveName(), property.descriptiveName()));
+ return;
+ }
+
+ if (m_passManager != nullptr)
+ generate_StoreProperty_SAcheck(propertyName, callBase);
+
+ // If the input can hold undefined we must not coerce it to the property type
+ // as that might eliminate an undefined value. For example, undefined -> string
+ // becomes "undefined".
+ // We need the undefined value for either resetting the property if that is supported
+ // or generating an exception otherwise. Therefore we explicitly require the value to
+ // be given as QVariant. This triggers the QVariant fallback path that's also used for
+ // shadowable properties. QVariant can hold undefined and the lookup functions will
+ // handle that appropriately.
+
+ const QQmlJSScope::ConstPtr varType = m_typeResolver->varType();
+ const QQmlJSRegisterContent readType = m_typeResolver->canHoldUndefined(m_state.accumulatorIn())
+ ? property.storedIn(varType).castTo(varType)
+ : std::move(property);
+ addReadAccumulator(readType);
+ addReadRegister(base, callBase);
+ m_state.setHasSideEffects(true);
+}
+
+void QQmlJSTypePropagator::generate_SetLookup(int index, int base)
+{
+ generate_StoreProperty(m_jsUnitGenerator->lookupNameIndex(index), base);
+}
+
+void QQmlJSTypePropagator::generate_LoadSuperProperty(int property)
+{
+ Q_UNUSED(property)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_StoreSuperProperty(int property)
+{
+ Q_UNUSED(property)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_Yield()
+{
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_YieldStar()
+{
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_Resume(int)
+{
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_CallValue(int name, int argc, int argv)
+{
+ m_state.setHasSideEffects(true);
+ Q_UNUSED(name)
+ Q_UNUSED(argc)
+ Q_UNUSED(argv)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_CallWithReceiver(int name, int thisObject, int argc, int argv)
+{
+ m_state.setHasSideEffects(true);
+ Q_UNUSED(name)
+ Q_UNUSED(thisObject)
+ Q_UNUSED(argc)
+ Q_UNUSED(argv)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+static bool isLoggingMethod(const QString &consoleMethod)
+{
+ return consoleMethod == u"log" || consoleMethod == u"debug" || consoleMethod == u"info"
+ || consoleMethod == u"warn" || consoleMethod == u"error";
+}
+
+void QQmlJSTypePropagator::generate_CallProperty_SCMath(int base, int argc, int argv)
+{
+ // If we call a method on the Math object we don't need the actual Math object. We do need
+ // to transfer the type information to the code generator so that it knows that this is the
+ // Math object. Read the base register as void. void isn't stored, and the place where it's
+ // created will be optimized out if there are no other readers. The code generator can
+ // retrieve the original type and determine that it was the Math object.
+
+ addReadRegister(base, m_typeResolver->globalType(m_typeResolver->voidType()));
+
+ QQmlJSRegisterContent realType = m_typeResolver->returnType(
+ m_typeResolver->realType(), QQmlJSRegisterContent::MethodReturnValue,
+ m_typeResolver->mathObject());
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, realType);
+ setAccumulator(realType);
+}
+
+void QQmlJSTypePropagator::generate_CallProperty_SCconsole(int base, int argc, int argv)
+{
+ const QQmlJSRegisterContent voidType
+ = m_typeResolver->globalType(m_typeResolver->voidType());
+
+ // If we call a method on the console object we don't need the console object.
+ addReadRegister(base, voidType);
+
+ const QQmlJSRegisterContent stringType
+ = m_typeResolver->globalType(m_typeResolver->stringType());
+
+ if (argc > 0) {
+ const QQmlJSRegisterContent firstContent = m_state.registers[argv].content;
+ const QQmlJSScope::ConstPtr firstArg = m_typeResolver->containedType(firstContent);
+ switch (firstArg->accessSemantics()) {
+ case QQmlJSScope::AccessSemantics::Reference:
+ // We cannot know whether this will be a logging category at run time.
+ // Therefore we always pass any object types as special last argument.
+ addReadRegister(argv, m_typeResolver->globalType(
+ m_typeResolver->genericType(firstArg)));
+ break;
+ case QQmlJSScope::AccessSemantics::Sequence:
+ addReadRegister(argv, firstContent);
+ break;
+ default:
+ addReadRegister(argv, stringType);
+ break;
+ }
+ }
+
+ for (int i = 1; i < argc; ++i) {
+ const QQmlJSRegisterContent argContent = m_state.registers[argv + i].content;
+ const QQmlJSScope::ConstPtr arg = m_typeResolver->containedType(argContent);
+ addReadRegister(
+ argv + i,
+ arg->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
+ ? argContent
+ : stringType);
+ }
+
+ m_state.setHasSideEffects(true);
+ setAccumulator(m_typeResolver->returnType(
+ m_typeResolver->voidType(), QQmlJSRegisterContent::MethodReturnValue,
+ m_typeResolver->consoleObject()));
+}
+
+void QQmlJSTypePropagator::generate_callProperty_SAcheck(const QString propertyName, const QQmlJSScope::ConstPtr &baseType)
+{
+ // TODO: Should there be an analyzeCall() in the future? (w. corresponding onCall in Pass)
+ QQmlSA::PassManagerPrivate::get(m_passManager)->analyzeRead(
+ QQmlJSScope::createQQmlSAElement(baseType), propertyName,
+ QQmlJSScope::createQQmlSAElement(m_function->qmlScope),
+ QQmlSA::SourceLocationPrivate::createQQmlSASourceLocation(
+ getCurrentBindingSourceLocation()));
+}
+
+void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int argc, int argv)
+{
+ Q_ASSERT(m_state.registers.contains(base));
+ const auto callBase = m_state.registers[base].content;
+ const QString propertyName = m_jsUnitGenerator->stringForIndex(nameIndex);
+
+ if (m_typeResolver->registerContains(callBase, m_typeResolver->mathObject())) {
+ generate_CallProperty_SCMath(base, argc, argv);
+ return;
+ }
+
+ if (m_typeResolver->registerContains(callBase, m_typeResolver->consoleObject()) && isLoggingMethod(propertyName)) {
+ generate_CallProperty_SCconsole(base, argc, argv);
+ return;
+ }
+
+ const auto baseType = m_typeResolver->containedType(callBase);
+ const auto member = m_typeResolver->memberType(callBase, propertyName);
+
+ if (!member.isMethod()) {
+ if (m_typeResolver->registerContains(callBase, m_typeResolver->jsValueType())
+ || m_typeResolver->registerContains(callBase, m_typeResolver->varType())) {
+ const auto jsValueType = m_typeResolver->globalType(m_typeResolver->jsValueType());
+ addReadRegister(base, jsValueType);
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, jsValueType);
+ m_state.setHasSideEffects(true);
+ setAccumulator(m_typeResolver->returnType(
+ m_typeResolver->jsValueType(), QQmlJSRegisterContent::JavaScriptReturnValue,
+ m_typeResolver->jsValueType()));
+ return;
+ }
+
+ setError(u"Type %1 does not have a property %2 for calling"_s
+ .arg(callBase.descriptiveName(), propertyName));
+
+ if (callBase.isType() && isCallingProperty(callBase.type(), propertyName))
+ return;
+
+ if (checkForEnumProblems(callBase, propertyName))
+ return;
+
+ std::optional<QQmlJSFixSuggestion> fixSuggestion;
+
+ if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->methods().keys(),
+ getCurrentSourceLocation());
+ suggestion.has_value()) {
+ fixSuggestion = suggestion;
+ }
+
+ m_logger->log(u"Member \"%1\" not found on type \"%2\""_s.arg(
+ propertyName, m_typeResolver->containedTypeName(callBase, true)),
+ qmlMissingProperty, getCurrentSourceLocation(), true, true, fixSuggestion);
+ return;
+ }
+
+ checkDeprecated(baseType, propertyName, true);
+
+ if (m_passManager != nullptr)
+ generate_callProperty_SAcheck(propertyName, baseType);
+
+ addReadRegister(base, callBase);
+
+ if (m_typeResolver->registerContains(callBase, m_typeResolver->stringType())) {
+ if (propertyName == u"arg"_s && argc == 1) {
+ propagateStringArgCall(argv);
+ return;
+ }
+ }
+
+ if (baseType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
+ && m_typeResolver->equals(member.scopeType(), m_typeResolver->arrayPrototype())
+ && propagateArrayMethod(propertyName, argc, argv, callBase)) {
+ return;
+ }
+
+ propagateCall(member.method(), argc, argv, member.scopeType());
+}
+
+QQmlJSMetaMethod QQmlJSTypePropagator::bestMatchForCall(const QList<QQmlJSMetaMethod> &methods,
+ int argc, int argv, QStringList *errors)
+{
+ QQmlJSMetaMethod javascriptFunction;
+ QQmlJSMetaMethod candidate;
+ bool hasMultipleCandidates = false;
+
+ for (const auto &method : methods) {
+
+ // If we encounter a JavaScript function, use this as a fallback if no other method matches
+ if (method.isJavaScriptFunction() && !javascriptFunction.isValid())
+ javascriptFunction = method;
+
+ if (method.returnType().isNull() && !method.returnTypeName().isEmpty()) {
+ errors->append(u"return type %1 cannot be resolved"_s
+ .arg(method.returnTypeName()));
+ continue;
+ }
+
+ const auto arguments = method.parameters();
+ if (argc != arguments.size()) {
+ errors->append(
+ u"Function expects %1 arguments, but %2 were provided"_s.arg(arguments.size())
+ .arg(argc));
+ continue;
+ }
+
+ bool fuzzyMatch = true;
+ bool exactMatch = true;
+ for (int i = 0; i < argc; ++i) {
+ const auto argumentType = arguments[i].type();
+ if (argumentType.isNull()) {
+ errors->append(
+ u"type %1 for argument %2 cannot be resolved"_s.arg(arguments[i].typeName())
+ .arg(i));
+ exactMatch = false;
+ fuzzyMatch = false;
+ break;
+ }
+
+ const auto content = m_state.registers[argv + i].content;
+ if (m_typeResolver->registerContains(content, argumentType))
+ continue;
+
+ exactMatch = false;
+ if (canConvertFromTo(content, m_typeResolver->globalType(argumentType)))
+ continue;
+
+ // We can try to call a method that expects a derived type.
+ if (argumentType->isReferenceType()
+ && m_typeResolver->inherits(
+ argumentType->baseType(), m_typeResolver->containedType(content))) {
+ continue;
+ }
+
+ errors->append(
+ u"argument %1 contains %2 but is expected to contain the type %3"_s.arg(i).arg(
+ content.descriptiveName(), arguments[i].typeName()));
+ fuzzyMatch = false;
+ break;
+ }
+
+ if (exactMatch) {
+ return method;
+ } else if (fuzzyMatch) {
+ if (!candidate.isValid())
+ candidate = method;
+ else
+ hasMultipleCandidates = true;
+ }
+ }
+
+ if (hasMultipleCandidates)
+ return QQmlJSMetaMethod();
+
+ return candidate.isValid() ? candidate : javascriptFunction;
+}
+
+void QQmlJSTypePropagator::setAccumulator(const QQmlJSRegisterContent &content)
+{
+ setRegister(Accumulator, content);
+}
+
+void QQmlJSTypePropagator::setRegister(int index, const QQmlJSRegisterContent &content)
+{
+ // If we've come to the same conclusion before, let's not track the type again.
+ auto it = m_prevStateAnnotations.find(currentInstructionOffset());
+ if (it != m_prevStateAnnotations.end()) {
+ const QQmlJSRegisterContent &lastTry = it->second.changedRegister;
+ if (m_typeResolver->registerContains(lastTry, m_typeResolver->containedType(content))) {
+ m_state.setRegister(index, lastTry);
+ return;
+ }
+ }
+
+ m_state.setRegister(index, m_typeResolver->tracked(content));
+}
+
+void QQmlJSTypePropagator::mergeRegister(
+ int index, const QQmlJSRegisterContent &a, const QQmlJSRegisterContent &b)
+{
+ auto merged = m_typeResolver->merge(a, b);
+ Q_ASSERT(merged.isValid());
+
+ if (!merged.isConversion()) {
+ // The registers were the same. We're already tracking them.
+ m_state.annotations[currentInstructionOffset()].typeConversions[index].content = merged;
+ m_state.registers[index].content = merged;
+ return;
+ }
+
+ auto tryPrevStateConversion = [this](int index, const QQmlJSRegisterContent &merged) -> bool {
+ auto it = m_prevStateAnnotations.find(currentInstructionOffset());
+ if (it == m_prevStateAnnotations.end())
+ return false;
+
+ auto conversion = it->second.typeConversions.find(index);
+ if (conversion == it->second.typeConversions.end())
+ return false;
+
+ const VirtualRegister &lastTry = conversion.value();
+
+ Q_ASSERT(lastTry.content.isValid());
+ if (!lastTry.content.isConversion())
+ return false;
+
+ if (!m_typeResolver->equals(lastTry.content.conversionResult(), merged.conversionResult())
+ || lastTry.content.conversionOrigins() != merged.conversionOrigins()) {
+ return false;
+ }
+
+ // We don't need to track it again if we've come to the same conclusion before.
+ m_state.annotations[currentInstructionOffset()].typeConversions[index] = lastTry;
+ m_state.registers[index] = lastTry;
+ return true;
+ };
+
+ if (!tryPrevStateConversion(index, merged)) {
+ merged = m_typeResolver->tracked(merged);
+ Q_ASSERT(merged.isValid());
+ m_state.annotations[currentInstructionOffset()].typeConversions[index].content = merged;
+ m_state.registers[index].content = merged;
+ }
+}
+
+void QQmlJSTypePropagator::addReadRegister(int index, const QQmlJSRegisterContent &convertTo)
+{
+ m_state.addReadRegister(index, m_typeResolver->convert
+ (m_state.registers[index].content, convertTo));
+}
+
+void QQmlJSTypePropagator::propagateCall(
+ const QList<QQmlJSMetaMethod> &methods, int argc, int argv,
+ const QQmlJSScope::ConstPtr &scope)
+{
+ QStringList errors;
+ const QQmlJSMetaMethod match = bestMatchForCall(methods, argc, argv, &errors);
+
+ if (!match.isValid()) {
+ if (methods.size() == 1) {
+ // Cannot have multiple fuzzy matches if there is only one method
+ Q_ASSERT(errors.size() == 1);
+ setError(errors.first());
+ } else if (errors.size() < methods.size()) {
+ setError(u"Multiple matching overrides found. Cannot determine the right one."_s);
+ } else {
+ setError(u"No matching override found. Candidates:\n"_s + errors.join(u'\n'));
+ }
+ return;
+ }
+
+ const auto returnType = match.isJavaScriptFunction()
+ ? m_typeResolver->jsValueType()
+ : QQmlJSScope::ConstPtr(match.returnType());
+ setAccumulator(m_typeResolver->returnType(
+ returnType ? QQmlJSScope::ConstPtr(returnType) : m_typeResolver->voidType(),
+ match.isJavaScriptFunction()
+ ? QQmlJSRegisterContent::JavaScriptReturnValue
+ : QQmlJSRegisterContent::MethodReturnValue,
+ scope));
+ if (!m_state.accumulatorOut().isValid())
+ setError(u"Cannot store return type of method %1()."_s.arg(match.methodName()));
+
+ const auto types = match.parameters();
+ for (int i = 0; i < argc; ++i) {
+ if (i < types.size()) {
+ const QQmlJSScope::ConstPtr type = match.isJavaScriptFunction()
+ ? m_typeResolver->jsValueType()
+ : QQmlJSScope::ConstPtr(types.at(i).type());
+ if (!type.isNull()) {
+ addReadRegister(argv + i, m_typeResolver->globalType(type));
+ continue;
+ }
+ }
+ addReadRegister(argv + i, m_typeResolver->globalType(m_typeResolver->jsValueType()));
+ }
+ m_state.setHasSideEffects(true);
+}
+
+bool QQmlJSTypePropagator::propagateTranslationMethod(
+ const QList<QQmlJSMetaMethod> &methods, int argc, int argv)
+{
+ if (methods.size() != 1)
+ return false;
+
+ const QQmlJSMetaMethod method = methods.front();
+ const QQmlJSRegisterContent intType
+ = m_typeResolver->globalType(m_typeResolver->int32Type());
+ const QQmlJSRegisterContent stringType
+ = m_typeResolver->globalType(m_typeResolver->stringType());
+ const QQmlJSRegisterContent returnType
+ = m_typeResolver->returnType(
+ m_typeResolver->stringType(), QQmlJSRegisterContent::MethodReturnValue,
+ m_typeResolver->jsGlobalObject());
+
+ if (method.methodName() == u"qsTranslate"_s) {
+ switch (argc) {
+ case 4:
+ addReadRegister(argv + 3, intType); // n
+ Q_FALLTHROUGH();
+ case 3:
+ addReadRegister(argv + 2, stringType); // disambiguation
+ Q_FALLTHROUGH();
+ case 2:
+ addReadRegister(argv + 1, stringType); // sourceText
+ addReadRegister(argv, stringType); // context
+ setAccumulator(returnType);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ if (method.methodName() == u"QT_TRANSLATE_NOOP"_s) {
+ switch (argc) {
+ case 3:
+ addReadRegister(argv + 2, stringType); // disambiguation
+ Q_FALLTHROUGH();
+ case 2:
+ addReadRegister(argv + 1, stringType); // sourceText
+ addReadRegister(argv, stringType); // context
+ setAccumulator(returnType);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ if (method.methodName() == u"qsTr"_s) {
+ switch (argc) {
+ case 3:
+ addReadRegister(argv + 2, intType); // n
+ Q_FALLTHROUGH();
+ case 2:
+ addReadRegister(argv + 1, stringType); // disambiguation
+ Q_FALLTHROUGH();
+ case 1:
+ addReadRegister(argv, stringType); // sourceText
+ setAccumulator(returnType);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ if (method.methodName() == u"QT_TR_NOOP"_s) {
+ switch (argc) {
+ case 2:
+ addReadRegister(argv + 1, stringType); // disambiguation
+ Q_FALLTHROUGH();
+ case 1:
+ addReadRegister(argv, stringType); // sourceText
+ setAccumulator(returnType);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ if (method.methodName() == u"qsTrId"_s) {
+ switch (argc) {
+ case 2:
+ addReadRegister(argv + 1, intType); // n
+ Q_FALLTHROUGH();
+ case 1:
+ addReadRegister(argv, stringType); // id
+ setAccumulator(returnType);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ if (method.methodName() == u"QT_TRID_NOOP"_s) {
+ switch (argc) {
+ case 1:
+ addReadRegister(argv, stringType); // id
+ setAccumulator(returnType);
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ return false;
+}
+
+void QQmlJSTypePropagator::propagateStringArgCall(int argv)
+{
+ setAccumulator(m_typeResolver->returnType(
+ m_typeResolver->stringType(), QQmlJSRegisterContent::MethodReturnValue,
+ m_typeResolver->stringType()));
+ Q_ASSERT(m_state.accumulatorOut().isValid());
+
+ const QQmlJSScope::ConstPtr input = m_typeResolver->containedType(
+ m_state.registers[argv].content);
+
+ if (m_typeResolver->equals(input, m_typeResolver->uint32Type())) {
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->realType()));
+ return;
+ }
+
+ if (m_typeResolver->isIntegral(input)) {
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->int32Type()));
+ return;
+ }
+
+ if (m_typeResolver->isNumeric(input)) {
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->realType()));
+ return;
+ }
+
+ if (m_typeResolver->equals(input, m_typeResolver->boolType())) {
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->boolType()));
+ return;
+ }
+
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->stringType()));
+}
+
+bool QQmlJSTypePropagator::propagateArrayMethod(
+ const QString &name, int argc, int argv, const QQmlJSRegisterContent &baseType)
+{
+ // TODO:
+ // * For concat() we need to decide what kind of array to return and what kinds of arguments to
+ // accept.
+ // * For entries(), keys(), and values() we need iterators.
+ // * For find(), findIndex(), sort(), every(), some(), forEach(), map(), filter(), reduce(),
+ // and reduceRight() we need typed function pointers.
+
+ const auto intType = m_typeResolver->globalType(m_typeResolver->int32Type());
+ const auto stringType = m_typeResolver->globalType(m_typeResolver->stringType());
+ const auto baseContained = m_typeResolver->containedType(baseType);
+ const auto valueContained = baseContained->valueType();
+ const auto valueType = m_typeResolver->globalType(valueContained);
+
+ const bool canHaveSideEffects = (baseType.isProperty() && baseType.isWritable())
+ || baseContained->isListProperty()
+ || baseType.isConversion();
+
+ const auto setReturnType = [&](const QQmlJSScope::ConstPtr type) {
+ setAccumulator(m_typeResolver->returnType(
+ type, QQmlJSRegisterContent::MethodReturnValue, baseContained));
+ };
+
+ if (name == u"copyWithin" && argc > 0 && argc < 4) {
+ for (int i = 0; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
+ return false;
+ }
+
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, intType);
+
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(baseContained);
+ return true;
+ }
+
+ if (name == u"fill" && argc > 0 && argc < 4) {
+ if (!canConvertFromTo(m_state.registers[argv].content, valueType))
+ return false;
+
+ for (int i = 1; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
+ return false;
+ }
+
+ addReadRegister(argv, valueType);
+
+ for (int i = 1; i < argc; ++i)
+ addReadRegister(argv + i, intType);
+
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(baseContained);
+ return true;
+ }
+
+ if (name == u"includes" && argc > 0 && argc < 3) {
+ if (!canConvertFromTo(m_state.registers[argv].content, valueType))
+ return false;
+
+ if (argc == 2) {
+ if (!canConvertFromTo(m_state.registers[argv + 1].content, intType))
+ return false;
+ addReadRegister(argv + 1, intType);
+ }
+
+ addReadRegister(argv, valueType);
+ setReturnType(m_typeResolver->boolType());
+ return true;
+ }
+
+ if (name == u"toString" || (name == u"join" && argc < 2)) {
+ if (argc == 1) {
+ if (!canConvertFromTo(m_state.registers[argv].content, stringType))
+ return false;
+ addReadRegister(argv, stringType);
+ }
+
+ setReturnType(m_typeResolver->stringType());
+ return true;
+ }
+
+ if ((name == u"pop" || name == u"shift") && argc == 0) {
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(valueContained);
+ return true;
+ }
+
+ if (name == u"push" || name == u"unshift") {
+ for (int i = 0; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, valueType))
+ return false;
+ }
+
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, valueType);
+
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(m_typeResolver->int32Type());
+ return true;
+ }
+
+ if (name == u"reverse" && argc == 0) {
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(baseContained);
+ return true;
+ }
+
+ if (name == u"slice" && argc < 3) {
+ for (int i = 0; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
+ return false;
+ }
+
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(argv + i, intType);
+
+ setReturnType(baseType.storedType()->isListProperty()
+ ? m_typeResolver->qObjectListType()
+ : baseContained);
+ return true;
+ }
+
+ if (name == u"splice" && argc > 0) {
+ for (int i = 0; i < 2; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, intType))
+ return false;
+ }
+
+ for (int i = 2; i < argc; ++i) {
+ if (!canConvertFromTo(m_state.registers[argv + i].content, valueType))
+ return false;
+ }
+
+ for (int i = 0; i < 2; ++i)
+ addReadRegister(argv + i, intType);
+
+ for (int i = 2; i < argc; ++i)
+ addReadRegister(argv + i, valueType);
+
+ m_state.setHasSideEffects(canHaveSideEffects);
+ setReturnType(baseContained);
+ return true;
+ }
+
+ if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) {
+ if (!canConvertFromTo(m_state.registers[argv].content, valueType))
+ return false;
+
+ if (argc == 2) {
+ if (!canConvertFromTo(m_state.registers[argv + 1].content, intType))
+ return false;
+ addReadRegister(argv + 1, intType);
+ }
+
+ addReadRegister(argv, valueType);
+ setReturnType(m_typeResolver->int32Type());
+ return true;
+ }
+
+ return false;
+}
+
+void QQmlJSTypePropagator::generate_CallPropertyLookup(int lookupIndex, int base, int argc,
+ int argv)
+{
+ generate_CallProperty(m_jsUnitGenerator->lookupNameIndex(lookupIndex), base, argc, argv);
+}
+
+void QQmlJSTypePropagator::generate_CallName(int name, int argc, int argv)
+{
+ propagateScopeLookupCall(m_jsUnitGenerator->stringForIndex(name), argc, argv);
+}
+
+void QQmlJSTypePropagator::generate_CallPossiblyDirectEval(int argc, int argv)
+{
+ m_state.setHasSideEffects(true);
+ Q_UNUSED(argc)
+ Q_UNUSED(argv)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::propagateScopeLookupCall(const QString &functionName, int argc, int argv)
+{
+ const QQmlJSRegisterContent resolvedContent
+ = m_typeResolver->scopedType(m_function->qmlScope, functionName);
+ if (resolvedContent.isMethod()) {
+ const auto methods = resolvedContent.method();
+ if (resolvedContent.variant() == QQmlJSRegisterContent::JavaScriptGlobal) {
+ if (propagateTranslationMethod(methods, argc, argv))
+ return;
+ }
+
+ if (!methods.isEmpty()) {
+ propagateCall(methods, argc, argv, resolvedContent.scopeType());
+ return;
+ }
+ }
+
+ setError(u"method %1 cannot be resolved."_s.arg(functionName));
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType()));
+
+ setError(u"Cannot find function '%1'"_s.arg(functionName));
+
+ handleUnqualifiedAccess(functionName, true);
+}
+
+void QQmlJSTypePropagator::generate_CallGlobalLookup(int index, int argc, int argv)
+{
+ propagateScopeLookupCall(m_jsUnitGenerator->lookupName(index), argc, argv);
+}
+
+void QQmlJSTypePropagator::generate_CallQmlContextPropertyLookup(int index, int argc, int argv)
+{
+ const QString name = m_jsUnitGenerator->lookupName(index);
+ propagateScopeLookupCall(name, argc, argv);
+ checkDeprecated(m_function->qmlScope, name, true);
+}
+
+void QQmlJSTypePropagator::generate_CallWithSpread(int func, int thisObject, int argc, int argv)
+{
+ m_state.setHasSideEffects(true);
+ Q_UNUSED(func)
+ Q_UNUSED(thisObject)
+ Q_UNUSED(argc)
+ Q_UNUSED(argv)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_TailCall(int func, int thisObject, int argc, int argv)
+{
+ m_state.setHasSideEffects(true);
+ Q_UNUSED(func)
+ Q_UNUSED(thisObject)
+ Q_UNUSED(argc)
+ Q_UNUSED(argv)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_Construct_SCDate(int argc, int argv)
+{
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->dateTimeType()));
+
+ if (argc == 1) {
+ const QQmlJSRegisterContent argType = m_state.registers[argv].content;
+ if (m_typeResolver->isNumeric(argType)) {
+ addReadRegister(
+ argv, m_typeResolver->globalType(m_typeResolver->realType()));
+ } else if (m_typeResolver->registerContains(argType, m_typeResolver->stringType())) {
+ addReadRegister(
+ argv, m_typeResolver->globalType(m_typeResolver->stringType()));
+ } else if (m_typeResolver->registerContains(argType, m_typeResolver->dateTimeType())
+ || m_typeResolver->registerContains(argType, m_typeResolver->dateType())
+ || m_typeResolver->registerContains(argType, m_typeResolver->timeType())) {
+ addReadRegister(
+ argv, m_typeResolver->globalType(m_typeResolver->dateTimeType()));
+ } else {
+ addReadRegister(
+ argv, m_typeResolver->globalType(m_typeResolver->jsPrimitiveType()));
+ }
+ } else {
+ constexpr int maxArgc = 7; // year, month, day, hours, minutes, seconds, milliseconds
+ for (int i = 0; i < std::min(argc, maxArgc); ++i) {
+ addReadRegister(
+ argv + i, m_typeResolver->globalType(m_typeResolver->realType()));
+ }
+ }
+}
+
+void QQmlJSTypePropagator::generate_Construct_SCArray(int argc, int argv)
+{
+ if (argc == 1) {
+ if (m_typeResolver->isNumeric(m_state.registers[argv].content)) {
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->variantListType()));
+ addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->realType()));
+ } else {
+ generate_DefineArray(argc, argv);
+ }
+ } else {
+ generate_DefineArray(argc, argv);
+ }
+}
+void QQmlJSTypePropagator::generate_Construct(int func, int argc, int argv)
+{
+ const QQmlJSRegisterContent type = m_state.registers[func].content;
+ if (!type.isMethod()) {
+ m_state.setHasSideEffects(true);
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType()));
+ return;
+ }
+
+ if (type.method() == m_typeResolver->jsGlobalObject()->methods(u"Date"_s)) {
+ generate_Construct_SCDate(argc, argv);
+ return;
+ }
+
+ if (type.method() == m_typeResolver->jsGlobalObject()->methods(u"Array"_s)) {
+ generate_Construct_SCArray(argc, argv);
+
+ return;
+ }
+
+ m_state.setHasSideEffects(true);
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType()));
+}
+
+void QQmlJSTypePropagator::generate_ConstructWithSpread(int func, int argc, int argv)
+{
+ m_state.setHasSideEffects(true);
+ Q_UNUSED(func)
+ Q_UNUSED(argc)
+ Q_UNUSED(argv)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_SetUnwindHandler(int offset)
+{
+ m_state.setHasSideEffects(true);
+ Q_UNUSED(offset)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE();
+}
+
+void QQmlJSTypePropagator::generate_UnwindDispatch()
+{
+ m_state.setHasSideEffects(true);
+ INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE();
+}
+
+void QQmlJSTypePropagator::generate_UnwindToLabel(int level, int offset)
+{
+ m_state.setHasSideEffects(true);
+ Q_UNUSED(level)
+ Q_UNUSED(offset)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_DeadTemporalZoneCheck(int name)
+{
+ const auto fail = [this, name]() {
+ setError(u"Cannot statically assert the dead temporal zone check for %1"_s.arg(
+ name ? m_jsUnitGenerator->stringForIndex(name) : u"the anonymous accumulator"_s));
+ };
+
+ const QQmlJSRegisterContent in = m_state.accumulatorIn();
+ if (in.isConversion()) {
+ for (const QQmlJSScope::ConstPtr &origin : in.conversionOrigins()) {
+ if (!m_typeResolver->equals(origin, m_typeResolver->emptyType()))
+ continue;
+ fail();
+ break;
+ }
+ } else if (m_typeResolver->registerContains(in, m_typeResolver->emptyType())) {
+ fail();
+ }
+}
+
+void QQmlJSTypePropagator::generate_ThrowException()
+{
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType()));
+ m_state.setHasSideEffects(true);
+ m_state.skipInstructionsUntilNextJumpTarget = true;
+}
+
+void QQmlJSTypePropagator::generate_GetException()
+{
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_SetException()
+{
+ m_state.setHasSideEffects(true);
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_CreateCallContext()
+{
+ m_state.setHasSideEffects(true);
+}
+
+void QQmlJSTypePropagator::generate_PushCatchContext(int index, int name)
+{
+ m_state.setHasSideEffects(true);
+ Q_UNUSED(index)
+ Q_UNUSED(name)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED_IGNORE();
+}
+
+void QQmlJSTypePropagator::generate_PushWithContext()
+{
+ m_state.setHasSideEffects(true);
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_PushBlockContext(int index)
+{
+ m_state.setHasSideEffects(true);
+ Q_UNUSED(index)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_CloneBlockContext()
+{
+ m_state.setHasSideEffects(true);
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_PushScriptContext(int index)
+{
+ m_state.setHasSideEffects(true);
+ Q_UNUSED(index)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_PopScriptContext()
+{
+ m_state.setHasSideEffects(true);
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_PopContext()
+{
+ m_state.setHasSideEffects(true);
+}
+
+void QQmlJSTypePropagator::generate_GetIterator(int iterator)
+{
+ const QQmlJSRegisterContent listType = m_state.accumulatorIn();
+ if (!listType.isList()) {
+ const auto jsValue = m_typeResolver->globalType(m_typeResolver->jsValueType());
+ addReadAccumulator(jsValue);
+ setAccumulator(jsValue);
+ return;
+ }
+
+ addReadAccumulator(listType);
+ setAccumulator(m_typeResolver->iteratorPointer(
+ listType, QQmlJS::AST::ForEachType(iterator), currentInstructionOffset()));
+}
+
+void QQmlJSTypePropagator::generate_IteratorNext(int value, int offset)
+{
+ const QQmlJSRegisterContent iteratorType = m_state.accumulatorIn();
+ addReadAccumulator(iteratorType);
+ setRegister(value, m_typeResolver->merge(
+ m_typeResolver->valueType(iteratorType),
+ m_typeResolver->globalType(m_typeResolver->voidType())));
+ saveRegisterStateForJump(offset);
+ m_state.setHasSideEffects(true);
+}
+
+void QQmlJSTypePropagator::generate_IteratorNextForYieldStar(int iterator, int object, int offset)
+{
+ Q_UNUSED(iterator)
+ Q_UNUSED(object)
+ Q_UNUSED(offset)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_IteratorClose()
+{
+ // Noop
+}
+
+void QQmlJSTypePropagator::generate_DestructureRestElement()
+{
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_DeleteProperty(int base, int index)
+{
+ Q_UNUSED(base)
+ Q_UNUSED(index)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_DeleteName(int name)
+{
+ Q_UNUSED(name)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_TypeofName(int name)
+{
+ Q_UNUSED(name);
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->stringType()));
+}
+
+void QQmlJSTypePropagator::generate_TypeofValue()
+{
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->stringType()));
+}
+
+void QQmlJSTypePropagator::generate_DeclareVar(int varName, int isDeletable)
+{
+ Q_UNUSED(varName)
+ Q_UNUSED(isDeletable)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_DefineArray(int argc, int args)
+{
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->variantListType()));
+
+ // Track all arguments as the same type.
+ const QQmlJSRegisterContent elementType
+ = m_typeResolver->tracked(m_typeResolver->globalType(m_typeResolver->varType()));
+ for (int i = 0; i < argc; ++i)
+ addReadRegister(args + i, elementType);
+}
+
+void QQmlJSTypePropagator::generate_DefineObjectLiteral(int internalClassId, int argc, int args)
+{
+ const int classSize = m_jsUnitGenerator->jsClassSize(internalClassId);
+ Q_ASSERT(argc >= classSize);
+
+ // Track each element as separate type
+ for (int i = 0; i < classSize; ++i) {
+ addReadRegister(
+ args + i,
+ m_typeResolver->tracked(m_typeResolver->globalType(m_typeResolver->varType())));
+ }
+
+ for (int i = classSize; i < argc; i += 3) {
+ // layout for remaining members is:
+ // 0: ObjectLiteralArgument - Value|Method|Getter|Setter
+ // We cannot do anything useful with this. Any code that would call a getter/setter/method
+ // could not be compiled to C++. Ignore it.
+
+ // 1: name of argument
+ addReadRegister(
+ args + i + 1,
+ m_typeResolver->tracked(m_typeResolver->globalType(m_typeResolver->stringType())));
+
+ // 2: value of argument
+ addReadRegister(
+ args + i + 2,
+ m_typeResolver->tracked(m_typeResolver->globalType(m_typeResolver->varType())));
+ }
+
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->variantMapType()));
+}
+
+void QQmlJSTypePropagator::generate_CreateClass(int classIndex, int heritage, int computedNames)
+{
+ Q_UNUSED(classIndex)
+ Q_UNUSED(heritage)
+ Q_UNUSED(computedNames)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_CreateMappedArgumentsObject()
+{
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_CreateUnmappedArgumentsObject()
+{
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_CreateRestParameter(int argIndex)
+{
+ Q_UNUSED(argIndex)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_ConvertThisToObject()
+{
+ setRegister(This, m_typeResolver->globalType(m_typeResolver->qObjectType()));
+}
+
+void QQmlJSTypePropagator::generate_LoadSuperConstructor()
+{
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_ToObject()
+{
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_Jump(int offset)
+{
+ saveRegisterStateForJump(offset);
+ m_state.skipInstructionsUntilNextJumpTarget = true;
+ m_state.setHasSideEffects(true);
+}
+
+void QQmlJSTypePropagator::generate_JumpTrue(int offset)
+{
+ if (!canConvertFromTo(m_state.accumulatorIn(),
+ m_typeResolver->globalType(m_typeResolver->boolType()))) {
+ setError(u"cannot convert from %1 to boolean"_s
+ .arg(m_state.accumulatorIn().descriptiveName()));
+ return;
+ }
+ saveRegisterStateForJump(offset);
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->boolType()));
+ m_state.setHasSideEffects(true);
+}
+
+void QQmlJSTypePropagator::generate_JumpFalse(int offset)
+{
+ if (!canConvertFromTo(m_state.accumulatorIn(),
+ m_typeResolver->globalType(m_typeResolver->boolType()))) {
+ setError(u"cannot convert from %1 to boolean"_s
+ .arg(m_state.accumulatorIn().descriptiveName()));
+ return;
+ }
+ saveRegisterStateForJump(offset);
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->boolType()));
+ m_state.setHasSideEffects(true);
+}
+
+void QQmlJSTypePropagator::generate_JumpNoException(int offset)
+{
+ saveRegisterStateForJump(offset);
+ m_state.setHasSideEffects(true);
+}
+
+void QQmlJSTypePropagator::generate_JumpNotUndefined(int offset)
+{
+ Q_UNUSED(offset)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_CheckException()
+{
+ m_state.setHasSideEffects(true);
+}
+
+void QQmlJSTypePropagator::recordEqualsNullType()
+{
+ // TODO: We can specialize this further, for QVariant, QJSValue, int, bool, whatever.
+ if (m_typeResolver->registerContains(m_state.accumulatorIn(), m_typeResolver->nullType())
+ || m_typeResolver->containedType(m_state.accumulatorIn())->isReferenceType()) {
+ addReadAccumulator(m_state.accumulatorIn());
+ } else {
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->jsPrimitiveType()));
+ }
+}
+void QQmlJSTypePropagator::recordEqualsIntType()
+{
+ // We have specializations for numeric types and bool.
+ const QQmlJSScope::ConstPtr in = m_typeResolver->containedType(m_state.accumulatorIn());
+ if (m_typeResolver->registerContains(m_state.accumulatorIn(), m_typeResolver->boolType())
+ || m_typeResolver->isNumeric(m_state.accumulatorIn())) {
+ addReadAccumulator(m_state.accumulatorIn());
+ } else {
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->jsPrimitiveType()));
+ }
+}
+void QQmlJSTypePropagator::recordEqualsType(int lhs)
+{
+ const auto isNumericOrEnum = [this](const QQmlJSRegisterContent &content) {
+ return content.isEnumeration() || m_typeResolver->isNumeric(content);
+ };
+
+ const auto accumulatorIn = m_state.accumulatorIn();
+ const auto lhsRegister = m_state.registers[lhs].content;
+
+ // If the types are primitive, we compare directly ...
+ if (m_typeResolver->isPrimitive(accumulatorIn) || accumulatorIn.isEnumeration()) {
+ if (m_typeResolver->registerContains(
+ accumulatorIn, m_typeResolver->containedType(lhsRegister))
+ || (isNumericOrEnum(accumulatorIn) && isNumericOrEnum(lhsRegister))
+ || m_typeResolver->isPrimitive(lhsRegister)) {
+ addReadRegister(lhs, lhsRegister);
+ addReadAccumulator(accumulatorIn);
+ return;
+ }
+ }
+
+ const auto containedAccumulatorIn = m_typeResolver->isOptionalType(accumulatorIn)
+ ? m_typeResolver->extractNonVoidFromOptionalType(accumulatorIn)
+ : m_typeResolver->containedType(accumulatorIn);
+
+ const auto containedLhs = m_typeResolver->isOptionalType(lhsRegister)
+ ? m_typeResolver->extractNonVoidFromOptionalType(lhsRegister)
+ : m_typeResolver->containedType(lhsRegister);
+
+ // We don't modify types if the types are comparable with QObject, QUrl or var types
+ if (canStrictlyCompareWithVar(m_typeResolver, containedLhs, containedAccumulatorIn)
+ || canCompareWithQObject(m_typeResolver, containedLhs, containedAccumulatorIn)
+ || canCompareWithQUrl(m_typeResolver, containedLhs, containedAccumulatorIn)) {
+ addReadRegister(lhs, lhsRegister);
+ addReadAccumulator(accumulatorIn);
+ return;
+ }
+
+ // Otherwise they're both casted to QJSValue.
+ // TODO: We can add more specializations here: object/null etc
+
+ const QQmlJSRegisterContent jsval = m_typeResolver->globalType(m_typeResolver->jsValueType());
+ addReadRegister(lhs, jsval);
+ addReadAccumulator(jsval);
+}
+
+void QQmlJSTypePropagator::recordCompareType(int lhs)
+{
+ // If they're both numeric, we can compare them directly.
+ // They may be casted to double, though.
+ const QQmlJSRegisterContent read
+ = (m_typeResolver->isNumeric(m_state.accumulatorIn())
+ && m_typeResolver->isNumeric(m_state.registers[lhs].content))
+ ? m_typeResolver->merge(m_state.accumulatorIn(), m_state.registers[lhs].content)
+ : m_typeResolver->globalType(m_typeResolver->jsPrimitiveType());
+ addReadRegister(lhs, read);
+ addReadAccumulator(read);
+}
+
+void QQmlJSTypePropagator::generate_CmpEqNull()
+{
+ recordEqualsNullType();
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->boolType()));
+}
+
+void QQmlJSTypePropagator::generate_CmpNeNull()
+{
+ recordEqualsNullType();
+ setAccumulator(m_typeResolver->globalType(m_typeResolver->boolType()));
+}
+
+void QQmlJSTypePropagator::generate_CmpEqInt(int lhsConst)
+{
+ recordEqualsIntType();
+ Q_UNUSED(lhsConst)
+ setAccumulator(QQmlJSRegisterContent(m_typeResolver->typeForBinaryOperation(
+ QSOperator::Op::Equal, m_typeResolver->globalType(m_typeResolver->int32Type()),
+ m_state.accumulatorIn())));
+}
+
+void QQmlJSTypePropagator::generate_CmpNeInt(int lhsConst)
+{
+ recordEqualsIntType();
+ Q_UNUSED(lhsConst)
+ setAccumulator(QQmlJSRegisterContent(m_typeResolver->typeForBinaryOperation(
+ QSOperator::Op::NotEqual, m_typeResolver->globalType(m_typeResolver->int32Type()),
+ m_state.accumulatorIn())));
+}
+
+void QQmlJSTypePropagator::generate_CmpEq(int lhs)
+{
+ recordEqualsType(lhs);
+ propagateBinaryOperation(QSOperator::Op::Equal, lhs);
+}
+
+void QQmlJSTypePropagator::generate_CmpNe(int lhs)
+{
+ recordEqualsType(lhs);
+ propagateBinaryOperation(QSOperator::Op::NotEqual, lhs);
+}
+
+void QQmlJSTypePropagator::generate_CmpGt(int lhs)
+{
+ recordCompareType(lhs);
+ propagateBinaryOperation(QSOperator::Op::Gt, lhs);
+}
+
+void QQmlJSTypePropagator::generate_CmpGe(int lhs)
+{
+ recordCompareType(lhs);
+ propagateBinaryOperation(QSOperator::Op::Ge, lhs);
+}
+
+void QQmlJSTypePropagator::generate_CmpLt(int lhs)
+{
+ recordCompareType(lhs);
+ propagateBinaryOperation(QSOperator::Op::Lt, lhs);
+}
+
+void QQmlJSTypePropagator::generate_CmpLe(int lhs)
+{
+ recordCompareType(lhs);
+ propagateBinaryOperation(QSOperator::Op::Le, lhs);
+}
+
+void QQmlJSTypePropagator::generate_CmpStrictEqual(int lhs)
+{
+ recordEqualsType(lhs);
+ propagateBinaryOperation(QSOperator::Op::StrictEqual, lhs);
+}
+
+void QQmlJSTypePropagator::generate_CmpStrictNotEqual(int lhs)
+{
+ recordEqualsType(lhs);
+ propagateBinaryOperation(QSOperator::Op::StrictNotEqual, lhs);
+}
+
+void QQmlJSTypePropagator::generate_CmpIn(int lhs)
+{
+ // TODO: Most of the time we don't need the object at all, but only its metatype.
+ // Fix this when we add support for the "in" instruction to the code generator.
+ // Also, specialize on lhs to avoid conversion to QJSPrimitiveValue.
+
+ addReadRegister(lhs, m_typeResolver->globalType(m_typeResolver->jsValueType()));
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->jsValueType()));
+
+ propagateBinaryOperation(QSOperator::Op::In, lhs);
+}
+
+void QQmlJSTypePropagator::generate_CmpInstanceOf(int lhs)
+{
+ Q_UNUSED(lhs)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_As(int lhs)
+{
+ const QQmlJSRegisterContent input = checkedInputRegister(lhs);
+ const QQmlJSScope::ConstPtr inContained = m_typeResolver->containedType(input);
+
+ QQmlJSScope::ConstPtr outContained;
+
+ switch (m_state.accumulatorIn().variant()) {
+ case QQmlJSRegisterContent::ScopeAttached:
+ outContained = m_state.accumulatorIn().scopeType();
+ break;
+ case QQmlJSRegisterContent::MetaType:
+ outContained = m_state.accumulatorIn().scopeType();
+ if (outContained->isComposite()) // Otherwise we don't need it
+ addReadAccumulator(m_typeResolver->globalType(m_typeResolver->metaObjectType()));
+ break;
+ default:
+ outContained = m_typeResolver->containedType(m_state.accumulatorIn());
+ break;
+ }
+
+ QQmlJSRegisterContent output;
+
+ if (outContained->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
+ // A referece type cast can result in either the type or null.
+ // Reference types can hold null. We don't need to special case that.
+
+ if (m_typeResolver->inherits(inContained, outContained))
+ output = input;
+ else
+ output = m_typeResolver->cast(input, outContained);
+ } else if (!m_typeResolver->canAddressValueTypes()) {
+ setError(u"invalid cast from %1 to %2. You can only cast object types."_s
+ .arg(input.descriptiveName(), m_state.accumulatorIn().descriptiveName()));
+ return;
+ } else {
+ if (m_typeResolver->inherits(inContained, outContained)) {
+ // A "slicing" cannot result in void
+ output = m_typeResolver->cast(input, outContained);
+ } else {
+ // A value type cast can result in either the type or undefined.
+ // Using convert() retains the variant of the input type.
+ output = m_typeResolver->merge(
+ m_typeResolver->cast(input, outContained),
+ m_typeResolver->cast(input, m_typeResolver->voidType()));
+ }
+ }
+
+ addReadRegister(lhs, input);
+ setAccumulator(output);
+}
+
+void QQmlJSTypePropagator::checkConversion(
+ const QQmlJSRegisterContent &from, const QQmlJSRegisterContent &to)
+{
+ if (!canConvertFromTo(from, to)) {
+ setError(u"cannot convert from %1 to %2"_s
+ .arg(from.descriptiveName(), to.descriptiveName()));
+ }
+}
+
+void QQmlJSTypePropagator::generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator op)
+{
+ const QQmlJSRegisterContent type = m_typeResolver->typeForArithmeticUnaryOperation(
+ op, m_state.accumulatorIn());
+ checkConversion(m_state.accumulatorIn(), type);
+ addReadAccumulator(type);
+ setAccumulator(type);
+}
+
+void QQmlJSTypePropagator::generate_UNot()
+{
+ generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Not);
+}
+
+void QQmlJSTypePropagator::generate_UPlus()
+{
+ generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Plus);
+}
+
+void QQmlJSTypePropagator::generate_UMinus()
+{
+ generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Minus);
+}
+
+void QQmlJSTypePropagator::generate_UCompl()
+{
+ generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Complement);
+}
+
+void QQmlJSTypePropagator::generate_Increment()
+{
+ generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Increment);
+}
+
+void QQmlJSTypePropagator::generate_Decrement()
+{
+ generateUnaryArithmeticOperation(QQmlJSTypeResolver::UnaryOperator::Decrement);
+}
+
+void QQmlJSTypePropagator::generateBinaryArithmeticOperation(QSOperator::Op op, int lhs)
+{
+ const QQmlJSRegisterContent type = propagateBinaryOperation(op, lhs);
+
+ checkConversion(checkedInputRegister(lhs), type);
+ addReadRegister(lhs, type);
+
+ checkConversion(m_state.accumulatorIn(), type);
+ addReadAccumulator(type);
+}
+
+void QQmlJSTypePropagator::generateBinaryConstArithmeticOperation(QSOperator::Op op)
+{
+ const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation(
+ op, m_state.accumulatorIn(),
+ m_typeResolver->builtinType(m_typeResolver->int32Type()));
+
+ checkConversion(m_state.accumulatorIn(), type);
+ addReadAccumulator(type);
+ setAccumulator(type);
+}
+
+void QQmlJSTypePropagator::generate_Add(int lhs)
+{
+ generateBinaryArithmeticOperation(QSOperator::Op::Add, lhs);
+}
+
+void QQmlJSTypePropagator::generate_BitAnd(int lhs)
+{
+ generateBinaryArithmeticOperation(QSOperator::Op::BitAnd, lhs);
+}
+
+void QQmlJSTypePropagator::generate_BitOr(int lhs)
+{
+ generateBinaryArithmeticOperation(QSOperator::Op::BitOr, lhs);
+}
+
+void QQmlJSTypePropagator::generate_BitXor(int lhs)
+{
+ generateBinaryArithmeticOperation(QSOperator::Op::BitXor, lhs);
+}
+
+void QQmlJSTypePropagator::generate_UShr(int lhs)
+{
+ generateBinaryArithmeticOperation(QSOperator::Op::URShift, lhs);
+}
+
+void QQmlJSTypePropagator::generate_Shr(int lhs)
+{
+ generateBinaryArithmeticOperation(QSOperator::Op::RShift, lhs);
+}
+
+void QQmlJSTypePropagator::generate_Shl(int lhs)
+{
+ generateBinaryArithmeticOperation(QSOperator::Op::LShift, lhs);
+}
+
+void QQmlJSTypePropagator::generate_BitAndConst(int rhsConst)
+{
+ Q_UNUSED(rhsConst)
+ generateBinaryConstArithmeticOperation(QSOperator::Op::BitAnd);
+}
+
+void QQmlJSTypePropagator::generate_BitOrConst(int rhsConst)
+{
+ Q_UNUSED(rhsConst)
+ generateBinaryConstArithmeticOperation(QSOperator::Op::BitOr);
+}
+
+void QQmlJSTypePropagator::generate_BitXorConst(int rhsConst)
+{
+ Q_UNUSED(rhsConst)
+ generateBinaryConstArithmeticOperation(QSOperator::Op::BitXor);
+}
+
+void QQmlJSTypePropagator::generate_UShrConst(int rhsConst)
+{
+ Q_UNUSED(rhsConst)
+ generateBinaryConstArithmeticOperation(QSOperator::Op::URShift);
+}
+
+void QQmlJSTypePropagator::generate_ShrConst(int rhsConst)
+{
+ Q_UNUSED(rhsConst)
+ generateBinaryConstArithmeticOperation(QSOperator::Op::RShift);
+}
+
+void QQmlJSTypePropagator::generate_ShlConst(int rhsConst)
+{
+ Q_UNUSED(rhsConst)
+ generateBinaryConstArithmeticOperation(QSOperator::Op::LShift);
+}
+
+void QQmlJSTypePropagator::generate_Exp(int lhs)
+{
+ generateBinaryArithmeticOperation(QSOperator::Op::Exp, lhs);
+}
+
+void QQmlJSTypePropagator::generate_Mul(int lhs)
+{
+ generateBinaryArithmeticOperation(QSOperator::Op::Mul, lhs);
+}
+
+void QQmlJSTypePropagator::generate_Div(int lhs)
+{
+ generateBinaryArithmeticOperation(QSOperator::Op::Div, lhs);
+}
+
+void QQmlJSTypePropagator::generate_Mod(int lhs)
+{
+ generateBinaryArithmeticOperation(QSOperator::Op::Mod, lhs);
+}
+
+void QQmlJSTypePropagator::generate_Sub(int lhs)
+{
+ generateBinaryArithmeticOperation(QSOperator::Op::Sub, lhs);
+}
+
+void QQmlJSTypePropagator::generate_InitializeBlockDeadTemporalZone(int firstReg, int count)
+{
+ for (int reg = firstReg, end = firstReg + count; reg < end; ++reg)
+ setRegister(reg, m_typeResolver->globalType(m_typeResolver->emptyType()));
+}
+
+void QQmlJSTypePropagator::generate_ThrowOnNullOrUndefined()
+{
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+void QQmlJSTypePropagator::generate_GetTemplateObject(int index)
+{
+ Q_UNUSED(index)
+ INSTR_PROLOGUE_NOT_IMPLEMENTED();
+}
+
+QV4::Moth::ByteCodeHandler::Verdict
+QQmlJSTypePropagator::startInstruction(QV4::Moth::Instr::Type type)
+{
+ if (m_error->isValid())
+ return SkipInstruction;
+
+ if (m_state.jumpTargets.contains(currentInstructionOffset())) {
+ if (m_state.skipInstructionsUntilNextJumpTarget) {
+ // When re-surfacing from dead code, all registers are invalid.
+ m_state.registers.clear();
+ m_state.skipInstructionsUntilNextJumpTarget = false;
+ }
+ } else if (m_state.skipInstructionsUntilNextJumpTarget
+ && !instructionManipulatesContext(type)) {
+ return SkipInstruction;
+ }
+
+ const int currentOffset = currentInstructionOffset();
+
+ // If we reach an instruction that is a target of a jump earlier, then we must check that the
+ // register state at the origin matches the current state. If not, then we may have to inject
+ // conversion code (communicated to code gen via m_state.typeConversions). For
+ // example:
+ //
+ // function blah(x: number) { return x > 10 ? 10 : x}
+ //
+ // translates to a situation where in the "true" case, we load an integer into the accumulator
+ // and in the else case a number (x). When the control flow is joined, the types don't match and
+ // we need to make sure that the int is converted to a double just before the jump.
+ for (auto originRegisterStateIt =
+ m_jumpOriginRegisterStateByTargetInstructionOffset.constFind(currentOffset);
+ originRegisterStateIt != m_jumpOriginRegisterStateByTargetInstructionOffset.constEnd()
+ && originRegisterStateIt.key() == currentOffset;
+ ++originRegisterStateIt) {
+ auto stateToMerge = *originRegisterStateIt;
+ for (auto registerIt = stateToMerge.registers.constBegin(),
+ end = stateToMerge.registers.constEnd();
+ registerIt != end; ++registerIt) {
+ const int registerIndex = registerIt.key();
+
+ auto newType = registerIt.value().content;
+ if (!newType.isValid()) {
+ setError(u"When reached from offset %1, %2 is undefined"_s
+ .arg(stateToMerge.originatingOffset)
+ .arg(registerName(registerIndex)));
+ return SkipInstruction;
+ }
+
+ auto currentRegister = m_state.registers.find(registerIndex);
+ if (currentRegister != m_state.registers.end())
+ mergeRegister(registerIndex, newType, currentRegister.value().content);
+ else
+ mergeRegister(registerIndex, newType, newType);
+ }
+ }
+
+ return ProcessInstruction;
+}
+
+void QQmlJSTypePropagator::endInstruction(QV4::Moth::Instr::Type instr)
+{
+ InstructionAnnotation &currentInstruction = m_state.annotations[currentInstructionOffset()];
+ currentInstruction.changedRegister = m_state.changedRegister();
+ currentInstruction.changedRegisterIndex = m_state.changedRegisterIndex();
+ currentInstruction.readRegisters = m_state.takeReadRegisters();
+ currentInstruction.hasSideEffects = m_state.hasSideEffects();
+ currentInstruction.isRename = m_state.isRename();
+
+ switch (instr) {
+ // the following instructions are not expected to produce output in the accumulator
+ case QV4::Moth::Instr::Type::Ret:
+ case QV4::Moth::Instr::Type::Jump:
+ case QV4::Moth::Instr::Type::JumpFalse:
+ case QV4::Moth::Instr::Type::JumpTrue:
+ case QV4::Moth::Instr::Type::StoreReg:
+ case QV4::Moth::Instr::Type::StoreElement:
+ case QV4::Moth::Instr::Type::StoreNameSloppy:
+ case QV4::Moth::Instr::Type::StoreProperty:
+ case QV4::Moth::Instr::Type::SetLookup:
+ case QV4::Moth::Instr::Type::MoveConst:
+ case QV4::Moth::Instr::Type::MoveReg:
+ case QV4::Moth::Instr::Type::CheckException:
+ case QV4::Moth::Instr::Type::CreateCallContext:
+ case QV4::Moth::Instr::Type::PopContext:
+ case QV4::Moth::Instr::Type::JumpNoException:
+ case QV4::Moth::Instr::Type::ThrowException:
+ case QV4::Moth::Instr::Type::SetUnwindHandler:
+ case QV4::Moth::Instr::Type::PushCatchContext:
+ case QV4::Moth::Instr::Type::UnwindDispatch:
+ case QV4::Moth::Instr::Type::InitializeBlockDeadTemporalZone:
+ case QV4::Moth::Instr::Type::ConvertThisToObject:
+ case QV4::Moth::Instr::Type::DeadTemporalZoneCheck:
+ case QV4::Moth::Instr::Type::IteratorNext:
+ case QV4::Moth::Instr::Type::IteratorNextForYieldStar:
+ if (m_state.changedRegisterIndex() == Accumulator && !m_error->isValid()) {
+ setError(u"Instruction is not expected to populate the accumulator"_s);
+ return;
+ }
+ break;
+ default:
+ // If the instruction is expected to produce output, save it in the register set
+ // for the next instruction.
+ if ((!m_state.changedRegister().isValid() || m_state.changedRegisterIndex() != Accumulator)
+ && !m_error->isValid()) {
+ setError(u"Instruction is expected to populate the accumulator"_s);
+ return;
+ }
+ }
+
+ if (!(m_error->isValid() && m_error->isError())
+ && instr != QV4::Moth::Instr::Type::DeadTemporalZoneCheck) {
+ // An instruction needs to have side effects or write to another register otherwise it's a
+ // noop. DeadTemporalZoneCheck is not needed by the compiler and is ignored.
+ Q_ASSERT(m_state.hasSideEffects() || m_state.changedRegisterIndex() != -1);
+ }
+
+ if (m_state.changedRegisterIndex() != InvalidRegister) {
+ Q_ASSERT(m_error->isValid() || m_state.changedRegister().isValid());
+ VirtualRegister &r = m_state.registers[m_state.changedRegisterIndex()];
+ r.content = m_state.changedRegister();
+ r.canMove = false;
+ r.affectedBySideEffects = m_state.isRename()
+ && m_state.isRegisterAffectedBySideEffects(m_state.renameSourceRegisterIndex());
+ m_state.clearChangedRegister();
+ }
+
+ m_state.setHasSideEffects(false);
+ m_state.setIsRename(false);
+ m_state.setReadRegisters(VirtualRegisters());
+}
+
+QQmlJSRegisterContent QQmlJSTypePropagator::propagateBinaryOperation(QSOperator::Op op, int lhs)
+{
+ auto lhsRegister = checkedInputRegister(lhs);
+ if (!lhsRegister.isValid())
+ return QQmlJSRegisterContent();
+
+ const QQmlJSRegisterContent type = m_typeResolver->typeForBinaryOperation(
+ op, lhsRegister, m_state.accumulatorIn());
+
+ setAccumulator(type);
+
+ // If we're dealing with QJSPrimitiveType, do not force premature conversion of the arguemnts
+ // to the target type. Such an operation can lose information.
+ if (type.storedType() == m_typeResolver->jsPrimitiveType())
+ return m_typeResolver->globalType(m_typeResolver->jsPrimitiveType());
+
+ return type;
+}
+
+void QQmlJSTypePropagator::saveRegisterStateForJump(int offset)
+{
+ auto jumpToOffset = offset + nextInstructionOffset();
+ ExpectedRegisterState state;
+ state.registers = m_state.registers;
+ state.originatingOffset = currentInstructionOffset();
+ m_state.jumpTargets.insert(jumpToOffset);
+ if (offset < 0) {
+ // We're jumping backwards. We won't get to merge the register states in this pass anymore.
+
+ const auto registerStates =
+ m_jumpOriginRegisterStateByTargetInstructionOffset.equal_range(jumpToOffset);
+ for (auto it = registerStates.first; it != registerStates.second; ++it) {
+ if (it->registers.keys() == state.registers.keys()
+ && it->registers.values() == state.registers.values()) {
+ return; // We've seen the same register state before. No need for merging.
+ }
+ }
+
+ // The register state at the target offset needs to be resolved in a further pass.
+ m_state.needsMorePasses = true;
+ }
+ m_jumpOriginRegisterStateByTargetInstructionOffset.insert(jumpToOffset, state);
+}
+
+QString QQmlJSTypePropagator::registerName(int registerIndex) const
+{
+ if (registerIndex == Accumulator)
+ return u"accumulator"_s;
+ if (registerIndex >= FirstArgument
+ && registerIndex < FirstArgument + m_function->argumentTypes.size()) {
+ return u"argument %1"_s.arg(registerIndex - FirstArgument);
+ }
+
+ return u"temporary register %1"_s.arg(
+ registerIndex - FirstArgument - m_function->argumentTypes.size());
+}
+
+QQmlJSRegisterContent QQmlJSTypePropagator::checkedInputRegister(int reg)
+{
+ const auto regIt = m_state.registers.find(reg);
+ if (regIt == m_state.registers.end()) {
+ if (isArgument(reg))
+ return argumentType(reg);
+
+ setError(u"Type error: could not infer the type of an expression"_s);
+ return {};
+ }
+ return regIt.value().content;
+}
+
+bool QQmlJSTypePropagator::canConvertFromTo(const QQmlJSRegisterContent &from,
+ const QQmlJSRegisterContent &to)
+{
+ return m_typeResolver->canConvertFromTo(from, to);
+}
+
+QT_END_NAMESPACE