diff options
Diffstat (limited to 'src/qmlcompiler/qqmljstyperesolver.cpp')
-rw-r--r-- | src/qmlcompiler/qqmljstyperesolver.cpp | 1787 |
1 files changed, 1787 insertions, 0 deletions
diff --git a/src/qmlcompiler/qqmljstyperesolver.cpp b/src/qmlcompiler/qqmljstyperesolver.cpp new file mode 100644 index 0000000000..93f9c7f7f4 --- /dev/null +++ b/src/qmlcompiler/qqmljstyperesolver.cpp @@ -0,0 +1,1787 @@ +// 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 <private/qv4value_p.h> + +#include <private/qduplicatetracker_p.h> + +#include <QtCore/qloggingcategory.h> + +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<QHash<QQmlJSScope::ConstPtr, TrackedType>>()) +{ + 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<QObject>"_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<QQmlJSScope::ConstPtr> origins = origin.conversionOrigins(); + QList<QQmlJSScope::ConstPtr> 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<QQmlJSScope::ConstPtr> &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<QQmlJSScope::ConstPtr> 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(<target containing %2>)."_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<QQmlJSMetaPropertyBinding> 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<QQmlJSScope::ConstPtr>{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 |