// 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 "qqmljstyperesolver_p.h" #include "qqmljsimporter_p.h" #include "qqmljsimportvisitor_p.h" #include "qqmljslogger_p.h" #include "qqmljsutils_p.h" #include #include #include QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; Q_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_imports(importer->builtinInternalNames()), m_trackedTypes(std::make_unique>()) { 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_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_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"_s); Q_ASSERT(m_listPropertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence); Q_ASSERT(m_listPropertyType->valueTypeName() == 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; } /*! \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.clearTypes(); m_signalHandlers.clear(); if (program) program->accept(visitor); m_objectsById = visitor->addressableScopes(); m_objectsByLocation = visitor->scopesBylocation(); m_signalHandlers = visitor->signalHandlers(); m_imports = visitor->imports(); } 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, const QQmlJSRegisterContent &left, const 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 globalType(boolType()); case QSOperator::Op::BitAnd: case QSOperator::Op::BitOr: case QSOperator::Op::BitXor: case QSOperator::Op::LShift: case QSOperator::Op::RShift: return builtinType(int32Type()); case QSOperator::Op::URShift: return builtinType(uint32Type()); case QSOperator::Op::Add: { const auto leftContents = containedType(left); const auto rightContents = containedType(right); if (equals(leftContents, stringType()) || equals(rightContents, stringType())) return builtinType(stringType()); const QQmlJSScope::ConstPtr result = merge(leftContents, rightContents); if (equals(result, boolType())) return builtinType(int32Type()); if (isNumeric(result)) return builtinType(realType()); return builtinType(jsPrimitiveType()); } case QSOperator::Op::Sub: case QSOperator::Op::Mul: case QSOperator::Op::Exp: { const QQmlJSScope::ConstPtr result = merge(containedType(left), containedType(right)); return builtinType(equals(result, boolType()) ? int32Type() : realType()); } case QSOperator::Op::Div: case QSOperator::Op::Mod: return builtinType(realType()); case QSOperator::Op::As: return right; default: break; } return merge(left, right); } QQmlJSRegisterContent QQmlJSTypeResolver::typeForArithmeticUnaryOperation( UnaryOperator op, const QQmlJSRegisterContent &operand) const { switch (op) { case UnaryOperator::Not: return builtinType(boolType()); case UnaryOperator::Complement: return builtinType(int32Type()); case UnaryOperator::Plus: if (isIntegral(operand)) return operand; Q_FALLTHROUGH(); default: if (equals(containedType(operand), boolType())) return builtinType(int32Type()); break; } return builtinType(realType()); } bool QQmlJSTypeResolver::isPrimitive(const QQmlJSRegisterContent &type) const { return isPrimitive(containedType(type)); } bool QQmlJSTypeResolver::isNumeric(const QQmlJSRegisterContent &type) const { return isNumeric(containedType(type)); } bool QQmlJSTypeResolver::isIntegral(const QQmlJSRegisterContent &type) const { return isIntegral(containedType(type)); } bool QQmlJSTypeResolver::isIntegral(const QQmlJSScope::ConstPtr &type) const { // Only types of length <= 32bit count as integral return isSignedInteger(type) || isUnsignedInteger(type); } bool QQmlJSTypeResolver::isPrimitive(const QQmlJSScope::ConstPtr &type) const { return isNumeric(type) || equals(type, m_boolType) || equals(type, m_voidType) || equals(type, m_nullType) || equals(type, m_stringType) || equals(type, m_jsPrimitiveType); } bool QQmlJSTypeResolver::isNumeric(const QQmlJSScope::ConstPtr &type) const { return QQmlJSUtils::searchBaseAndExtensionTypes( type, [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) { if (mode == QQmlJSScope::ExtensionNamespace) return false; return equals(scope, m_numberPrototype); }); } bool QQmlJSTypeResolver::isSignedInteger(const QQmlJSScope::ConstPtr &type) const { return equals(type, m_int8Type) || equals(type, m_int16Type) || equals(type, m_int32Type) || equals(type, m_int64Type); } bool QQmlJSTypeResolver::isUnsignedInteger(const QQmlJSScope::ConstPtr &type) const { return equals(type, m_uint8Type) || equals(type, m_uint16Type) || equals(type, m_uint32Type) || equals(type, m_uint64Type); } bool QQmlJSTypeResolver::isNativeArrayIndex(const QQmlJSScope::ConstPtr &type) const { return (equals(type, m_uint8Type) || equals(type, m_int8Type) || equals(type, m_uint16Type) || equals(type, m_int16Type) || equals(type, m_uint32Type) || equals(type, m_int32Type)); } QQmlJSScope::ConstPtr QQmlJSTypeResolver::containedType(const QQmlJSRegisterContent &container) const { if (container.isType()) return container.type(); if (container.isProperty()) return container.property().type(); if (container.isEnumeration()) return container.enumeration().type(); if (container.isMethod()) return container.storedType(); // Methods can only be stored in QJSValue. if (container.isImportNamespace()) { switch (container.variant()) { case QQmlJSRegisterContent::ScopeModulePrefix: return container.storedType(); // We don't store scope module prefixes case QQmlJSRegisterContent::ObjectModulePrefix: return container.scopeType(); // We need to pass the original object through. default: Q_UNREACHABLE(); } } if (container.isConversion()) return container.conversionResult(); Q_UNREACHABLE_RETURN({}); } QQmlJSScope::ConstPtr QQmlJSTypeResolver::trackedType(const QQmlJSScope::ConstPtr &type) const { if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes) return type; // If origin is in fact an already tracked type, track the original of that one instead. const auto it = m_trackedTypes->find(type); QQmlJSScope::ConstPtr orig = (it == m_trackedTypes->end()) ? type : it->original; QQmlJSScope::Ptr clone = QQmlJSScope::clone(orig); m_trackedTypes->insert(clone, { std::move(orig), QQmlJSScope::ConstPtr(), clone }); return clone; } QQmlJSRegisterContent QQmlJSTypeResolver::transformed( const QQmlJSRegisterContent &origin, QQmlJSScope::ConstPtr (QQmlJSTypeResolver::*op)(const QQmlJSScope::ConstPtr &) const) const { if (origin.isType()) { return QQmlJSRegisterContent::create( (this->*op)(origin.storedType()), (this->*op)(origin.type()), origin.resultLookupIndex(), origin.variant(), (this->*op)(origin.scopeType())); } if (origin.isProperty()) { QQmlJSMetaProperty prop = origin.property(); prop.setType((this->*op)(prop.type())); return QQmlJSRegisterContent::create( (this->*op)(origin.storedType()), prop, origin.baseLookupIndex(), origin.resultLookupIndex(), origin.variant(), (this->*op)(origin.scopeType())); } if (origin.isEnumeration()) { QQmlJSMetaEnum enumeration = origin.enumeration(); enumeration.setType((this->*op)(enumeration.type())); return QQmlJSRegisterContent::create( (this->*op)(origin.storedType()), enumeration, origin.enumMember(), origin.variant(), (this->*op)(origin.scopeType())); } if (origin.isMethod()) { return QQmlJSRegisterContent::create( (this->*op)(origin.storedType()), origin.method(), origin.variant(), (this->*op)(origin.scopeType())); } if (origin.isImportNamespace()) { return QQmlJSRegisterContent::create( (this->*op)(origin.storedType()), origin.importNamespace(), origin.variant(), (this->*op)(origin.scopeType())); } if (origin.isConversion()) { // When retrieving the originals we want a deep retrieval. // When tracking a new type, we don't want to re-track its originals, though. const QList origins = origin.conversionOrigins(); QList transformedOrigins; if (op == &QQmlJSTypeResolver::trackedType) { transformedOrigins = origins; } else { transformedOrigins.reserve(origins.length()); for (const QQmlJSScope::ConstPtr &origin: origins) transformedOrigins.append((this->*op)(origin)); } return QQmlJSRegisterContent::create( (this->*op)(origin.storedType()), transformedOrigins, (this->*op)(origin.conversionResult()), (this->*op)(origin.conversionResultScope()), origin.variant(), (this->*op)(origin.scopeType())); } Q_UNREACHABLE_RETURN({}); } QQmlJSRegisterContent QQmlJSTypeResolver::registerContentForName( const QString &name, const QQmlJSScope::ConstPtr &scopeType, bool hasObjectModulePrefix) const { QQmlJSScope::ConstPtr type = typeForName(name); if (!type) return QQmlJSRegisterContent(); if (type->isSingleton()) { return QQmlJSRegisterContent::create( storedType(type), type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Singleton, scopeType); } if (type->isScript()) { return QQmlJSRegisterContent::create( storedType(type), type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Script, 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 QQmlJSRegisterContent::create( storedType(attached), attached, QQmlJSRegisterContent::InvalidLookupIndex, hasObjectModulePrefix ? QQmlJSRegisterContent::ObjectAttached : QQmlJSRegisterContent::ScopeAttached, type); } } 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 QQmlJSRegisterContent::create( metaObjectType(), metaObjectType(), QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::MetaType, type); case QQmlJSScope::AccessSemantics::Sequence: case QQmlJSScope::AccessSemantics::Value: if (canAddressValueTypes()) { return QQmlJSRegisterContent::create( metaObjectType(), metaObjectType(), QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::MetaType, type); } // 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(const QQmlJSRegisterContent &type) const { return transformed(type, &QQmlJSTypeResolver::originalType); } QQmlJSRegisterContent QQmlJSTypeResolver::tracked(const QQmlJSRegisterContent &type) const { return transformed(type, &QQmlJSTypeResolver::trackedType); } QQmlJSScope::ConstPtr QQmlJSTypeResolver::trackedContainedType( const QQmlJSRegisterContent &container) const { const QQmlJSScope::ConstPtr type = containedType(container); return m_trackedTypes->contains(type) ? type : QQmlJSScope::ConstPtr(); } QQmlJSScope::ConstPtr QQmlJSTypeResolver::originalContainedType( const QQmlJSRegisterContent &container) const { return originalType(containedType(container)); } bool QQmlJSTypeResolver::adjustTrackedType( const QQmlJSScope::ConstPtr &tracked, const QQmlJSScope::ConstPtr &conversion) const { if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes) return true; const auto it = m_trackedTypes->find(tracked); Q_ASSERT(it != m_trackedTypes->end()); // If we cannot convert to the new type without the help of e.g. lookupResultMetaType(), // we better not change the type. if (!canPrimitivelyConvertFromTo(tracked, conversion) && !canPopulate(conversion, tracked, nullptr) && !selectConstructor(conversion, tracked, nullptr).isValid()) { return false; } it->replacement = comparableType(conversion); *it->clone = std::move(*QQmlJSScope::clone(conversion)); return true; } bool QQmlJSTypeResolver::adjustTrackedType( const QQmlJSScope::ConstPtr &tracked, const QList &conversions) const { if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes) return true; const auto it = m_trackedTypes->find(tracked); Q_ASSERT(it != m_trackedTypes->end()); QQmlJSScope::Ptr mutableTracked = it->clone; QQmlJSScope::ConstPtr result; for (const QQmlJSScope::ConstPtr &type : conversions) result = merge(type, result); // If we cannot convert to the new type without the help of e.g. lookupResultMetaType(), // we better not change the type. if (!canPrimitivelyConvertFromTo(tracked, result) && !canPopulate(result, tracked, nullptr) && !selectConstructor(result, tracked, nullptr).isValid()) { return false; } it->replacement = comparableType(result); *mutableTracked = std::move(*QQmlJSScope::clone(result)); return true; } void QQmlJSTypeResolver::adjustOriginalType( const QQmlJSScope::ConstPtr &tracked, const QQmlJSScope::ConstPtr &conversion) const { if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes) return; const auto it = m_trackedTypes->find(tracked); Q_ASSERT(it != m_trackedTypes->end()); it->original = conversion; *it->clone = std::move(*QQmlJSScope::clone(conversion)); } void QQmlJSTypeResolver::generalizeType(const QQmlJSScope::ConstPtr &type) const { if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes) return; const auto it = m_trackedTypes->find(type); Q_ASSERT(it != m_trackedTypes->end()); *it->clone = std::move(*QQmlJSScope::clone(genericType(type))); if (it->replacement) it->replacement = genericType(it->replacement); it->original = genericType(it->original); } QString QQmlJSTypeResolver::containedTypeName(const QQmlJSRegisterContent &container, bool useFancyName) const { QQmlJSScope::ConstPtr type; // Use the type proper instead of the attached type switch (container.variant()) { case QQmlJSRegisterContent::ScopeAttached: case QQmlJSRegisterContent::MetaType: type = container.scopeType(); break; default: type = containedType(container); break; } QString typeName = type->internalName().isEmpty() ? type->baseTypeName() : type->internalName(); if (useFancyName) return QQmlJSScope::prettyName(typeName); return typeName; } 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 (equals(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(const QQmlJSRegisterContent &from, const QQmlJSRegisterContent &to) const { return canConvertFromTo(containedType(from), containedType(to)); } static QQmlJSRegisterContent::ContentVariant mergeVariants(QQmlJSRegisterContent::ContentVariant a, QQmlJSRegisterContent::ContentVariant b) { return (a == b) ? a : QQmlJSRegisterContent::Unknown; } QQmlJSRegisterContent QQmlJSTypeResolver::merge(const QQmlJSRegisterContent &a, const QQmlJSRegisterContent &b) const { if (a == b) return a; QList origins; QQmlJSScope::ConstPtr aResultScope; if (a.isConversion()) { origins.append(a.conversionOrigins()); aResultScope = a.conversionResultScope(); } else { origins.append(containedType(a)); aResultScope = a.scopeType(); } QQmlJSScope::ConstPtr bResultScope; if (b.isConversion()) { origins.append(b.conversionOrigins()); bResultScope = b.conversionResultScope(); } else { origins.append(containedType(b)); bResultScope = b.scopeType(); } std::sort(origins.begin(), origins.end()); const auto erase = std::unique(origins.begin(), origins.end()); origins.erase(erase, origins.end()); return QQmlJSRegisterContent::create( merge(a.storedType(), b.storedType()), origins, merge(containedType(a), containedType(b)), merge(aResultScope, bResultScope), mergeVariants(a.variant(), b.variant()), merge(a.scopeType(), b.scopeType())); } 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 commonBaseType = [this]( const QQmlJSScope::ConstPtr &a, const QQmlJSScope::ConstPtr &b) { for (QQmlJSScope::ConstPtr aBase = a; aBase; aBase = aBase->baseType()) { for (QQmlJSScope::ConstPtr bBase = b; bBase; bBase = bBase->baseType()) { if (equals(aBase, bBase)) return aBase; } } return QQmlJSScope::ConstPtr(); }; if (equals(a, b)) return a; if (equals(a, jsValueType()) || equals(a, varType())) return a; if (equals(b, jsValueType()) || equals(b, varType())) return b; const auto isInt32Compatible = [&](const QQmlJSScope::ConstPtr &type) { return (isIntegral(type) && !equals(type, uint32Type())) || equals(type, boolType()); }; if (isInt32Compatible(a) && isInt32Compatible(b)) return int32Type(); const auto isUInt32Compatible = [&](const QQmlJSScope::ConstPtr &type) { return isUnsignedInteger(type) || equals(type, boolType()); }; if (isUInt32Compatible(a) && isUInt32Compatible(b)) return uint32Type(); if (isNumeric(a) && isNumeric(b)) return realType(); if (isPrimitive(a) && isPrimitive(b)) return jsPrimitiveType(); if (auto commonBase = commonBaseType(a, b)) return commonBase; if ((equals(a, nullType()) || equals(a, boolType())) && b->isReferenceType()) return b; if ((equals(b, nullType()) || equals(b, boolType())) && a->isReferenceType()) return a; return varType(); } bool QQmlJSTypeResolver::canHold( const QQmlJSScope::ConstPtr &container, const QQmlJSScope::ConstPtr &contained) const { if (equals(container, contained) || equals(container, m_varType) || equals(container, m_jsValueType)) { return true; } if (equals(container, m_jsPrimitiveType)) return isPrimitive(contained); if (equals(container, m_variantListType)) return contained->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence; if (equals(container, m_qObjectListType) || equals(container, m_listPropertyType)) { if (contained->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence) return false; if (QQmlJSScope::ConstPtr value = contained->valueType()) return value->isReferenceType(); return false; } if (QQmlJSUtils::searchBaseAndExtensionTypes( container, [&](const QQmlJSScope::ConstPtr &base) { return equals(base, contained); })) { return true; } if (container->isReferenceType()) { if (QQmlJSUtils::searchBaseAndExtensionTypes( contained, [&](const QQmlJSScope::ConstPtr &base) { return equals(base, container); })) { return true; } } return false; } bool QQmlJSTypeResolver::canHoldUndefined(const QQmlJSRegisterContent &content) const { const auto canBeUndefined = [this](const QQmlJSScope::ConstPtr &type) { return equals(type, m_voidType) || equals(type, m_varType) || equals(type, m_jsValueType) || equals(type, m_jsPrimitiveType); }; if (!canBeUndefined(content.storedType())) return false; if (!content.isConversion()) return canBeUndefined(containedType(content)); const auto origins = content.conversionOrigins(); for (const auto &origin : origins) { if (canBeUndefined(origin)) return true; } return false; } bool QQmlJSTypeResolver::isOptionalType(const QQmlJSRegisterContent &content) const { if (!content.isConversion()) return false; const auto origins = content.conversionOrigins(); if (origins.length() != 2) return false; return equals(origins[0], m_voidType) || equals(origins[1], m_voidType); } QQmlJSScope::ConstPtr QQmlJSTypeResolver::extractNonVoidFromOptionalType( const QQmlJSRegisterContent &content) const { if (!isOptionalType(content)) return QQmlJSScope::ConstPtr(); const auto origins = content.conversionOrigins(); const QQmlJSScope::ConstPtr result = equals(origins[0], m_voidType) ? origins[1] : origins[0]; Q_ASSERT(!equals(result, m_voidType)); return result; } QQmlJSScope::ConstPtr QQmlJSTypeResolver::genericType( const QQmlJSScope::ConstPtr &type, ComponentIsGeneric allowComponent) const { if (type->isScript()) return m_jsValueType; if (equals(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; } } 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()."_s .arg(type->internalName(), unresolvedBaseTypeName), qmlCompiler, type->sourceLocation()); // 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. return m_jsValueType; } if (type->isListProperty()) return m_listPropertyType; if (type->scopeType() == QQmlSA::ScopeType::EnumScope) return type->baseType(); if (isPrimitive(type)) return type; 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 (equals(type, builtin) || equals(type, builtin->listType())) return type; } return m_varType; } QQmlJSRegisterContent QQmlJSTypeResolver::builtinType(const QQmlJSScope::ConstPtr &type) const { Q_ASSERT(storedType(type) == type); return QQmlJSRegisterContent::create( type, type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Builtin); } QQmlJSRegisterContent QQmlJSTypeResolver::globalType(const QQmlJSScope::ConstPtr &type) const { return QQmlJSRegisterContent::create( storedType(type), type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Unknown); } static QQmlJSRegisterContent::ContentVariant scopeContentVariant(QQmlJSScope::ExtensionKind mode, bool isMethod) { switch (mode) { case QQmlJSScope::NotExtension: return isMethod ? QQmlJSRegisterContent::ScopeMethod : QQmlJSRegisterContent::ScopeProperty; case QQmlJSScope::ExtensionType: case QQmlJSScope::ExtensionJavaScript: return isMethod ? QQmlJSRegisterContent::ExtensionScopeMethod : QQmlJSRegisterContent::ExtensionScopeProperty; 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; } QQmlJSRegisterContent QQmlJSTypeResolver::scopedType(const QQmlJSScope::ConstPtr &scope, const QString &name, int lookupIndex, QQmlJSScopesByIdOptions options) const { const auto isAssignedToDefaultProperty = [this](const QQmlJSScope::ConstPtr &parent, const QQmlJSScope::ConstPtr &child) { const QString defaultPropertyName = parent->defaultPropertyName(); if (defaultPropertyName.isEmpty()) // no reason to search for bindings return false; const QList defaultPropBindings = parent->propertyBindings(defaultPropertyName); for (const QQmlJSMetaPropertyBinding &binding : defaultPropBindings) { if (binding.bindingType() == QQmlSA::BindingType::Object && equals(binding.objectType(), child)) { return true; } } return false; }; if (QQmlJSScope::ConstPtr identified = m_objectsById.scope(name, scope, options)) { return QQmlJSRegisterContent::create(storedType(identified), identified, lookupIndex, QQmlJSRegisterContent::ObjectById, scope); } if (QQmlJSScope::ConstPtr base = QQmlJSScope::findCurrentQMLScope(scope)) { QQmlJSRegisterContent 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)) { QQmlJSMetaProperty prop = found->ownProperty(name); if (!isRevisionAllowed(prop.revision(), scope)) return false; if (m_parentMode == UseDocumentParent && name == base->parentPropertyName()) { QQmlJSScope::ConstPtr baseParent = base->parentScope(); if (baseParent && baseParent->inherits(prop.type()) && isAssignedToDefaultProperty(baseParent, base)) { prop.setType(baseParent); } } result = QQmlJSRegisterContent::create( storedType(prop.type()), prop, QQmlJSRegisterContent::InvalidLookupIndex, lookupIndex, scopeContentVariant(mode, false), scope); return true; } if (found->hasOwnMethod(name)) { auto methods = found->ownMethods(name); for (auto it = methods.begin(); it != methods.end();) { if (!isRevisionAllowed(it->revision(), scope)) it = methods.erase(it); else ++it; } if (methods.isEmpty()) return false; result = QQmlJSRegisterContent::create( jsValueType(), methods, scopeContentVariant(mode, true), scope); return true; } // Unqualified enums are not allowed return false; })) { return result; } } QQmlJSRegisterContent result = registerContentForName(name); if (result.isValid()) return result; if (m_jsGlobalObject->hasProperty(name)) { return QQmlJSRegisterContent::create( jsValueType(), m_jsGlobalObject->property(name), QQmlJSRegisterContent::InvalidLookupIndex, lookupIndex, QQmlJSRegisterContent::JavaScriptGlobal, m_jsGlobalObject); } else if (m_jsGlobalObject->hasMethod(name)) { return QQmlJSRegisterContent::create(jsValueType(), m_jsGlobalObject->methods(name), QQmlJSRegisterContent::JavaScriptGlobal, m_jsGlobalObject); } return {}; } bool QQmlJSTypeResolver::checkEnums(const QQmlJSScope::ConstPtr &scope, const QString &name, QQmlJSRegisterContent *result, QQmlJSScope::ExtensionKind mode) 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 bool inExtension = (mode != QQmlJSScope::NotExtension); const auto enums = scope->ownEnumerations(); for (const auto &enumeration : enums) { if ((enumeration.isScoped() || enumeration.isQml()) && enumeration.name() == name) { *result = QQmlJSRegisterContent::create( storedType(enumeration.type()), enumeration, QString(), inExtension ? QQmlJSRegisterContent::ExtensionObjectEnum : QQmlJSRegisterContent::ObjectEnum, scope); return true; } if (!enumeration.isScoped() && enumeration.hasKey(name)) { *result = QQmlJSRegisterContent::create( storedType(enumeration.type()), enumeration, name, inExtension ? QQmlJSRegisterContent::ExtensionObjectEnum : QQmlJSRegisterContent::ObjectEnum, scope); return true; } } return false; } bool QQmlJSTypeResolver::canPopulate( const QQmlJSScope::ConstPtr &type, const QQmlJSScope::ConstPtr &passedArgumentType, bool *isExtension) const { // TODO: We could allow QVariantMap and QVariantHash to be populated, but that needs extra // code in the code generator. if (type.isNull() || canHold(passedArgumentType, type) || isPrimitive(passedArgumentType) || type->accessSemantics() != QQmlJSScope::AccessSemantics::Value || !type->isStructured()) { return false; } if (isExtension) *isExtension = !type->extensionType().scope.isNull(); return true; } QQmlJSMetaMethod QQmlJSTypeResolver::selectConstructor( const QQmlJSScope::ConstPtr &type, const QQmlJSScope::ConstPtr &passedArgumentType, bool *isExtension) const { // If the "from" type can hold the target type, we should not try to coerce // it to any constructor argument. if (type.isNull() || canHold(passedArgumentType, type) || type->accessSemantics() != QQmlJSScope::AccessSemantics::Value || !type->isCreatable()) { return QQmlJSMetaMethod(); } auto doSelectConstructor = [&](const QQmlJSScope::ConstPtr &type) { QQmlJSMetaMethod candidate; const auto ownMethods = type->ownMethods(); for (const QQmlJSMetaMethod &method : ownMethods) { if (!method.isConstructor()) continue; const auto index = method.constructorIndex(); Q_ASSERT(index != QQmlJSMetaMethod::RelativeFunctionIndex::Invalid); const auto methodArguments = method.parameters(); if (methodArguments.size() != 1) continue; const QQmlJSScope::ConstPtr methodArgumentType = methodArguments[0].type(); if (equals(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 ((equals(a, eq[0]) && equals(b, eq[1])) || (equals(a, eq[1]) && equals(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 (equals(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 (equals(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 (equals(from, to)) return true; if (equals(from, m_varType) || equals(to, m_varType)) return true; if (equals(from, m_jsValueType) || equals(to, m_jsValueType)) return true; if (equals(to, m_qQmlScriptStringType)) return true; if (isNumeric(from) && isNumeric(to)) return true; if (isNumeric(from) && equals(to, m_boolType)) return true; if (from->accessSemantics() == QQmlJSScope::AccessSemantics::Reference && (equals(to, m_boolType) || equals(to, m_stringType))) { return true; } // Yes, our String has number constructors. if (isNumeric(from) && equals(to, m_stringType)) return true; // We can convert strings to numbers, but not to enums if (equals(from, m_stringType) && isNumeric(to)) return to->scopeType() != QQmlJSScope::ScopeType::EnumScope; // We can always convert between strings and urls. if ((equals(from, m_stringType) && equals(to, m_urlType)) || (equals(from, m_urlType) && equals(to, m_stringType))) { return true; } // We can always convert between strings and byte arrays. if ((equals(from, m_stringType) && equals(to, m_byteArrayType)) || (equals(from, m_byteArrayType) && equals(to, m_stringType))) { return true; } if (equals(to, m_voidType)) return true; if (to.isNull()) return equals(from, m_voidType); const auto types = { m_dateTimeType, m_dateType, m_timeType, m_stringType }; for (const auto &originType : types) { if (!equals(from, originType)) continue; for (const auto &targetType : types) { if (equals(to, targetType)) return true; } if (equals(to, m_realType)) return true; break; } if (equals(from, m_nullType) && to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) { return true; } if (equals(from, m_jsPrimitiveType)) { // You can cast any primitive to a nullptr return isPrimitive(to) || to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference; } if (equals(to, m_jsPrimitiveType)) return isPrimitive(from); if (equals(from, m_variantListType)) return to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence; const bool matchByName = !to->isComposite(); Q_ASSERT(!matchByName || !to->internalName().isEmpty()); for (auto baseType = from; baseType; baseType = baseType->baseType()) { if (equals(baseType, to)) return true; if (matchByName && baseType->internalName() == to->internalName()) return true; } // We can convert anything that fits into QJSPrimitiveValue if (canConvertFromTo(from, m_jsPrimitiveType) && canConvertFromTo(m_jsPrimitiveType, to)) return true; // We can convert everything to bool. if (equals(to, m_boolType)) return true; if (areEquivalentLists(from, to)) return true; if (from->isListProperty() && to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence && canConvertFromTo(from->valueType(), to->valueType())) { return true; } if (equals(to, m_stringType) && from->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) { return canConvertFromTo(from->valueType(), m_stringType); } return false; } QQmlJSRegisterContent QQmlJSTypeResolver::lengthProperty( bool isWritable, const QQmlJSScope::ConstPtr &scope) const { QQmlJSMetaProperty prop; prop.setPropertyName(u"length"_s); prop.setTypeName(u"qsizetype"_s); prop.setType(sizeType()); prop.setIsWritable(isWritable); return QQmlJSRegisterContent::create( sizeType(), prop, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Builtin, scope); } QQmlJSRegisterContent QQmlJSTypeResolver::memberType( const QQmlJSScope::ConstPtr &type, const QString &name, int baseLookupIndex, int resultLookupIndex) const { QQmlJSRegisterContent result; // If we got a plain type reference we have to check the enums of the _scope_. if (equals(type, metaObjectType())) return {}; if (equals(type, variantMapType())) { QQmlJSMetaProperty prop; prop.setPropertyName(name); prop.setTypeName(u"QVariant"_s); prop.setType(varType()); prop.setIsWritable(true); return QQmlJSRegisterContent::create( varType(), prop, baseLookupIndex, resultLookupIndex, QQmlJSRegisterContent::GenericObjectProperty, type); } if (equals(type, jsValueType())) { QQmlJSMetaProperty prop; prop.setPropertyName(name); prop.setTypeName(u"QJSValue"_s); prop.setType(jsValueType()); prop.setIsWritable(true); return QQmlJSRegisterContent::create( jsValueType(), prop, baseLookupIndex, resultLookupIndex, QQmlJSRegisterContent::GenericObjectProperty, type); } if ((equals(type, stringType()) || type->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) && name == u"length"_s) { return lengthProperty(!equals(type, stringType()), type); } const auto check = [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) { if (mode != QQmlJSScope::ExtensionNamespace) { if (scope->hasOwnProperty(name)) { const auto prop = scope->ownProperty(name); result = QQmlJSRegisterContent::create( storedType(prop.type()), prop, baseLookupIndex, resultLookupIndex, mode == QQmlJSScope::NotExtension ? QQmlJSRegisterContent::ObjectProperty : QQmlJSRegisterContent::ExtensionObjectProperty, scope); return true; } if (scope->hasOwnMethod(name)) { const auto methods = scope->ownMethods(name); result = QQmlJSRegisterContent::create( jsValueType(), methods, mode == QQmlJSScope::NotExtension ? QQmlJSRegisterContent::ObjectMethod : QQmlJSRegisterContent::ExtensionObjectMethod, scope); return true; } } return checkEnums(scope, name, &result, mode); }; if (QQmlJSUtils::searchBaseAndExtensionTypes(type, check)) return result; for (auto scope = type; scope && (scope->scopeType() == QQmlSA::ScopeType::JSFunctionScope || 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 QQmlJSRegisterContent::create(jsValueType(), prop, baseLookupIndex, resultLookupIndex, QQmlJSRegisterContent::JavaScriptObject, scope); } } 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 (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 { return QQmlJSRegisterContent::create( storedType(attached), attached, resultLookupIndex, QQmlJSRegisterContent::ObjectAttached, attachedBase); } } } return {}; } QQmlJSRegisterContent QQmlJSTypeResolver::memberEnumType(const QQmlJSScope::ConstPtr &type, const QString &name) const { QQmlJSRegisterContent result; if (QQmlJSUtils::searchBaseAndExtensionTypes( type, [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) { return checkEnums(scope, name, &result, mode); })) { return result; } return {}; } QQmlJSRegisterContent QQmlJSTypeResolver::memberType( const QQmlJSRegisterContent &type, const QString &name, int lookupIndex) const { if (type.isType()) { const auto content = type.type(); const auto result = memberType(content, name, type.resultLookupIndex(), lookupIndex); if (result.isValid()) return result; // If we didn't find anything and it's an attached type, // we might have an enum of the attaching type. return memberEnumType(type.scopeType(), name); } if (type.isProperty()) return memberType(type.property().type(), name, type.resultLookupIndex(), lookupIndex); if (type.isEnumeration()) { const auto enumeration = type.enumeration(); if (!type.enumMember().isEmpty() || !enumeration.hasKey(name)) return {}; return QQmlJSRegisterContent::create(storedType(enumeration.type()), enumeration, name, QQmlJSRegisterContent::ObjectEnum, type.scopeType()); } if (type.isMethod()) { QQmlJSMetaProperty prop; prop.setTypeName(u"QJSValue"_s); prop.setPropertyName(name); prop.setType(jsValueType()); prop.setIsWritable(true); return QQmlJSRegisterContent::create( jsValueType(), prop, QQmlJSRegisterContent::InvalidLookupIndex, lookupIndex, QQmlJSRegisterContent::GenericObjectProperty, jsValueType()); } if (type.isImportNamespace()) { if (type.scopeType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) { m_logger->log(u"Cannot use a non-QObject type %1 to access prefixed import"_s.arg( type.scopeType()->internalName()), qmlPrefixedImportType, type.scopeType()->sourceLocation()); return {}; } return registerContentForName( name, type.scopeType(), type.variant() == QQmlJSRegisterContent::ObjectModulePrefix); } if (type.isConversion()) { if (const auto result = memberType( type.conversionResult(), name, type.resultLookupIndex(), lookupIndex); result.isValid()) { return result; } if (const auto result = memberEnumType(type.scopeType(), 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. auto origins = type.conversionOrigins(); const auto begin = origins.begin(); const auto end = std::remove_if(begin, origins.end(), [this](const QQmlJSScope::ConstPtr &origin) { return equals(origin, m_voidType); }); // If the conversion cannot hold the original type, it loses information. return (end - begin == 1 && canHold(type.conversionResult(), *begin)) ? memberType(*begin, name, type.resultLookupIndex(), lookupIndex) : QQmlJSRegisterContent(); } Q_UNREACHABLE_RETURN({}); } QQmlJSRegisterContent QQmlJSTypeResolver::valueType(const QQmlJSRegisterContent &list) const { QQmlJSScope::ConstPtr scope; QQmlJSScope::ConstPtr value; auto valueType = [&](const QQmlJSScope::ConstPtr &scope) { if (scope->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) return scope->valueType(); if (equals(scope, m_forInIteratorPtr)) return m_sizeType; if (equals(scope, m_forOfIteratorPtr)) return list.scopeType()->valueType(); if (equals(scope, m_jsValueType) || equals(scope, m_varType)) return m_jsValueType; if (equals(scope, m_stringType)) return m_stringType; return QQmlJSScope::ConstPtr(); }; if (list.isType()) { scope = list.type(); value = valueType(scope); } else if (list.isConversion()) { value = valueType(list.conversionResult()); } else if (list.isProperty()) { const auto prop = list.property(); scope = prop.type(); value = valueType(scope); } if (value.isNull()) return {}; QQmlJSMetaProperty property; property.setPropertyName(u"[]"_s); property.setTypeName(value->internalName()); property.setType(value); return QQmlJSRegisterContent::create( storedType(value), property, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ListValue, scope); } QQmlJSRegisterContent QQmlJSTypeResolver::returnType( const QQmlJSScope::ConstPtr &type, QQmlJSRegisterContent::ContentVariant variant, const QQmlJSScope::ConstPtr &scope) const { Q_ASSERT(variant == QQmlJSRegisterContent::MethodReturnValue || variant == QQmlJSRegisterContent::JavaScriptReturnValue); return QQmlJSRegisterContent::create( storedType(type), type, QQmlJSRegisterContent::InvalidLookupIndex, variant, scope); } QQmlJSRegisterContent QQmlJSTypeResolver::iteratorPointer( const QQmlJSRegisterContent &listType, QQmlJS::AST::ForEachType type, int lookupIndex) const { const QQmlJSScope::ConstPtr value = (type == QQmlJS::AST::ForEachType::In) ? m_int32Type : containedType(valueType(listType)); QQmlJSScope::ConstPtr iteratorPointer = type == QQmlJS::AST::ForEachType::In ? m_forInIteratorPtr : m_forOfIteratorPtr; const QQmlJSScope::ConstPtr listContained = containedType(listType); QQmlJSMetaProperty prop; prop.setPropertyName(u"<>"_s); prop.setTypeName(iteratorPointer->internalName()); prop.setType(iteratorPointer); return QQmlJSRegisterContent::create( storedType(iteratorPointer), prop, lookupIndex, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ListIterator, listContained); } bool QQmlJSTypeResolver::registerIsStoredIn( const QQmlJSRegisterContent ®, const QQmlJSScope::ConstPtr &type) const { return equals(reg.storedType(), type); } bool QQmlJSTypeResolver::registerContains(const QQmlJSRegisterContent ®, const QQmlJSScope::ConstPtr &type) const { if (reg.isType()) return equals(reg.type(), type); if (reg.isConversion()) return equals(reg.conversionResult(), type); if (reg.isProperty()) return equals(type, reg.property().type()); if (reg.isEnumeration()) return equals(type, reg.enumeration().type()); if (reg.isMethod()) return equals(type, jsValueType()); return false; } QQmlJSScope::ConstPtr QQmlJSTypeResolver::storedType(const QQmlJSScope::ConstPtr &type) const { if (type.isNull()) return {}; if (equals(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; } QQmlJSScope::ConstPtr QQmlJSTypeResolver::originalType(const QQmlJSScope::ConstPtr &type) const { const auto it = m_trackedTypes->find(type); return it == m_trackedTypes->end() ? type : it->original; } /*! * \internal * * Compares the origin types of \a a and \a b. A straight a == b would compare the identity * of the pointers. However, since we clone types to keep track of them, we need a separate * way to compare the clones. Usually you'd do *a == *b for that, but as QQmlJSScope is rather * large, we offer an optimization here that uses the type tracking we already have in place. */ bool QQmlJSTypeResolver::equals(const QQmlJSScope::ConstPtr &a, const QQmlJSScope::ConstPtr &b) const { return comparableType(a) == comparableType(b); } QQmlJSRegisterContent QQmlJSTypeResolver::convert( const QQmlJSRegisterContent &from, const QQmlJSRegisterContent &to) const { if (from.isConversion()) { return QQmlJSRegisterContent::create( to.storedType(), from.conversionOrigins(), containedType(to), to.scopeType() ? to.scopeType() : from.conversionResultScope(), from.variant(), from.scopeType()); } return QQmlJSRegisterContent::create( to.storedType(), QList{containedType(from)}, containedType(to), to.scopeType(), from.variant(), from.scopeType()); } QQmlJSRegisterContent QQmlJSTypeResolver::cast( const QQmlJSRegisterContent &from, const QQmlJSScope::ConstPtr &to) const { return from.castTo(to).storedIn(storedType(to)); } QQmlJSScope::ConstPtr QQmlJSTypeResolver::comparableType(const QQmlJSScope::ConstPtr &type) const { const auto it = m_trackedTypes->constFind(type); if (it == m_trackedTypes->constEnd()) return type; return it->replacement ? it->replacement : it->original; } QT_END_NAMESPACE