Revision control
++ // Copyright (C) 2021 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++ // Qt-Security score:significant
++
++ #include "qqmljstyperesolver_p.h"
++
++ #include "qqmljsimporter_p.h"
++ #include "qqmljsimportvisitor_p.h"
++ #include "qqmljslogger_p.h"
++ #include "qqmljsutils_p.h"
++ #include <private/qv4value_p.h>
++
++ #include <private/qduplicatetracker_p.h>
++
++ #include <QtCore/qloggingcategory.h>
++ #include <QtCore/qtyperevision.h>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ Q_STATIC_LOGGING_CATEGORY(lcTypeResolver, "qt.qml.compiler.typeresolver", QtInfoMsg);
++
++ static inline void assertExtension(const QQmlJSScope::ConstPtr &type, QLatin1String extension)
++ {
++ Q_ASSERT(type);
++ Q_ASSERT(type->extensionType().scope->internalName() == extension);
++ Q_ASSERT(type->extensionIsJavaScript());
++ }
++
++ QQmlJSTypeResolver::QQmlJSTypeResolver(QQmlJSImporter *importer)
++ : m_pool(std::make_unique<QQmlJSRegisterContentPool>())
++ , m_imports(importer->builtinInternalNames())
++ , m_importer(importer)
++ {
++ const QQmlJSImporter::ImportedTypes &builtinTypes = m_imports;
++
++ m_voidType = builtinTypes.type(u"void"_s).scope;
++ assertExtension(m_voidType, "undefined"_L1);
++
++ m_nullType = builtinTypes.type(u"std::nullptr_t"_s).scope;
++ Q_ASSERT(m_nullType);
++
++ m_realType = builtinTypes.type(u"double"_s).scope;
++ assertExtension(m_realType, "Number"_L1);
++
++ m_floatType = builtinTypes.type(u"float"_s).scope;
++ assertExtension(m_floatType, "Number"_L1);
++
++ m_int8Type = builtinTypes.type(u"qint8"_s).scope;
++ assertExtension(m_int8Type, "Number"_L1);
++
++ m_uint8Type = builtinTypes.type(u"quint8"_s).scope;
++ assertExtension(m_uint8Type, "Number"_L1);
++
++ m_int16Type = builtinTypes.type(u"short"_s).scope;
++ assertExtension(m_int16Type, "Number"_L1);
++
++ m_uint16Type = builtinTypes.type(u"ushort"_s).scope;
++ assertExtension(m_uint16Type, "Number"_L1);
++
++ m_int32Type = builtinTypes.type(u"int"_s).scope;
++ assertExtension(m_int32Type, "Number"_L1);
++
++ m_uint32Type = builtinTypes.type(u"uint"_s).scope;
++ assertExtension(m_uint32Type, "Number"_L1);
++
++ m_int64Type = builtinTypes.type(u"qlonglong"_s).scope;
++ Q_ASSERT(m_int64Type);
++
++ m_uint64Type = builtinTypes.type(u"qulonglong"_s).scope;
++ Q_ASSERT(m_uint64Type);
++
++ m_sizeType = builtinTypes.type(u"qsizetype"_s).scope;
++ assertExtension(m_sizeType, "Number"_L1);
++
++ // qsizetype is either a 32bit or a 64bit signed integer. We don't want to special-case it.
++ Q_ASSERT(m_sizeType == m_int32Type || m_sizeType == m_int64Type);
++
++ m_boolType = builtinTypes.type(u"bool"_s).scope;
++ assertExtension(m_boolType, "Boolean"_L1);
++
++ m_stringType = builtinTypes.type(u"QString"_s).scope;
++ assertExtension(m_stringType, "String"_L1);
++
++ m_stringListType = builtinTypes.type(u"QStringList"_s).scope;
++ assertExtension(m_stringListType, "Array"_L1);
++
++ m_byteArrayType = builtinTypes.type(u"QByteArray"_s).scope;
++ assertExtension(m_byteArrayType, "ArrayBuffer"_L1);
++
++ m_urlType = builtinTypes.type(u"QUrl"_s).scope;
++ assertExtension(m_urlType, "URL"_L1);
++
++ m_dateTimeType = builtinTypes.type(u"QDateTime"_s).scope;
++ assertExtension(m_dateTimeType, "Date"_L1);
++
++ m_dateType = builtinTypes.type(u"QDate"_s).scope;
++ Q_ASSERT(m_dateType);
++
++ m_timeType = builtinTypes.type(u"QTime"_s).scope;
++ Q_ASSERT(m_timeType);
++
++ m_regexpType = builtinTypes.type(u"QRegularExpression"_s).scope;
++ Q_ASSERT(m_regexpType);
++
++ m_variantListType = builtinTypes.type(u"QVariantList"_s).scope;
++ assertExtension(m_variantListType, "Array"_L1);
++
++ m_variantMapType = builtinTypes.type(u"QVariantMap"_s).scope;
++ Q_ASSERT(m_variantMapType);
++ m_varType = builtinTypes.type(u"QVariant"_s).scope;
++ Q_ASSERT(m_varType);
++
++ m_qmlPropertyMapType = builtinTypes.type(u"QQmlPropertyMap"_s).scope;
++ Q_ASSERT(m_qmlPropertyMapType);
++
++ m_jsValueType = builtinTypes.type(u"QJSValue"_s).scope;
++ Q_ASSERT(m_jsValueType);
++
++ m_qObjectType = builtinTypes.type(u"QObject"_s).scope;
++ assertExtension(m_qObjectType, "Object"_L1);
++
++ m_qObjectListType = builtinTypes.type(u"QObjectList"_s).scope;
++ assertExtension(m_qObjectListType, "Array"_L1);
++
++ m_qQmlScriptStringType = builtinTypes.type(u"QQmlScriptString"_s).scope;
++ Q_ASSERT(m_qQmlScriptStringType);
++
++ m_functionType = builtinTypes.type(u"function"_s).scope;
++ Q_ASSERT(m_functionType);
++
++ m_numberPrototype = builtinTypes.type(u"NumberPrototype"_s).scope;
++ Q_ASSERT(m_numberPrototype);
++
++ m_arrayPrototype = builtinTypes.type(u"ArrayPrototype"_s).scope;
++ Q_ASSERT(m_arrayPrototype);
++
++ m_listPropertyType = m_qObjectType->listType();
++ Q_ASSERT(m_listPropertyType->internalName() == u"QQmlListProperty<QObject>"_s);
++ Q_ASSERT(m_listPropertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence);
++ Q_ASSERT(m_listPropertyType->elementTypeName() == u"QObject"_s);
++ assertExtension(m_listPropertyType, "Array"_L1);
++
++ QQmlJSScope::Ptr emptyType = QQmlJSScope::create();
++ emptyType->setAccessSemantics(QQmlJSScope::AccessSemantics::None);
++ m_emptyType = emptyType;
++
++ QQmlJSScope::Ptr jsPrimitiveType = QQmlJSScope::create();
++ jsPrimitiveType->setInternalName(u"QJSPrimitiveValue"_s);
++ jsPrimitiveType->setFilePath(u"qjsprimitivevalue.h"_s);
++ jsPrimitiveType->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
++ m_jsPrimitiveType = jsPrimitiveType;
++
++ QQmlJSScope::Ptr metaObjectType = QQmlJSScope::create();
++ metaObjectType->setInternalName(u"const QMetaObject"_s);
++ metaObjectType->setFilePath(u"qmetaobject.h"_s);
++ metaObjectType->setAccessSemantics(QQmlJSScope::AccessSemantics::Reference);
++ m_metaObjectType = metaObjectType;
++
++ m_jsGlobalObject = importer->jsGlobalObject();
++
++ QQmlJSScope::Ptr forInIteratorPtr = QQmlJSScope::create();
++ forInIteratorPtr->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
++ forInIteratorPtr->setFilePath(u"qjslist.h"_s);
++ forInIteratorPtr->setInternalName(u"QJSListForInIterator::Ptr"_s);
++ m_forInIteratorPtr = forInIteratorPtr;
++
++ QQmlJSScope::Ptr forOfIteratorPtr = QQmlJSScope::create();
++ forOfIteratorPtr->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
++ forOfIteratorPtr->setFilePath(u"qjslist.h"_s);
++ forOfIteratorPtr->setInternalName(u"QJSListForOfIterator::Ptr"_s);
++ m_forOfIteratorPtr = forOfIteratorPtr;
++
++ // We use this as scope type quite often, and it should always be the same scope type.
++ m_jsGlobalObjectContent = m_pool->createType(
++ m_jsGlobalObject, QQmlJSRegisterContent::InvalidLookupIndex,
++ QQmlJSRegisterContent::ScopeObject);
++ }
++
++ /*!
++ \internal
++
++ Initializes the type resolver. As part of that initialization, makes \a
++ visitor traverse the program when given.
++ */
++ void QQmlJSTypeResolver::init(QQmlJSImportVisitor *visitor, QQmlJS::AST::Node *program)
++ {
++ m_logger = visitor->logger();
++
++ m_objectsById.clear();
++ m_objectsByLocation.clear();
++ m_imports.clear();
++ m_signalHandlers.clear();
++ m_mergeCache.clear();
++
++ if (program)
++ program->accept(visitor);
++
++ m_objectsById = visitor->addressableScopes();
++ m_objectsByLocation = visitor->scopesBylocation();
++ m_signalHandlers = visitor->signalHandlers();
++ m_imports = visitor->imports();
++ m_seenModuleQualifiers = visitor->seenModuleQualifiers();
++ }
++
++ QQmlJSScope::ConstPtr QQmlJSTypeResolver::mathObject() const
++ {
++ return jsGlobalObject()->property(u"Math"_s).type();
++ }
++
++ QQmlJSScope::ConstPtr QQmlJSTypeResolver::consoleObject() const
++ {
++ return jsGlobalObject()->property(u"console"_s).type();
++ }
++
++ QQmlJSScope::ConstPtr
++ QQmlJSTypeResolver::scopeForLocation(const QV4::CompiledData::Location &location) const
++ {
++ // #if required for standalone DOM compilation against Qt 6.2
++ qCDebug(lcTypeResolver()).nospace()
++ << "looking for object at " << location.line() << ':' << location.column();
++ return m_objectsByLocation[location];
++ }
++
++ QQmlJSScope::ConstPtr QQmlJSTypeResolver::typeFromAST(QQmlJS::AST::Type *type) const
++ {
++ const QString typeId = QmlIR::IRBuilder::asString(type->typeId);
++ if (!type->typeArgument)
++ return m_imports.type(typeId).scope;
++ if (typeId == u"list"_s) {
++ if (const QQmlJSScope::ConstPtr typeArgument = typeForName(type->typeArgument->toString()))
++ return typeArgument->listType();
++ }
++ return QQmlJSScope::ConstPtr();
++ }
++
++ QQmlJSScope::ConstPtr QQmlJSTypeResolver::typeForConst(QV4::ReturnedValue rv) const
++ {
++ QV4::Value value = QV4::Value::fromReturnedValue(rv);
++ if (value.isUndefined())
++ return voidType();
++
++ if (value.isInt32())
++ return int32Type();
++
++ if (value.isBoolean())
++ return boolType();
++
++ if (value.isDouble())
++ return realType();
++
++ if (value.isNull())
++ return nullType();
++
++ if (value.isEmpty())
++ return emptyType();
++
++ return {};
++ }
++
++ QQmlJSRegisterContent
++ QQmlJSTypeResolver::typeForBinaryOperation(QSOperator::Op oper, QQmlJSRegisterContent left,
++ QQmlJSRegisterContent right) const
++ {
++ Q_ASSERT(left.isValid());
++ Q_ASSERT(right.isValid());
++
++ switch (oper) {
++ case QSOperator::Op::Equal:
++ case QSOperator::Op::NotEqual:
++ case QSOperator::Op::StrictEqual:
++ case QSOperator::Op::StrictNotEqual:
++ case QSOperator::Op::Lt:
++ case QSOperator::Op::Gt:
++ case QSOperator::Op::Ge:
++ case QSOperator::Op::In:
++ case QSOperator::Op::Le:
++ return operationType(boolType());
++ case QSOperator::Op::BitAnd:
++ case QSOperator::Op::BitOr:
++ case QSOperator::Op::BitXor:
++ case QSOperator::Op::LShift:
++ case QSOperator::Op::RShift:
++ return operationType(int32Type());
++ case QSOperator::Op::URShift:
++ return operationType(uint32Type());
++ case QSOperator::Op::Add: {
++ const auto leftContents = left.containedType();
++ const auto rightContents = right.containedType();
++ if (leftContents == stringType() || rightContents == stringType())
++ return operationType(stringType());
++
++ const QQmlJSScope::ConstPtr result = merge(leftContents, rightContents);
++ if (result == boolType())
++ return operationType(int32Type());
++ if (isNumeric(result))
++ return operationType(realType());
++
++ return operationType(jsPrimitiveType());
++ }
++ case QSOperator::Op::Sub:
++ case QSOperator::Op::Mul:
++ case QSOperator::Op::Exp: {
++ const QQmlJSScope::ConstPtr result = merge(left.containedType(), right.containedType());
++ return operationType(result == boolType() ? int32Type() : realType());
++ }
++ case QSOperator::Op::Div:
++ case QSOperator::Op::Mod:
++ return operationType(realType());
++ case QSOperator::Op::As:
++ return operationType(right.containedType());
++ default:
++ break;
++ }
++
++ return operationType(merge(left.containedType(), right.containedType()));
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::typeForArithmeticUnaryOperation(
++ UnaryOperator op, QQmlJSRegisterContent operand) const
++ {
++ switch (op) {
++ case UnaryOperator::Not:
++ return operationType(boolType());
++ case UnaryOperator::Complement:
++ return operationType(int32Type());
++ case UnaryOperator::Plus:
++ if (isIntegral(operand))
++ return operationType(operand.containedType());
++ Q_FALLTHROUGH();
++ default:
++ if (operand.containedType() == boolType())
++ return operationType(int32Type());
++ break;
++ }
++
++ return operationType(realType());
++ }
++
++ bool QQmlJSTypeResolver::isPrimitive(QQmlJSRegisterContent type) const
++ {
++ return isPrimitive(type.containedType());
++ }
++
++ bool QQmlJSTypeResolver::isNumeric(QQmlJSRegisterContent type) const
++ {
++ return isNumeric(type.containedType());
++ }
++
++ bool QQmlJSTypeResolver::isIntegral(QQmlJSRegisterContent type) const
++ {
++ return isIntegral(type.containedType());
++ }
++
++ bool QQmlJSTypeResolver::isIntegral(const QQmlJSScope::ConstPtr &type) const
++ {
++ return isSignedInteger(type) || isUnsignedInteger(type);
++ }
++
++ bool QQmlJSTypeResolver::isPrimitive(const QQmlJSScope::ConstPtr &type) const
++ {
++ return (isNumeric(type) && type != m_int64Type && type != m_uint64Type)
++ || type == m_boolType || type == m_voidType || type == m_nullType
++ || type == m_stringType || type == m_jsPrimitiveType;
++ }
++
++ bool QQmlJSTypeResolver::isNumeric(const QQmlJSScope::ConstPtr &type) const
++ {
++ if (!type) // should this be a precondition instead?
++ return false;
++
++ if (type->scopeType() == QQmlJSScope::ScopeType::EnumScope)
++ return true;
++
++ if (type == m_realType)
++ return true;
++ if (type == m_floatType)
++ return true;
++ if (type == m_int8Type)
++ return true;
++ if (type == m_uint8Type)
++ return true;
++ if (type == m_int16Type)
++ return true;
++ if (type == m_uint16Type)
++ return true;
++ if (type == m_int32Type)
++ return true;
++ if (type == m_uint32Type)
++ return true;
++ if (type == m_int64Type)
++ return true;
++ if (type == m_uint64Type)
++ return true;
++ // sizetype is covered by one of the above cases (int32 or int64)
++ // booleans are not numeric
++
++ // the number prototype is numeric
++ if (type == m_numberPrototype)
++ return true;
++
++ // and types directly inheriting from it, notably number, are also
++ // numeric
++ if (type->baseType() == m_numberPrototype)
++ return true;
++
++ // it isn't possible (for users) to derive from m_numberPrototpye,
++ // so we know the list above is exhaustive / we don't need to go up
++ // further in the inheritance chain
++
++ return false;
++ }
++
++ bool QQmlJSTypeResolver::isSignedInteger(const QQmlJSScope::ConstPtr &type) const
++ {
++ return type == m_int8Type || type == m_int16Type
++ || type == m_int32Type || type == m_int64Type;
++ }
++
++ bool QQmlJSTypeResolver::isUnsignedInteger(const QQmlJSScope::ConstPtr &type) const
++ {
++ return type == m_uint8Type || type == m_uint16Type
++ || type == m_uint32Type || type == m_uint64Type;
++ }
++
++ bool QQmlJSTypeResolver::isNativeArrayIndex(const QQmlJSScope::ConstPtr &type) const
++ {
++ return type == m_uint8Type || type == m_int8Type || type == m_uint16Type || type == m_int16Type
++ || type == m_uint32Type || type == m_int32Type;
++ }
++
++ QQmlJSScope::ConstPtr QQmlJSTypeResolver::containedTypeForName(const QString &name) const
++ {
++ QQmlJSScope::ConstPtr type = typeForName(name);
++
++ if (!type || type->isSingleton() || type->isScript())
++ return type;
++
++ switch (type->accessSemantics()) {
++ case QQmlJSScope::AccessSemantics::Reference:
++ if (const auto attached = type->attachedType())
++ return genericType(attached) ? attached : QQmlJSScope::ConstPtr();
++ return metaObjectType();
++ case QQmlJSScope::AccessSemantics::None:
++ return metaObjectType();
++ case QQmlJSScope::AccessSemantics::Sequence:
++ case QQmlJSScope::AccessSemantics::Value:
++ return canAddressValueTypes() ? metaObjectType() : QQmlJSScope::ConstPtr();
++ }
++
++ Q_UNREACHABLE_RETURN(QQmlJSScope::ConstPtr());
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::registerContentForName(
++ const QString &name, QQmlJSRegisterContent scopeType) const
++ {
++ QQmlJSScope::ConstPtr type = typeForName(name);
++ if (!type)
++ return QQmlJSRegisterContent();
++
++ if (type->isSingleton()) {
++ return m_pool->createType(
++ type, QQmlJSRegisterContent::InvalidLookupIndex,
++ QQmlJSRegisterContent::Singleton, scopeType);
++ }
++
++ if (type->isScript()) {
++ return m_pool->createType(
++ type, QQmlJSRegisterContent::InvalidLookupIndex,
++ QQmlJSRegisterContent::Script, scopeType);
++ }
++
++ const QQmlJSRegisterContent namedType = m_pool->createType(
++ type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::TypeByName,
++ scopeType);
++
++ if (const auto attached = type->attachedType()) {
++ if (!genericType(attached)) {
++ m_logger->log(u"Cannot resolve generic base of attached %1"_s.arg(
++ attached->internalName()),
++ qmlCompiler, attached->sourceLocation());
++ return {};
++ } else if (type->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
++ m_logger->log(u"Cannot retrieve attached object for non-reference type %1"_s.arg(
++ type->internalName()),
++ qmlCompiler, type->sourceLocation());
++ return {};
++ } else {
++ // We don't know yet whether we need the attached or the plain object. In direct
++ // mode, we will figure this out using the scope type and access any enums of the
++ // plain type directly. In indirect mode, we can use enum lookups.
++ return m_pool->createType(
++ attached, QQmlJSRegisterContent::InvalidLookupIndex,
++ QQmlJSRegisterContent::Attachment, namedType);
++ }
++ }
++
++ switch (type->accessSemantics()) {
++ case QQmlJSScope::AccessSemantics::None:
++ case QQmlJSScope::AccessSemantics::Reference:
++ // A plain reference to a non-singleton, non-attached type.
++ // We may still need the plain type reference for enum lookups,
++ // Store it as QMetaObject.
++ // This only works with namespaces and object types.
++ return m_pool->createType(
++ metaObjectType(), QQmlJSRegisterContent::InvalidLookupIndex,
++ QQmlJSRegisterContent::MetaType, namedType);
++ case QQmlJSScope::AccessSemantics::Sequence:
++ case QQmlJSScope::AccessSemantics::Value:
++ if (scopeType.isImportNamespace() || canAddressValueTypes()) {
++ return m_pool->createType(
++ metaObjectType(), QQmlJSRegisterContent::InvalidLookupIndex,
++ QQmlJSRegisterContent::MetaType, namedType);
++ }
++ // Else this is not actually a type reference. You cannot get the metaobject
++ // of a value type in QML and sequences don't even have metaobjects.
++ break;
++ }
++
++ return QQmlJSRegisterContent();
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::original(QQmlJSRegisterContent type) const
++ {
++ QQmlJSRegisterContent result = type.original();
++ return result.isNull() ? type : result;
++ }
++
++ QQmlJSScope::ConstPtr QQmlJSTypeResolver::originalContainedType(
++ QQmlJSRegisterContent container) const
++ {
++ return original(container).containedType();
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::shadowed(QQmlJSRegisterContent type) const
++ {
++ QQmlJSRegisterContent result = type.shadowed();
++ return result.isNull() ? type : result;
++ }
++
++ bool QQmlJSTypeResolver::adjustTrackedType(
++ QQmlJSRegisterContent tracked, const QQmlJSScope::ConstPtr &conversion) const
++ {
++ if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
++ return true;
++
++ QQmlJSScope::ConstPtr contained = tracked.containedType();
++ QQmlJSScope::ConstPtr result = conversion;
++
++ // Do not adjust to the JavaScript extension of the original type. Rather keep the original
++ // type in that case.
++ QQmlJSUtils::searchBaseAndExtensionTypes(
++ contained, [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind kind) {
++ if (kind != QQmlJSScope::ExtensionJavaScript || scope != result)
++ return false;
++ result = contained;
++ return true;
++ });
++
++
++ // If we cannot convert to the new type without the help of e.g. lookupResultMetaType(),
++ // we better not change the type.
++ if (!canPrimitivelyConvertFromTo(contained, result)
++ && !canPopulate(result, contained, nullptr)
++ && !selectConstructor(result, contained, nullptr).isValid()) {
++ return false;
++ }
++
++ m_pool->adjustType(tracked, result);
++ return true;
++ }
++
++ bool QQmlJSTypeResolver::adjustTrackedType(
++ QQmlJSRegisterContent tracked, QQmlJSRegisterContent conversion) const
++ {
++ return adjustTrackedType(tracked, conversion.containedType());
++ }
++
++ bool QQmlJSTypeResolver::adjustTrackedType(
++ QQmlJSRegisterContent tracked, const QList<QQmlJSRegisterContent> &conversions) const
++ {
++ if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
++ return true;
++
++ QQmlJSScope::ConstPtr result;
++ for (QQmlJSRegisterContent type : conversions)
++ result = merge(type.containedType(), result);
++
++ return adjustTrackedType(tracked, result);
++ }
++
++ void QQmlJSTypeResolver::adjustOriginalType(
++ QQmlJSRegisterContent tracked, const QQmlJSScope::ConstPtr &conversion) const
++ {
++ if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
++ return;
++
++ m_pool->generalizeType(tracked, conversion);
++ }
++
++ void QQmlJSTypeResolver::generalizeType(QQmlJSRegisterContent type) const
++ {
++ if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
++ return;
++
++ for (QQmlJSRegisterContent orig = type; !orig.isNull(); orig = orig.original()) {
++ if (!orig.shadowed().isValid())
++ m_pool->generalizeType(orig, genericType(orig.containedType()));
++ }
++ }
++
++ bool QQmlJSTypeResolver::canConvertFromTo(const QQmlJSScope::ConstPtr &from,
++ const QQmlJSScope::ConstPtr &to) const
++ {
++ if (canPrimitivelyConvertFromTo(from, to)
++ || canPopulate(to, from, nullptr)
++ || selectConstructor(to, from, nullptr).isValid()) {
++ return true;
++ }
++
++ // ### need a generic solution for custom cpp types:
++ // if (from->m_hasBoolOverload && equals(to, boolType))
++ // return true;
++
++ // All of these types have QString conversions that require a certain format
++ // TODO: Actually verify these strings or deprecate them.
++ // Some of those type are builtins or should be builtins. We should add code for them
++ // in QQmlJSCodeGenerator::conversion().
++ if (from == m_stringType && !to.isNull()) {
++ const QString toTypeName = to->internalName();
++ if (toTypeName == u"QPoint"_s || toTypeName == u"QPointF"_s
++ || toTypeName == u"QSize"_s || toTypeName == u"QSizeF"_s
++ || toTypeName == u"QRect"_s || toTypeName == u"QRectF"_s) {
++ return true;
++ }
++ }
++
++ return false;
++ }
++
++ bool QQmlJSTypeResolver::canConvertFromTo(QQmlJSRegisterContent from,
++ QQmlJSRegisterContent to) const
++ {
++ return canConvertFromTo(from.containedType(), to.containedType());
++ }
++
++ static QQmlJSRegisterContent::ContentVariant mergeVariants(QQmlJSRegisterContent::ContentVariant a,
++ QQmlJSRegisterContent::ContentVariant b)
++ {
++ return (a == b) ? a : QQmlJSRegisterContent::Unknown;
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::merge(
++ QQmlJSRegisterContent a, QQmlJSRegisterContent b) const
++ {
++ Q_ASSERT(a != b);
++
++ // Both mergeScopes() calls below recurse into this function. See m_mergeCache.
++ const RegisterPair key(a, b);
++ if (const auto it = m_mergeCache.constFind(key); it != m_mergeCache.constEnd())
++ return m_pool->clone(*it);
++
++ // We cannot easily provide an operator< for QQmlJSRegisterContent.
++ // Therefore we use qHash and operator== here to deduplicate. That's somewhat inefficient.
++ QSet<QQmlJSRegisterContent> origins;
++
++ QQmlJSRegisterContent aResultScope;
++ if (a.isConversion()) {
++ const auto &aOrigins = a.conversionOrigins();
++ for (const auto &aOrigin : aOrigins)
++ origins.insert(aOrigin);
++ aResultScope = a.conversionResultScope();
++ } else {
++ origins.insert(a);
++ aResultScope = a.scope();
++ }
++
++ QQmlJSRegisterContent bResultScope;
++ if (b.isConversion()) {
++ const auto &bOrigins = b.conversionOrigins();
++ for (const auto &bOrigin : bOrigins)
++ origins.insert(bOrigin);
++ bResultScope = b.conversionResultScope();
++ } else {
++ origins.insert(b);
++ bResultScope = b.scope();
++ }
++
++ const auto mergeScopes = [&](QQmlJSRegisterContent a, QQmlJSRegisterContent b) {
++ // If they are the same, we don't want to re-track them.
++ // We want the repetition on type mismatches to converge.
++ return (a == b) ? a : merge(a, b);
++ };
++
++ const QQmlJSRegisterContent merged = m_pool->createConversion(
++ origins.values(),
++ merge(a.containedType(), b.containedType()),
++ mergeScopes(aResultScope, bResultScope),
++ mergeVariants(a.variant(), b.variant()),
++ mergeScopes(a.scope(), b.scope()));
++
++ m_mergeCache.insert(key, m_pool->clone(merged));
++ return merged;
++ }
++
++ QQmlJSScope::ConstPtr QQmlJSTypeResolver::merge(const QQmlJSScope::ConstPtr &a,
++ const QQmlJSScope::ConstPtr &b) const
++ {
++ if (a.isNull())
++ return b;
++
++ if (b.isNull())
++ return a;
++
++ const auto baseOrExtension
++ = [](const QQmlJSScope::ConstPtr &a, const QQmlJSScope::ConstPtr &b) {
++ QQmlJSScope::ConstPtr found;
++ QQmlJSUtils::searchBaseAndExtensionTypes(
++ a, [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind kind) {
++ switch (kind) {
++ case QQmlJSScope::NotExtension:
++ // If b inherits scope, then scope is a common base type of a and b
++ if (b->inherits(scope)) {
++ found = scope;
++ return true;
++ }
++ break;
++ case QQmlJSScope::ExtensionJavaScript:
++ // Merging a type with its JavaScript extension produces the type.
++ // Giving the JavaScript extension as type to be read means we expect any type
++ // that fulfills the given JavaScript interface
++ if (scope == b) {
++ found = a;
++ return true;
++ }
++ break;
++ case QQmlJSScope::ExtensionType:
++ case QQmlJSScope::ExtensionNamespace:
++ break;
++ }
++ return false;
++ });
++ return found;
++ };
++
++ if (a == b)
++ return a;
++
++ if (a == jsValueType() || a == varType())
++ return a;
++ if (b == jsValueType() || b == varType())
++ return b;
++
++ const auto isInt32Compatible = [&](const QQmlJSScope::ConstPtr &type) {
++ return (isIntegral(type)
++ && type != uint32Type()
++ && type != int64Type()
++ && type != uint64Type())
++ || type == boolType();
++ };
++
++ if (isInt32Compatible(a) && isInt32Compatible(b))
++ return int32Type();
++
++ const auto isUInt32Compatible = [&](const QQmlJSScope::ConstPtr &type) {
++ return (isUnsignedInteger(type) && type != uint64Type()) || type == boolType();
++ };
++
++ if (isUInt32Compatible(a) && isUInt32Compatible(b))
++ return uint32Type();
++
++ if (isNumeric(a) && isNumeric(b))
++ return realType();
++
++ if (isPrimitive(a) && isPrimitive(b))
++ return jsPrimitiveType();
++
++ if (const auto base = baseOrExtension(a, b))
++ return base;
++
++ if (const auto base = baseOrExtension(b, a))
++ return base;
++
++ if ((a == nullType() || a == boolType()) && b->isReferenceType())
++ return b;
++
++ if ((b == nullType() || b == boolType()) && a->isReferenceType())
++ return a;
++
++ return varType();
++ }
++
++ bool QQmlJSTypeResolver::canHold(
++ const QQmlJSScope::ConstPtr &container, const QQmlJSScope::ConstPtr &contained) const
++ {
++ if (container == contained || container == m_varType || container == m_jsValueType)
++ return true;
++
++ if (container == m_jsPrimitiveType)
++ return isPrimitive(contained);
++
++ if (container == m_variantListType)
++ return contained->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence;
++
++ if (container == m_qObjectListType || container == m_listPropertyType) {
++ if (contained->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence)
++ return false;
++ if (QQmlJSScope::ConstPtr element = contained->elementType())
++ return element->isReferenceType();
++ return false;
++ }
++
++ if (QQmlJSUtils::searchBaseAndExtensionTypes(
++ container, [&](const QQmlJSScope::ConstPtr &base) {
++ return base == contained;
++ })) {
++ return true;
++ }
++
++ if (container->isReferenceType()) {
++ if (QQmlJSUtils::searchBaseAndExtensionTypes(
++ contained, [&](const QQmlJSScope::ConstPtr &base) {
++ return base == container;
++ })) {
++ return true;
++ }
++ }
++
++ return false;
++ }
++
++
++ bool QQmlJSTypeResolver::canHoldUndefined(QQmlJSRegisterContent content) const
++ {
++ const auto canBeUndefined = [this](const QQmlJSScope::ConstPtr &type) {
++ return type == m_voidType || type == m_varType
++ || type == m_jsValueType || type == m_jsPrimitiveType;
++ };
++
++ if (!canBeUndefined(content.containedType()))
++ return false;
++
++ if (!content.isConversion())
++ return true;
++
++ const auto &origins = content.conversionOrigins();
++ for (const auto &origin : origins) {
++ if (canBeUndefined(originalContainedType(origin)))
++ return true;
++ }
++
++ return false;
++ }
++
++ bool QQmlJSTypeResolver::isOptionalType(QQmlJSRegisterContent content) const
++ {
++ if (!content.isConversion())
++ return false;
++
++ const auto &origins = content.conversionOrigins();
++ if (origins.length() != 2)
++ return false;
++
++ // Conversion origins are always adjusted to the conversion result. None of them will be void.
++ // Therefore, retrieve the originals first.
++
++ return original(origins[0]).contains(m_voidType) || original(origins[1]).contains(m_voidType);
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::extractNonVoidFromOptionalType(
++ QQmlJSRegisterContent content) const
++ {
++ if (!isOptionalType(content))
++ return QQmlJSRegisterContent();
++
++ // Conversion origins are always adjusted to the conversion result. None of them will be void.
++ // Therefore, retrieve the originals first.
++
++ QList<QQmlJSRegisterContent> origins(content.conversionOrigins());
++ std::transform(origins.cbegin(), origins.cend(), origins.begin(),
++ [this](QQmlJSRegisterContent content) {return original(content);});
++ const auto &constOrigins = origins;
++ const QQmlJSRegisterContent result = constOrigins[0].contains(m_voidType)
++ ? constOrigins[1]
++ : constOrigins[0];
++
++ // The result may still be undefined. You can write "undefined ?? undefined ?? 1"
++ return result;
++ }
++
++ QQmlJSScope::ConstPtr QQmlJSTypeResolver::genericType(
++ const QQmlJSScope::ConstPtr &type,
++ ComponentIsGeneric allowComponent) const
++ {
++ if (type->isScript())
++ return m_jsValueType;
++
++ if (type == m_metaObjectType)
++ return m_metaObjectType;
++
++ if (type->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
++ QString unresolvedBaseTypeName;
++ for (auto base = type; base;) {
++ // QObject and QQmlComponent are the two required base types.
++ // Any QML type system has to define those, or use the ones from builtins.
++ // As QQmlComponent is derived from QObject, we can restrict ourselves to the latter.
++ // This results in less if'ery when retrieving a QObject* from somewhere and deciding
++ // what it is.
++ if (base->internalName() == u"QObject"_s) {
++ return base;
++ } else if (allowComponent == ComponentIsGeneric::Yes
++ && base->internalName() == u"QQmlComponent"_s) {
++ return base;
++ }
++
++ if (auto baseBase = base->baseType()) {
++ base = baseBase;
++ } else {
++ unresolvedBaseTypeName = base->baseTypeName();
++ break;
++ }
++ }
++
++ // Reference types that are not QObject or QQmlComponent are likely JavaScript objects.
++ // We don't want to deal with those, but m_jsValueType is the best generic option.
++ if (type->filePath().isEmpty())
++ return m_jsValueType;
++
++ m_logger->log(u"Object type %1 is not derived from QObject or QQmlComponent. "
++ "You may need to fully qualify all names in C++ so that moc can see them. "
++ "You may also need to add qt_extract_metatypes(<target containing %2>)."_s
++ .arg(type->internalName(), unresolvedBaseTypeName),
++ qmlCompiler, type->sourceLocation());
++
++ // If it does have a filePath, it's some C++ type which we haven't fully resolved.
++ return m_jsValueType;
++ }
++
++ if (type->isListProperty())
++ return m_listPropertyType;
++
++ if (type->scopeType() == QQmlSA::ScopeType::EnumScope)
++ return type->baseType();
++
++ if (isPrimitive(type)) {
++ // If the filePath is set, the type is storable and we can just return it.
++ if (!type->filePath().isEmpty())
++ return type;
++
++ // If the type is JavaScript's 'number' type, it's not directly storable, but still
++ // primitive. We use C++ 'double' then.
++ if (isNumeric(type))
++ return m_realType;
++
++ // Otherwise we use QJSPrimitiveValue.
++ // TODO: JavaScript's 'string' and 'boolean' could be special-cased here.
++ return m_jsPrimitiveType;
++ }
++
++ for (const QQmlJSScope::ConstPtr &builtin : {
++ m_realType, m_floatType, m_int8Type, m_uint8Type, m_int16Type, m_uint16Type,
++ m_int32Type, m_uint32Type, m_int64Type, m_uint64Type, m_boolType, m_stringType,
++ m_stringListType, m_byteArrayType, m_urlType, m_dateTimeType, m_dateType,
++ m_timeType, m_variantListType, m_variantMapType, m_varType, m_jsValueType,
++ m_jsPrimitiveType, m_listPropertyType, m_qObjectType, m_qObjectListType,
++ m_metaObjectType, m_forInIteratorPtr, m_forOfIteratorPtr }) {
++ if (type == builtin || type == builtin->listType())
++ return type;
++ }
++
++ return m_varType;
++ }
++
++ /*!
++ * \internal
++ * The type of a JavaScript literal value appearing in script code
++ */
++ QQmlJSRegisterContent QQmlJSTypeResolver::literalType(const QQmlJSScope::ConstPtr &type) const
++ {
++ return m_pool->createType(
++ type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Literal);
++ }
++
++ /*!
++ * \internal
++ * The type of the result of a JavaScript operation
++ */
++ QQmlJSRegisterContent QQmlJSTypeResolver::operationType(const QQmlJSScope::ConstPtr &type) const
++ {
++ return m_pool->createType(
++ type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Operation);
++ }
++
++ /*!
++ * \internal
++ * A type named explicitly, for example in "as"-casts or as function annotation.
++ */
++ QQmlJSRegisterContent QQmlJSTypeResolver::namedType(const QQmlJSScope::ConstPtr &type) const
++ {
++ return m_pool->createType(
++ type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::TypeByName);
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::syntheticType(const QQmlJSScope::ConstPtr &type) const
++ {
++ return m_pool->createType(
++ type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Unknown);
++ }
++
++ static QQmlJSRegisterContent::ContentVariant scopeContentVariant(QQmlJSScope::ExtensionKind mode,
++ bool isMethod)
++ {
++ switch (mode) {
++ case QQmlJSScope::NotExtension:
++ case QQmlJSScope::ExtensionType:
++ case QQmlJSScope::ExtensionJavaScript:
++ return isMethod
++ ? QQmlJSRegisterContent::Method
++ : QQmlJSRegisterContent::Property;
++ case QQmlJSScope::ExtensionNamespace:
++ break;
++ }
++ Q_UNREACHABLE_RETURN(QQmlJSRegisterContent::Unknown);
++ }
++
++ static bool isRevisionAllowed(int memberRevision, const QQmlJSScope::ConstPtr &scope)
++ {
++ Q_ASSERT(scope->isComposite());
++ const QTypeRevision revision = QTypeRevision::fromEncodedVersion(memberRevision);
++
++ // If the memberRevision is either invalid or 0.0, then everything is allowed.
++ if (!revision.isValid() || revision == QTypeRevision::zero())
++ return true;
++
++ const QTypeRevision typeRevision = QQmlJSScope::nonCompositeBaseRevision(
++ {scope->baseType(), scope->baseTypeRevision()});
++
++ // If the revision is not valid, we haven't found a non-composite base type.
++ // There is nothing we can say about the property then.
++ return typeRevision.isValid() && typeRevision >= revision;
++ }
++
++ QQmlJSScope::ConstPtr QQmlJSTypeResolver::resolveParentProperty(
++ const QString &name, const QQmlJSScope::ConstPtr &base,
++ const QQmlJSScope::ConstPtr &propType) const
++ {
++ if (m_parentMode != UseDocumentParent || name != base->parentPropertyName())
++ return propType;
++
++ const QQmlJSScope::ConstPtr baseParent = base->parentScope();
++ if (!baseParent || !baseParent->inherits(propType))
++ return propType;
++
++ const QString defaultPropertyName = baseParent->defaultPropertyName();
++ if (defaultPropertyName.isEmpty()) // no reason to search for bindings
++ return propType;
++
++ const QList<QQmlJSMetaPropertyBinding> defaultPropBindings
++ = baseParent->propertyBindings(defaultPropertyName);
++ for (const QQmlJSMetaPropertyBinding &binding : defaultPropBindings) {
++ if (binding.bindingType() == QQmlSA::BindingType::Object && binding.objectType() == base)
++ return baseParent;
++ }
++
++ return propType;
++ }
++
++ /*!
++ * \internal
++ *
++ * Retrieves the type of whatever \a name signifies in the given \a scope.
++ * \a name can be an ID, a property of the scope, a singleton, an attachment,
++ * a plain type reference or a JavaScript global.
++ *
++ * TODO: The lookup is actually wrong. We cannot really retrieve JavaScript
++ * globals here because any runtime-inserted context property would
++ * override them. We still do because we don't have a better solution for
++ * identifying e.g. the console object, yet.
++ *
++ * \a options tells us whether to consider components as bound. If components
++ * are bound we can retrieve objects identified by ID in outer contexts.
++ *
++ * TODO: This is also wrong because we should alternate scopes and contexts when
++ * traveling the scope/context hierarchy. Currently we have IDs from any
++ * context override all scope properties if components are considered
++ * bound. This is mostly because we don't care about outer scopes at all;
++ * so we cannot determine with certainty whether an ID from a far outer
++ * context is overridden by a property of a near outer scope. To
++ * complicate this further, user context properties can also be inserted
++ * in outer contexts at run time, shadowing names in further removed outer
++ * scopes and contexts. What we need to do is determine where exactly what
++ * kind of property can show up and defend against that with additional
++ * pragmas.
++ *
++ * Note: It probably takes at least 3 nested bound components in one document to
++ * trigger the misbehavior.
++ */
++ QQmlJSScope::ConstPtr QQmlJSTypeResolver::scopedType(
++ const QQmlJSScope::ConstPtr &scope, const QString &name,
++ QQmlJSScopesByIdOptions options) const
++ {
++ QQmlJSScopesById::CertainCallback<QQmlJSScope::ConstPtr> identified;
++ if (m_objectsById.possibleScopes(name, scope, options, identified)
++ != QQmlJSScopesById::Success::Yes) {
++ // Could not determine component boundaries
++ return {};
++ }
++
++ if (identified.result) {
++ // Found a definite match
++ return identified.result;
++ }
++
++ if (QQmlJSScope::ConstPtr base = QQmlJSScope::findCurrentQMLScope(scope)) {
++ QQmlJSScope::ConstPtr result;
++ if (QQmlJSUtils::searchBaseAndExtensionTypes(
++ base, [&](const QQmlJSScope::ConstPtr &found, QQmlJSScope::ExtensionKind mode) {
++ if (mode == QQmlJSScope::ExtensionNamespace) // no use for it here
++ return false;
++
++ if (found->hasOwnProperty(name)) {
++ const QQmlJSMetaProperty prop = found->ownProperty(name);
++ if (!isRevisionAllowed(prop.revision(), scope))
++ return false;
++
++ result = resolveParentProperty(name, base, prop.type());
++ return true;
++ }
++
++ if (found->hasOwnMethod(name)) {
++ const auto methods = found->ownMethods(name);
++ for (const auto &method : methods) {
++ if (isRevisionAllowed(method.revision(), scope)) {
++ result = jsValueType();
++ return true;
++ }
++ }
++ }
++
++ return false;
++ })) {
++ return result;
++ }
++ }
++
++ if (QQmlJSScope::ConstPtr result = containedTypeForName(name))
++ return result;
++
++ if (m_jsGlobalObject->hasProperty(name))
++ return m_jsGlobalObject->property(name).type();
++
++ if (m_jsGlobalObject->hasMethod(name))
++ return jsValueType();
++
++ return {};
++ }
++
++ /*!
++ * \internal
++ *
++ * Same as the other scopedType method, but accepts a QQmlJSRegisterContent and
++ * also returns one. This way you not only get the type, but also the content
++ * variant and various meta info.
++ */
++ QQmlJSRegisterContent QQmlJSTypeResolver::scopedType(QQmlJSRegisterContent scope,
++ const QString &name, int lookupIndex,
++ QQmlJSScopesByIdOptions options) const
++ {
++ const QQmlJSScope::ConstPtr contained = scope.containedType();
++
++ QQmlJSScopesById::CertainCallback<QQmlJSScope::ConstPtr> identified;
++ if (m_objectsById.possibleScopes(name, contained, options, identified)
++ != QQmlJSScopesById::Success::Yes) {
++ // Could not determine component boundaries
++ return {};
++ }
++
++ if (identified.result) {
++ // Found a definite match
++ return m_pool->createType(
++ identified.result, lookupIndex, QQmlJSRegisterContent::ObjectById, scope);
++ }
++
++ if (QQmlJSScope::ConstPtr base = QQmlJSScope::findCurrentQMLScope(contained)) {
++ QQmlJSRegisterContent result;
++ if (QQmlJSUtils::searchBaseAndExtensionTypes(
++ base, [&](const QQmlJSScope::ConstPtr &found, QQmlJSScope::ExtensionKind mode) {
++ if (mode == QQmlJSScope::ExtensionNamespace) // no use for it here
++ return false;
++
++ const QQmlJSRegisterContent resultScope = mode == QQmlJSScope::NotExtension
++ ? scope
++ : extensionType(found, scope);
++
++ if (found->hasOwnProperty(name)) {
++ QQmlJSMetaProperty prop = found->ownProperty(name);
++ if (!isRevisionAllowed(prop.revision(), contained))
++ return false;
++
++ prop.setType(resolveParentProperty(name, base, prop.type()));
++ result = m_pool->createProperty(
++ prop, QQmlJSRegisterContent::InvalidLookupIndex, lookupIndex,
++ scopeContentVariant(mode, false), resultScope);
++ return true;
++ }
++
++ if (found->hasOwnMethod(name)) {
++ auto methods = found->ownMethods(name);
++ for (auto it = methods.begin(); it != methods.end();) {
++ if (!isRevisionAllowed(it->revision(), contained))
++ it = methods.erase(it);
++ else
++ ++it;
++ }
++ if (methods.isEmpty())
++ return false;
++ result = m_pool->createMethod(
++ methods, jsValueType(), scopeContentVariant(mode, true), resultScope);
++ return true;
++ }
++
++ // Unqualified enums are not allowed
++ return false;
++ })) {
++ return result;
++ }
++ }
++
++ QQmlJSRegisterContent result = registerContentForName(name, scope);
++
++ if (result.isValid())
++ return result;
++
++ if (m_jsGlobalObject->hasProperty(name)) {
++ return m_pool->createProperty(
++ m_jsGlobalObject->property(name), QQmlJSRegisterContent::InvalidLookupIndex,
++ lookupIndex, QQmlJSRegisterContent::Property, m_jsGlobalObjectContent);
++ } else if (m_jsGlobalObject->hasMethod(name)) {
++ return m_pool->createMethod(
++ m_jsGlobalObject->methods(name), jsValueType(),
++ QQmlJSRegisterContent::Property, m_jsGlobalObjectContent);
++ }
++
++ return {};
++ }
++
++ /*!
++ * \fn QQmlJSScope::ConstPtr typeForId(const QQmlJSScope::ConstPtr &scope, const QString &name, QQmlJSScopesByIdOptions options) const
++ *
++ * \internal
++ *
++ * Same as scopedType(), but assumes that the \a name is an ID and only searches
++ * the context.
++ *
++ * TODO: This is just as wrong as scopedType() in that it disregards both scope
++ * properties overriding context properties and run time context
++ * properties.
++ */
++
++ bool QQmlJSTypeResolver::checkEnums(
++ QQmlJSRegisterContent scope, const QString &name,
++ QQmlJSRegisterContent *result) const
++ {
++ // You can't have lower case enum names in QML, even if we know the enums here.
++ if (name.isEmpty() || !name.at(0).isUpper())
++ return false;
++
++ const auto enums = scope.containedType()->ownEnumerations();
++ for (const auto &enumeration : enums) {
++ if (enumeration.name() == name) {
++ *result = m_pool->createEnumeration(
++ enumeration, QString(),
++ QQmlJSRegisterContent::Enum,
++ scope);
++ return true;
++ }
++
++ if (!(scope.containedType()->enforcesScopedEnums() && enumeration.isScoped())
++ && enumeration.hasKey(name)) {
++ *result = m_pool->createEnumeration(
++ enumeration, name,
++ QQmlJSRegisterContent::Enum,
++ scope);
++ return true;
++ }
++ }
++
++ return false;
++ }
++
++ bool QQmlJSTypeResolver::canPopulate(
++ const QQmlJSScope::ConstPtr &type, const QQmlJSScope::ConstPtr &passedArgumentType,
++ bool *isExtension) const
++ {
++ // TODO: We could allow QVariantMap and QVariantHash to be populated, but that needs extra
++ // code in the code generator.
++
++ if (type.isNull()
++ || canHold(passedArgumentType, type)
++ || isPrimitive(passedArgumentType)
++ || type->accessSemantics() != QQmlJSScope::AccessSemantics::Value
++ || !type->isStructured()) {
++ return false;
++ }
++
++ if (isExtension)
++ *isExtension = !type->extensionType().scope.isNull();
++
++ return true;
++ }
++
++ QQmlJSMetaMethod QQmlJSTypeResolver::selectConstructor(
++ const QQmlJSScope::ConstPtr &type, const QQmlJSScope::ConstPtr &passedArgumentType,
++ bool *isExtension) const
++ {
++ // If the "from" type can hold the target type, we should not try to coerce
++ // it to any constructor argument.
++ if (type.isNull()
++ || canHold(passedArgumentType, type)
++ || type->accessSemantics() != QQmlJSScope::AccessSemantics::Value
++ || !type->isCreatable()) {
++ return QQmlJSMetaMethod();
++ }
++
++ auto doSelectConstructor = [&](const QQmlJSScope::ConstPtr &ctorProvider) {
++ QQmlJSMetaMethod candidate;
++
++ const auto ownMethods = ctorProvider->ownMethods();
++ for (const QQmlJSMetaMethod &method : ownMethods) {
++ if (!method.isConstructor())
++ continue;
++
++ const auto index = method.constructorIndex();
++ Q_ASSERT(index != QQmlJSMetaMethod::RelativeFunctionIndex::Invalid);
++
++ const auto methodArguments = method.parameters();
++ if (methodArguments.size() != 1)
++ continue;
++
++ const QQmlJSScope::ConstPtr methodArgumentType = methodArguments[0].type();
++
++ // Do not select the copy constructor (also not if disguised via the extension).
++ if (methodArgumentType == type)
++ continue;
++
++ if (passedArgumentType == methodArgumentType)
++ return method;
++
++ // Do not select further ctors here. We don't want to do multi-step construction as that
++ // is confusing and easily leads to infinite recursion.
++ if (!candidate.isValid()
++ && canPrimitivelyConvertFromTo(passedArgumentType, methodArgumentType)) {
++ candidate = method;
++ }
++ }
++
++ return candidate;
++ };
++
++ if (QQmlJSScope::ConstPtr extension = type->extensionType().scope) {
++ const QQmlJSMetaMethod ctor = doSelectConstructor(extension);
++ if (ctor.isValid()) {
++ if (isExtension)
++ *isExtension = true;
++ return ctor;
++ }
++ }
++
++ if (isExtension)
++ *isExtension = false;
++
++ return doSelectConstructor(type);
++ }
++
++ bool QQmlJSTypeResolver::areEquivalentLists(
++ const QQmlJSScope::ConstPtr &a, const QQmlJSScope::ConstPtr &b) const
++ {
++ const QQmlJSScope::ConstPtr equivalentLists[2][2] = {
++ { m_stringListType, m_stringType->listType() },
++ { m_variantListType, m_varType->listType() }
++ };
++
++ for (const auto eq : equivalentLists) {
++ if ((a == eq[0] && b == eq[1]) || (a == eq[1] && b == eq[0]))
++ return true;
++ }
++
++ return false;
++ }
++
++ bool QQmlJSTypeResolver::isTriviallyCopyable(const QQmlJSScope::ConstPtr &type) const
++ {
++ // pointers are trivially copyable
++ if (type->isReferenceType())
++ return true;
++
++ // Enum values are trivially copyable
++ if (type->scopeType() == QQmlSA::ScopeType::EnumScope)
++ return true;
++
++ for (const QQmlJSScope::ConstPtr &trivial : {
++ m_nullType, m_voidType,
++ m_boolType, m_metaObjectType,
++ m_realType, m_floatType,
++ m_int8Type, m_uint8Type,
++ m_int16Type, m_uint16Type,
++ m_int32Type, m_uint32Type,
++ m_int64Type, m_uint64Type }) {
++ if (type == trivial)
++ return true;
++ }
++
++ return false;
++ }
++
++ bool QQmlJSTypeResolver::inherits(const QQmlJSScope::ConstPtr &derived, const QQmlJSScope::ConstPtr &base) const
++ {
++ const bool matchByName = !base->isComposite();
++ for (QQmlJSScope::ConstPtr derivedBase = derived; derivedBase;
++ derivedBase = derivedBase->baseType()) {
++ if (derivedBase == base)
++ return true;
++ if (matchByName
++ && !derivedBase->isComposite()
++ && derivedBase->internalName() == base->internalName()) {
++ return true;
++ }
++ }
++ return false;
++ }
++
++ bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo(
++ const QQmlJSScope::ConstPtr &from, const QQmlJSScope::ConstPtr &to) const
++ {
++ if (from == to)
++ return true;
++ if (from == m_varType || to == m_varType)
++ return true;
++ if (from == m_jsValueType || to == m_jsValueType)
++ return true;
++ if (to == m_qQmlScriptStringType)
++ return true;
++ if (isNumeric(from) && isNumeric(to))
++ return true;
++ // We can convert everything to bool.
++ if (to == m_boolType)
++ return true;
++
++ if (from->accessSemantics() == QQmlJSScope::AccessSemantics::Reference
++ && to == m_stringType) {
++ return true;
++ }
++
++ // Yes, our String has number constructors.
++ if (isNumeric(from) && to == m_stringType)
++ return true;
++
++ // We can convert strings to numbers, but not to enums
++ if (from == m_stringType && isNumeric(to))
++ return to->scopeType() != QQmlJSScope::ScopeType::EnumScope;
++
++ // We can always convert between strings and urls.
++ if ((from == m_stringType && to == m_urlType)
++ || (from == m_urlType && to == m_stringType)) {
++ return true;
++ }
++
++ // We can always convert between strings and byte arrays.
++ if ((from == m_stringType && to == m_byteArrayType)
++ || (from == m_byteArrayType && to == m_stringType)) {
++ return true;
++ }
++
++ if (to == m_voidType)
++ return true;
++
++ if (to.isNull())
++ return from == m_voidType;
++
++ const auto types = { m_dateTimeType, m_dateType, m_timeType, m_stringType };
++ for (const auto &originType : types) {
++ if (from != originType)
++ continue;
++
++ for (const auto &targetType : types) {
++ if (to == targetType)
++ return true;
++ }
++
++ if (to == m_realType)
++ return true;
++
++ break;
++ }
++
++ if (from == m_nullType && to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
++ return true;
++
++ if (from == m_jsPrimitiveType) {
++ // You can cast any primitive to a nullptr
++ return isPrimitive(to) || to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference;
++ }
++
++ if (to == m_jsPrimitiveType)
++ return isPrimitive(from);
++
++ const bool matchByName = !to->isComposite();
++ Q_ASSERT(!matchByName || !to->internalName().isEmpty());
++ if (QQmlJSUtils::searchBaseAndExtensionTypes(
++ from, [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind kind) {
++ switch (kind) {
++ case QQmlJSScope::NotExtension:
++ case QQmlJSScope::ExtensionJavaScript:
++ // Converting to a base type is trivially supported.
++ // Converting to the JavaScript extension of a type just produces the type itself.
++ // Giving the JavaScript extension as type to be converted to means we expect any
++ // result that fulfills the given JavaScript interface.
++ return scope == to
++ || (matchByName && scope->internalName() == to->internalName());
++ case QQmlJSScope::ExtensionType:
++ case QQmlJSScope::ExtensionNamespace:
++ break;
++ }
++ return false;
++ })) {
++ return true;
++ }
++
++ if (from == m_variantListType)
++ return to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence;
++
++ // We can convert anything that fits natively into QJSPrimitiveValue
++ if (canPrimitivelyConvertFromTo(from, m_jsPrimitiveType)
++ && canPrimitivelyConvertFromTo(m_jsPrimitiveType, to)) {
++ return true;
++ }
++
++ if (areEquivalentLists(from, to))
++ return true;
++
++ if (from->isListProperty()
++ && to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
++ && canConvertFromTo(from->elementType(), to->elementType())) {
++ return true;
++ }
++
++ // it is possible to assing a singlar object to a list property if it could be stored in the list
++ if (to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
++ && from->accessSemantics() == QQmlJSScope::AccessSemantics::Reference
++ && from->inherits(to->elementType())) {
++ return true;
++ }
++
++ if (to == m_stringType && from->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence)
++ return canConvertFromTo(from->elementType(), m_stringType);
++
++ return false;
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::lengthProperty(
++ bool isWritable, QQmlJSRegisterContent scope) const
++ {
++ QQmlJSMetaProperty prop;
++ prop.setPropertyName(u"length"_s);
++ prop.setTypeName(u"qsizetype"_s);
++ prop.setType(sizeType());
++ prop.setIsWritable(isWritable);
++ return m_pool->createProperty(
++ prop, QQmlJSRegisterContent::InvalidLookupIndex,
++ QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Property, scope);
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(
++ QQmlJSRegisterContent type, const QString &name, int baseLookupIndex,
++ int resultLookupIndex) const
++ {
++ QQmlJSRegisterContent result;
++ const QQmlJSScope::ConstPtr contained = type.containedType();
++
++ // If we got a plain type reference we have to check the enums of the _scope_.
++ if (contained == metaObjectType())
++ return memberEnumType(type.scope(), name);
++
++ if (contained == variantMapType() || contained->inherits(qmlPropertyMapType())) {
++ QQmlJSMetaProperty prop;
++ prop.setPropertyName(name);
++ prop.setTypeName(u"QVariant"_s);
++ prop.setType(varType());
++ prop.setIsWritable(true);
++ return m_pool->createProperty(
++ prop, baseLookupIndex, resultLookupIndex,
++ QQmlJSRegisterContent::Property, type);
++ }
++
++ if (contained == jsValueType()) {
++ QQmlJSMetaProperty prop;
++ prop.setPropertyName(name);
++ prop.setTypeName(u"QJSValue"_s);
++ prop.setType(jsValueType());
++ prop.setIsWritable(true);
++ return m_pool->createProperty(
++ prop, baseLookupIndex, resultLookupIndex,
++ QQmlJSRegisterContent::Property, type);
++ }
++
++ if ((contained == stringType()
++ || contained->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence)
++ && name == u"length"_s) {
++ return lengthProperty(contained != stringType(), type);
++ }
++
++ const auto check = [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) {
++ const QQmlJSRegisterContent resultScope = mode == QQmlJSScope::NotExtension
++ ? baseType(scope, type)
++ : extensionType(scope, type);
++
++ if (mode != QQmlJSScope::ExtensionNamespace) {
++ if (scope->hasOwnProperty(name)) {
++ const auto prop = scope->ownProperty(name);
++ result = m_pool->createProperty(
++ prop, baseLookupIndex, resultLookupIndex,
++ QQmlJSRegisterContent::Property, resultScope);
++ return true;
++ }
++
++ if (scope->hasOwnMethod(name)) {
++ const auto methods = scope->ownMethods(name);
++ result = m_pool->createMethod(
++ methods, jsValueType(), QQmlJSRegisterContent::Method, resultScope);
++ return true;
++ }
++ }
++ // don't look up enums on ids...
++ if (type.variant() == QQmlJSRegisterContent::ObjectById)
++ return false;
++ return checkEnums(resultScope, name, &result);
++ };
++
++ if (QQmlJSUtils::searchBaseAndExtensionTypes(type.containedType(), check))
++ return result;
++
++ for (auto scope = contained;
++ scope && (QQmlSA::isFunctionScope(scope->scopeType())
++ || scope->scopeType() == QQmlSA::ScopeType::JSLexicalScope);
++ scope = scope->parentScope()) {
++ if (auto ownIdentifier = scope->ownJSIdentifier(name)) {
++ QQmlJSMetaProperty prop;
++ prop.setPropertyName(name);
++ prop.setTypeName(u"QJSValue"_s);
++ prop.setType(jsValueType());
++ prop.setIsWritable(!(ownIdentifier.value().isConst));
++
++ return m_pool->createProperty(
++ prop, baseLookupIndex, resultLookupIndex,
++ QQmlJSRegisterContent::Property,
++ parentScope(scope, type));
++ }
++ }
++
++ // check enums before checking attached types of attached types (chained attached types)
++ if (type.isType()) {
++ if (auto result = memberEnumType(type.scope(), name); result.isValid())
++ return result;
++ }
++
++ if (QQmlJSScope::ConstPtr attachedBase = typeForName(name)) {
++ if (QQmlJSScope::ConstPtr attached = attachedBase->attachedType()) {
++ if (!genericType(attached)) {
++ m_logger->log(u"Cannot resolve generic base of attached %1"_s.arg(
++ attached->internalName()),
++ qmlCompiler, attached->sourceLocation());
++ return {};
++ } else if (contained->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
++ m_logger->log(u"Cannot retrieve attached object for non-reference type %1"_s.arg(
++ contained->internalName()),
++ qmlCompiler, contained->sourceLocation());
++ return {};
++ } else {
++ const QQmlJSRegisterContent namedType = m_pool->createType(
++ attachedBase, QQmlJSRegisterContent::InvalidLookupIndex,
++ QQmlJSRegisterContent::TypeByName, type);
++
++ return m_pool->createType(
++ attached, resultLookupIndex, QQmlJSRegisterContent::Attachment,
++ namedType);
++ }
++ }
++ }
++
++ return {};
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::memberEnumType(
++ QQmlJSRegisterContent type, const QString &name) const
++ {
++ QQmlJSRegisterContent result;
++
++ if (QQmlJSUtils::searchBaseAndExtensionTypes(
++ type.containedType(),
++ [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) {
++ return checkEnums(mode == QQmlJSScope::NotExtension
++ ? baseType(scope, type)
++ : extensionType(scope, type),
++ name, &result);
++ })) {
++ return result;
++ }
++
++ return {};
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(
++ QQmlJSRegisterContent type, const QString &name, int lookupIndex) const
++ {
++ if (type.isType())
++ return memberType(type, name, type.resultLookupIndex(), lookupIndex);
++ if (type.isProperty() || type.isMethodCall())
++ return memberType(type, name, type.resultLookupIndex(), lookupIndex);
++ if (type.isEnumeration()) {
++ const auto enumeration = type.enumeration();
++ if (!type.enumMember().isEmpty() || !enumeration.hasKey(name))
++ return {};
++ return m_pool->createEnumeration(
++ enumeration, name, QQmlJSRegisterContent::Enum, type.scope());
++ }
++ if (type.isMethod()) {
++ QQmlJSMetaProperty prop;
++ prop.setTypeName(u"QJSValue"_s);
++ prop.setPropertyName(name);
++ prop.setType(jsValueType());
++ prop.setIsWritable(true);
++ return m_pool->createProperty(
++ prop, QQmlJSRegisterContent::InvalidLookupIndex, lookupIndex,
++ QQmlJSRegisterContent::Property, type);
++ }
++ if (type.isImportNamespace()) {
++ Q_ASSERT(type.scopeType()->isReferenceType());
++ return registerContentForName(name, type);
++ }
++ if (type.isConversion()) {
++ if (const auto result = memberType(
++ type, name, type.resultLookupIndex(), lookupIndex);
++ result.isValid()) {
++ return result;
++ }
++
++ if (const auto result = memberEnumType(type.scope(), name); result.isValid())
++ return result;
++
++ // If the conversion consists of only undefined and one actual type,
++ // we can produce the members of that one type.
++ // If the value is then actually undefined, the result is an exception.
++
++ const auto nonVoid = extractNonVoidFromOptionalType(type);
++
++ // If the conversion cannot hold the original type, it loses information.
++ return (!nonVoid.isNull() && canHold(type.conversionResultType(), nonVoid.containedType()))
++ ? memberType(nonVoid, name, type.resultLookupIndex(), lookupIndex)
++ : QQmlJSRegisterContent();
++ }
++
++ Q_UNREACHABLE_RETURN({});
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::elementType(QQmlJSRegisterContent list) const
++ {
++ QQmlJSScope::ConstPtr value;
++
++ auto valueType = [&](const QQmlJSScope::ConstPtr &scope) -> QQmlJSScope::ConstPtr {
++ if (scope->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence)
++ return scope->elementType();
++
++ if (scope == m_forInIteratorPtr)
++ return m_sizeType;
++
++ if (scope == m_forOfIteratorPtr)
++ return list.scopeType()->elementType();
++
++ if (scope == m_jsValueType || scope == m_varType)
++ return m_jsValueType;
++
++ if (scope == m_stringType)
++ return m_stringType;
++
++ return QQmlJSScope::ConstPtr();
++ };
++
++ value = valueType(list.containedType());
++
++ if (value.isNull())
++ return {};
++
++ QQmlJSMetaProperty property;
++ property.setPropertyName(u"[]"_s);
++ property.setTypeName(value->internalName());
++ property.setType(value);
++
++ return m_pool->createProperty(
++ property, QQmlJSRegisterContent::InvalidLookupIndex,
++ QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ListValue,
++ list);
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::returnType(
++ const QQmlJSMetaMethod &method, const QQmlJSScope::ConstPtr &returnType,
++ QQmlJSRegisterContent scope) const
++ {
++ return m_pool->createMethodCall(method, returnType, scope);
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::extensionType(
++ const QQmlJSScope::ConstPtr &extension, QQmlJSRegisterContent base) const
++ {
++ return m_pool->createType(
++ extension, base.resultLookupIndex(), QQmlJSRegisterContent::Extension, base);
++ }
++
++ /*!
++ * \internal
++ * Encodes \a base as a base type of \a derived and returns a QQmlJSRegisterContent.
++ * "Base type" here is understood the same way as std::is_base_of would understand it.
++ * That means, if you pass the contained type of \a derived as \a base, then \a derived
++ * itself is returned.
++ */
++ QQmlJSRegisterContent QQmlJSTypeResolver::baseType(
++ const QQmlJSScope::ConstPtr &base, QQmlJSRegisterContent derived) const
++ {
++ return m_pool->createType(
++ base, derived.resultLookupIndex(), QQmlJSRegisterContent::BaseType, derived);
++ }
++
++ /*!
++ * \internal
++ * Encodes \a parent as a parent scope of \a child and returns a QQmlJSRegisterContent.
++ * "Parent scope" here means any scope above, but also _including_ \a child.
++ * That means, if you pass the contained type of \a child as \a parent, then \a child
++ * itself is returned.
++ */
++ QQmlJSRegisterContent QQmlJSTypeResolver::parentScope(
++ const QQmlJSScope::ConstPtr &parent, QQmlJSRegisterContent child) const
++ {
++ return m_pool->createType(
++ parent, child.resultLookupIndex(), QQmlJSRegisterContent::ParentScope, child);
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::iteratorPointer(
++ QQmlJSRegisterContent listType, QQmlJS::AST::ForEachType type,
++ int lookupIndex) const
++ {
++ const QQmlJSScope::ConstPtr value = (type == QQmlJS::AST::ForEachType::In)
++ ? m_int32Type
++ : elementType(listType).containedType();
++
++ QQmlJSScope::ConstPtr iteratorPointer = type == QQmlJS::AST::ForEachType::In
++ ? m_forInIteratorPtr
++ : m_forOfIteratorPtr;
++
++ QQmlJSMetaProperty prop;
++ prop.setPropertyName(u"<>"_s);
++ prop.setTypeName(iteratorPointer->internalName());
++ prop.setType(iteratorPointer);
++ return m_pool->createProperty(
++ prop, lookupIndex,
++ QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ListIterator,
++ listType);
++ }
++
++ QQmlJSScope::ConstPtr QQmlJSTypeResolver::storedType(const QQmlJSScope::ConstPtr &type) const
++ {
++ if (type.isNull())
++ return {};
++ if (type == voidType())
++ return type;
++ if (type->isScript())
++ return jsValueType();
++ if (type->isComposite()) {
++ if (const QQmlJSScope::ConstPtr nonComposite = QQmlJSScope::nonCompositeBaseType(type))
++ return nonComposite;
++
++ // If we can't find the non-composite base, we really don't know what it is.
++ return genericType(type);
++ }
++ if (type->filePath().isEmpty())
++ return genericType(type);
++ return type;
++ }
++
++ static QQmlJSRegisterContent doConvert(
++ QQmlJSRegisterContent from, const QQmlJSScope::ConstPtr &to,
++ QQmlJSRegisterContent scope, QQmlJSRegisterContentPool *pool)
++ {
++ if (from.isConversion()) {
++ return pool->createConversion(
++ from.conversionOrigins(), to,
++ scope.isValid() ? scope : from.conversionResultScope(),
++ from.variant(), from.scope());
++ }
++
++ return pool->createConversion(
++ QList<QQmlJSRegisterContent>{from},
++ to, scope, from.variant(),
++ from.scope());
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::convert(
++ QQmlJSRegisterContent from, QQmlJSRegisterContent to) const
++ {
++ return doConvert(from, to.containedType(), to.scope(), m_pool.get());
++ }
++
++ QQmlJSRegisterContent QQmlJSTypeResolver::convert(
++ QQmlJSRegisterContent from, const QQmlJSScope::ConstPtr &to) const
++ {
++ return doConvert(from, to, QQmlJSRegisterContent(), m_pool.get());
++ }
++
++ QT_END_NAMESPACE