diff options
Diffstat (limited to 'src/qmlcompiler/qqmljstyperesolver.cpp')
-rw-r--r-- | src/qmlcompiler/qqmljstyperesolver.cpp | 868 |
1 files changed, 680 insertions, 188 deletions
diff --git a/src/qmlcompiler/qqmljstyperesolver.cpp b/src/qmlcompiler/qqmljstyperesolver.cpp index 901eccf387..93f9c7f7f4 100644 --- a/src/qmlcompiler/qqmljstyperesolver.cpp +++ b/src/qmlcompiler/qqmljstyperesolver.cpp @@ -19,42 +19,124 @@ 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>>()) + : 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; - m_intType = builtinTypes.type(u"int"_s).scope; - m_uintType = builtinTypes.type(u"uint"_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; - m_listPropertyType = builtinTypes.type(u"QQmlListProperty<QObject>"_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 emptyListType = QQmlJSScope::create(); - emptyListType->setInternalName(u"void*"_s); - emptyListType->setAccessSemantics(QQmlJSScope::AccessSemantics::Sequence); - QQmlJSScope::resolveTypes(emptyListType, builtinTypes); - Q_ASSERT(!emptyListType->extensionType().scope.isNull()); - m_emptyListType = emptyListType; - QQmlJSScope::Ptr jsPrimitiveType = QQmlJSScope::create(); jsPrimitiveType->setInternalName(u"QJSPrimitiveValue"_s); jsPrimitiveType->setFilePath(u"qjsprimitivevalue.h"_s); @@ -67,29 +149,26 @@ QQmlJSTypeResolver::QQmlJSTypeResolver(QQmlJSImporter *importer) metaObjectType->setAccessSemantics(QQmlJSScope::AccessSemantics::Reference); m_metaObjectType = metaObjectType; - QQmlJSScope::Ptr functionType = QQmlJSScope::create(); - functionType->setInternalName(u"function"_s); - functionType->setAccessSemantics(QQmlJSScope::AccessSemantics::Value); - m_functionType = functionType; - m_jsGlobalObject = importer->jsGlobalObject(); - auto numberMethods = m_jsGlobalObject->methods(u"Number"_s); - Q_ASSERT(numberMethods.size() == 1); - m_numberPrototype = numberMethods[0].returnType()->baseType(); - Q_ASSERT(m_numberPrototype); - Q_ASSERT(m_numberPrototype->internalName() == u"NumberPrototype"_s); - auto arrayMethods = m_jsGlobalObject->methods(u"Array"_s); - Q_ASSERT(arrayMethods.size() == 1); - m_arrayType = arrayMethods[0].returnType(); - Q_ASSERT(m_arrayType); + 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. + visitor traverse the program when given. */ void QQmlJSTypeResolver::init(QQmlJSImportVisitor *visitor, QQmlJS::AST::Node *program) { @@ -100,7 +179,8 @@ void QQmlJSTypeResolver::init(QQmlJSImportVisitor *visitor, QQmlJS::AST::Node *p m_imports.clearTypes(); m_signalHandlers.clear(); - program->accept(visitor); + if (program) + program->accept(visitor); m_objectsById = visitor->addressableScopes(); m_objectsByLocation = visitor->scopesBylocation(); @@ -108,6 +188,16 @@ void QQmlJSTypeResolver::init(QQmlJSImportVisitor *visitor, QQmlJS::AST::Node *p 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 { @@ -117,19 +207,15 @@ QQmlJSTypeResolver::scopeForLocation(const QV4::CompiledData::Location &location return m_objectsByLocation[location]; } -QQmlJSScope::ConstPtr QQmlJSTypeResolver::scopeForId( - const QString &id, const QQmlJSScope::ConstPtr &referrer) const -{ - return m_objectsById.scope(id, referrer); -} - 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) - return typeForName(type->typeArgument->toString())->listType(); + if (typeId == u"list"_s) { + if (const QQmlJSScope::ConstPtr typeArgument = typeForName(type->typeArgument->toString())) + return typeArgument->listType(); + } return QQmlJSScope::ConstPtr(); } @@ -140,7 +226,7 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::typeForConst(QV4::ReturnedValue rv) co return voidType(); if (value.isInt32()) - return intType(); + return int32Type(); if (value.isBoolean()) return boolType(); @@ -180,9 +266,9 @@ QQmlJSTypeResolver::typeForBinaryOperation(QSOperator::Op oper, const QQmlJSRegi case QSOperator::Op::BitXor: case QSOperator::Op::LShift: case QSOperator::Op::RShift: - return builtinType(intType()); + return builtinType(int32Type()); case QSOperator::Op::URShift: - return builtinType(uintType()); + return builtinType(uint32Type()); case QSOperator::Op::Add: { const auto leftContents = containedType(left); const auto rightContents = containedType(right); @@ -191,7 +277,7 @@ QQmlJSTypeResolver::typeForBinaryOperation(QSOperator::Op oper, const QQmlJSRegi const QQmlJSScope::ConstPtr result = merge(leftContents, rightContents); if (equals(result, boolType())) - return builtinType(intType()); + return builtinType(int32Type()); if (isNumeric(result)) return builtinType(realType()); @@ -201,7 +287,7 @@ QQmlJSTypeResolver::typeForBinaryOperation(QSOperator::Op oper, const QQmlJSRegi case QSOperator::Op::Mul: case QSOperator::Op::Exp: { const QQmlJSScope::ConstPtr result = merge(containedType(left), containedType(right)); - return builtinType(equals(result, boolType()) ? intType() : realType()); + return builtinType(equals(result, boolType()) ? int32Type() : realType()); } case QSOperator::Op::Div: case QSOperator::Op::Mod: @@ -222,14 +308,14 @@ QQmlJSRegisterContent QQmlJSTypeResolver::typeForArithmeticUnaryOperation( case UnaryOperator::Not: return builtinType(boolType()); case UnaryOperator::Complement: - return builtinType(intType()); + return builtinType(int32Type()); case UnaryOperator::Plus: if (isIntegral(operand)) return operand; Q_FALLTHROUGH(); default: if (equals(containedType(operand), boolType())) - return builtinType(intType()); + return builtinType(int32Type()); break; } @@ -248,13 +334,18 @@ bool QQmlJSTypeResolver::isNumeric(const QQmlJSRegisterContent &type) const bool QQmlJSTypeResolver::isIntegral(const QQmlJSRegisterContent &type) const { - return equals(containedType(type), m_intType) || equals(containedType(type), m_uintType); + 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 equals(type, m_intType) || equals(type, m_uintType) - || equals(type, m_realType) || equals(type, m_floatType) + return isNumeric(type) || equals(type, m_boolType) || equals(type, m_voidType) || equals(type, m_nullType) || equals(type, m_stringType) || equals(type, m_jsPrimitiveType); } @@ -266,7 +357,33 @@ bool QQmlJSTypeResolver::isNumeric(const QQmlJSScope::ConstPtr &type) const 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 @@ -317,15 +434,15 @@ QQmlJSRegisterContent QQmlJSTypeResolver::transformed( if (origin.isType()) { return QQmlJSRegisterContent::create( (this->*op)(origin.storedType()), (this->*op)(origin.type()), - origin.variant(), (this->*op)(origin.scopeType())); + 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.variant(), (this->*op)(origin.scopeType())); + (this->*op)(origin.storedType()), prop, origin.baseLookupIndex(), + origin.resultLookupIndex(), origin.variant(), (this->*op)(origin.scopeType())); } if (origin.isEnumeration()) { @@ -349,16 +466,31 @@ QQmlJSRegisterContent QQmlJSTypeResolver::transformed( } 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()), origin.conversionOrigins(), + (this->*op)(origin.storedType()), + transformedOrigins, (this->*op)(origin.conversionResult()), + (this->*op)(origin.conversionResultScope()), origin.variant(), (this->*op)(origin.scopeType())); } Q_UNREACHABLE_RETURN({}); } -QQmlJSRegisterContent QQmlJSTypeResolver::referenceTypeForName( +QQmlJSRegisterContent QQmlJSTypeResolver::registerContentForName( const QString &name, const QQmlJSScope::ConstPtr &scopeType, bool hasObjectModulePrefix) const { @@ -366,13 +498,17 @@ QQmlJSRegisterContent QQmlJSTypeResolver::referenceTypeForName( if (!type) return QQmlJSRegisterContent(); - if (type->isSingleton()) - return QQmlJSRegisterContent::create(storedType(type), type, - QQmlJSRegisterContent::Singleton, scopeType); + if (type->isSingleton()) { + return QQmlJSRegisterContent::create( + storedType(type), type, QQmlJSRegisterContent::InvalidLookupIndex, + QQmlJSRegisterContent::Singleton, scopeType); + } - if (type->isScript()) - return QQmlJSRegisterContent::create(storedType(type), type, - QQmlJSRegisterContent::Script, scopeType); + if (type->isScript()) { + return QQmlJSRegisterContent::create( + storedType(type), type, QQmlJSRegisterContent::InvalidLookupIndex, + QQmlJSRegisterContent::Script, scopeType); + } if (const auto attached = type->attachedType()) { if (!genericType(attached)) { @@ -390,7 +526,7 @@ QQmlJSRegisterContent QQmlJSTypeResolver::referenceTypeForName( // 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, + storedType(attached), attached, QQmlJSRegisterContent::InvalidLookupIndex, hasObjectModulePrefix ? QQmlJSRegisterContent::ObjectAttached : QQmlJSRegisterContent::ScopeAttached, type); @@ -404,11 +540,17 @@ QQmlJSRegisterContent QQmlJSTypeResolver::referenceTypeForName( // 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::MetaType, type); + return QQmlJSRegisterContent::create( + metaObjectType(), metaObjectType(), QQmlJSRegisterContent::InvalidLookupIndex, + QQmlJSRegisterContent::MetaType, type); case QQmlJSScope::AccessSemantics::Sequence: case QQmlJSScope::AccessSemantics::Value: - // This is not actually a type reference. You cannot get the metaobject + 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; } @@ -439,23 +581,33 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::originalContainedType( return originalType(containedType(container)); } -void QQmlJSTypeResolver::adjustTrackedType( +bool QQmlJSTypeResolver::adjustTrackedType( const QQmlJSScope::ConstPtr &tracked, const QQmlJSScope::ConstPtr &conversion) const { if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes) - return; + 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; } -void QQmlJSTypeResolver::adjustTrackedType( +bool QQmlJSTypeResolver::adjustTrackedType( const QQmlJSScope::ConstPtr &tracked, const QList<QQmlJSScope::ConstPtr> &conversions) const { if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes) - return; + return true; const auto it = m_trackedTypes->find(tracked); Q_ASSERT(it != m_trackedTypes->end()); @@ -466,10 +618,28 @@ void QQmlJSTypeResolver::adjustTrackedType( // 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)) { - it->replacement = comparableType(result); - *mutableTracked = std::move(*QQmlJSScope::clone(result)); + 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 @@ -512,8 +682,11 @@ QString QQmlJSTypeResolver::containedTypeName(const QQmlJSRegisterContent &conta bool QQmlJSTypeResolver::canConvertFromTo(const QQmlJSScope::ConstPtr &from, const QQmlJSScope::ConstPtr &to) const { - if (canPrimitivelyConvertFromTo(from, to)) + 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)) @@ -525,11 +698,9 @@ bool QQmlJSTypeResolver::canConvertFromTo(const QQmlJSScope::ConstPtr &from, // in QQmlJSCodeGenerator::conversion(). if (equals(from, m_stringType) && !to.isNull()) { const QString toTypeName = to->internalName(); - if (toTypeName == u"QTime"_s || toTypeName == u"QDate"_s - || toTypeName == u"QPoint"_s || toTypeName == u"QPointF"_s + 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 - || toTypeName == u"QColor"_s) { + || toTypeName == u"QRect"_s || toTypeName == u"QRectF"_s) { return true; } } @@ -552,16 +723,28 @@ static QQmlJSRegisterContent::ContentVariant mergeVariants(QQmlJSRegisterContent QQmlJSRegisterContent QQmlJSTypeResolver::merge(const QQmlJSRegisterContent &a, const QQmlJSRegisterContent &b) const { + if (a == b) + return a; + QList<QQmlJSScope::ConstPtr> origins; - if (a.isConversion()) + + QQmlJSScope::ConstPtr aResultScope; + if (a.isConversion()) { origins.append(a.conversionOrigins()); - else + aResultScope = a.conversionResultScope(); + } else { origins.append(containedType(a)); + aResultScope = a.scopeType(); + } - if (b.isConversion()) + QQmlJSScope::ConstPtr bResultScope; + if (b.isConversion()) { origins.append(b.conversionOrigins()); - else + 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()); @@ -571,6 +754,7 @@ QQmlJSRegisterContent QQmlJSTypeResolver::merge(const QQmlJSRegisterContent &a, merge(a.storedType(), b.storedType()), origins, merge(containedType(a), containedType(b)), + merge(aResultScope, bResultScope), mergeVariants(a.variant(), b.variant()), merge(a.scopeType(), b.scopeType())); } @@ -605,36 +789,81 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::merge(const QQmlJSScope::ConstPtr &a, if (equals(b, jsValueType()) || equals(b, varType())) return b; - auto canConvert = [&](const QQmlJSScope::ConstPtr &from, const QQmlJSScope::ConstPtr &to) { - return (equals(a, from) && equals(b, to)) || (equals(b, from) && equals(a, to)); + 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 (canConvert(boolType(), intType())) - return intType(); - if (canConvert(boolType(), uintType())) - return uintType(); - if (canConvert(intType(), stringType())) - return stringType(); - if (canConvert(uintType(), stringType())) - return stringType(); if (isPrimitive(a) && isPrimitive(b)) return jsPrimitiveType(); if (auto commonBase = commonBaseType(a, b)) return commonBase; - if (equals(a, nullType()) && b->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) + if ((equals(a, nullType()) || equals(a, boolType())) && b->isReferenceType()) return b; - if (equals(b, nullType()) && a->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) + 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) { @@ -657,6 +886,30 @@ bool QQmlJSTypeResolver::canHoldUndefined(const QQmlJSRegisterContent &content) 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 @@ -704,33 +957,21 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::genericType( if (type->isListProperty()) return m_listPropertyType; - if (isPrimitive(type) || equals(type, m_jsValueType) - || equals(type, m_urlType) || equals(type, m_dateTimeType) - || equals(type, m_dateType) || equals(type, m_timeType) - || equals(type, m_variantListType) || equals(type, m_varType) - || equals(type, m_stringListType) || equals(type, m_emptyListType) - || equals(type, m_byteArrayType)) { - return type; - } - - if (type->scopeType() == QQmlJSScope::EnumScope) - return m_intType; + if (type->scopeType() == QQmlSA::ScopeType::EnumScope) + return type->baseType(); - if (isNumeric(type)) - return m_realType; + if (isPrimitive(type)) + return type; - if (type->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) { - if (const QQmlJSScope::ConstPtr valueType = type->valueType()) { - switch (valueType->accessSemantics()) { - case QQmlJSScope::AccessSemantics::Value: - return genericType(valueType)->listType(); - case QQmlJSScope::AccessSemantics::Reference: - return m_qObjectListType; - default: - break; - } - } - return m_variantListType; + 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; @@ -739,12 +980,15 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::genericType( QQmlJSRegisterContent QQmlJSTypeResolver::builtinType(const QQmlJSScope::ConstPtr &type) const { Q_ASSERT(storedType(type) == type); - return QQmlJSRegisterContent::create(type, type, QQmlJSRegisterContent::Builtin); + return QQmlJSRegisterContent::create( + type, type, QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Builtin); } QQmlJSRegisterContent QQmlJSTypeResolver::globalType(const QQmlJSScope::ConstPtr &type) const { - return QQmlJSRegisterContent::create(storedType(type), type, QQmlJSRegisterContent::Unknown); + return QQmlJSRegisterContent::create( + storedType(type), type, QQmlJSRegisterContent::InvalidLookupIndex, + QQmlJSRegisterContent::Unknown); } static QQmlJSRegisterContent::ContentVariant scopeContentVariant(QQmlJSScope::ExtensionKind mode, @@ -754,6 +998,7 @@ static QQmlJSRegisterContent::ContentVariant scopeContentVariant(QQmlJSScope::Ex case QQmlJSScope::NotExtension: return isMethod ? QQmlJSRegisterContent::ScopeMethod : QQmlJSRegisterContent::ScopeProperty; case QQmlJSScope::ExtensionType: + case QQmlJSScope::ExtensionJavaScript: return isMethod ? QQmlJSRegisterContent::ExtensionScopeMethod : QQmlJSRegisterContent::ExtensionScopeProperty; case QQmlJSScope::ExtensionNamespace: @@ -780,7 +1025,8 @@ static bool isRevisionAllowed(int memberRevision, const QQmlJSScope::ConstPtr &s } QQmlJSRegisterContent QQmlJSTypeResolver::scopedType(const QQmlJSScope::ConstPtr &scope, - const QString &name) const + const QString &name, int lookupIndex, + QQmlJSScopesByIdOptions options) const { const auto isAssignedToDefaultProperty = [this](const QQmlJSScope::ConstPtr &parent, const QQmlJSScope::ConstPtr &child) { @@ -791,7 +1037,7 @@ QQmlJSRegisterContent QQmlJSTypeResolver::scopedType(const QQmlJSScope::ConstPtr const QList<QQmlJSMetaPropertyBinding> defaultPropBindings = parent->propertyBindings(defaultPropertyName); for (const QQmlJSMetaPropertyBinding &binding : defaultPropBindings) { - if (binding.bindingType() == QQmlJSMetaPropertyBinding::Object + if (binding.bindingType() == QQmlSA::BindingType::Object && equals(binding.objectType(), child)) { return true; } @@ -799,8 +1045,8 @@ QQmlJSRegisterContent QQmlJSTypeResolver::scopedType(const QQmlJSScope::ConstPtr return false; }; - if (QQmlJSScope::ConstPtr identified = scopeForId(name, scope)) { - return QQmlJSRegisterContent::create(storedType(identified), identified, + if (QQmlJSScope::ConstPtr identified = m_objectsById.scope(name, scope, options)) { + return QQmlJSRegisterContent::create(storedType(identified), identified, lookupIndex, QQmlJSRegisterContent::ObjectById, scope); } @@ -823,8 +1069,9 @@ QQmlJSRegisterContent QQmlJSTypeResolver::scopedType(const QQmlJSScope::ConstPtr } } result = QQmlJSRegisterContent::create( - storedType(prop.type()), - prop, scopeContentVariant(mode, false), scope); + storedType(prop.type()), prop, + QQmlJSRegisterContent::InvalidLookupIndex, lookupIndex, + scopeContentVariant(mode, false), scope); return true; } @@ -851,14 +1098,16 @@ QQmlJSRegisterContent QQmlJSTypeResolver::scopedType(const QQmlJSScope::ConstPtr } } - QQmlJSRegisterContent result = referenceTypeForName(name); + QQmlJSRegisterContent result = registerContentForName(name); + if (result.isValid()) return result; if (m_jsGlobalObject->hasProperty(name)) { - return QQmlJSRegisterContent::create(jsValueType(), m_jsGlobalObject->property(name), - QQmlJSRegisterContent::JavaScriptGlobal, - m_jsGlobalObject); + 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, @@ -876,23 +1125,22 @@ bool QQmlJSTypeResolver::checkEnums(const QQmlJSScope::ConstPtr &scope, const QS if (name.isEmpty() || !name.at(0).isUpper()) return false; - const bool inExtension = - (mode == QQmlJSScope::ExtensionType) || (mode == QQmlJSScope::ExtensionNamespace); + const bool inExtension = (mode != QQmlJSScope::NotExtension); const auto enums = scope->ownEnumerations(); for (const auto &enumeration : enums) { - if (enumeration.name() == name) { + if ((enumeration.isScoped() || enumeration.isQml()) && enumeration.name() == name) { *result = QQmlJSRegisterContent::create( - storedType(intType()), enumeration, QString(), + storedType(enumeration.type()), enumeration, QString(), inExtension ? QQmlJSRegisterContent::ExtensionObjectEnum : QQmlJSRegisterContent::ObjectEnum, scope); return true; } - if (enumeration.hasKey(name)) { + if (!enumeration.isScoped() && enumeration.hasKey(name)) { *result = QQmlJSRegisterContent::create( - storedType(intType()), enumeration, name, + storedType(enumeration.type()), enumeration, name, inExtension ? QQmlJSRegisterContent::ExtensionObjectEnum : QQmlJSRegisterContent::ObjectEnum, scope); @@ -903,6 +1151,143 @@ bool QQmlJSTypeResolver::checkEnums(const QQmlJSScope::ConstPtr &scope, const QS 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 { @@ -912,12 +1297,14 @@ bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo( 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_boolType) || equals(to, m_stringType))) { return true; } @@ -925,6 +1312,10 @@ bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo( 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))) { @@ -937,25 +1328,26 @@ bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo( return true; } - if (equals(from, m_voidType) || equals(to, m_voidType)) + if (equals(to, m_voidType)) return true; if (to.isNull()) - return false; - - if (equals(from, m_stringType) && equals(to, m_dateTimeType)) - return true; + return equals(from, m_voidType); - for (const auto &originType : {m_dateTimeType, m_dateType, m_timeType}) { + 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 : {m_dateTimeType, m_dateType, m_timeType, m_stringType}) { + for (const auto &targetType : types) { if (equals(to, targetType)) return true; } - break;; + if (equals(to, m_realType)) + return true; + + break; } if (equals(from, m_nullType) @@ -971,7 +1363,7 @@ bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo( if (equals(to, m_jsPrimitiveType)) return isPrimitive(from); - if (equals(from, m_emptyListType) || equals(from, m_variantListType)) + if (equals(from, m_variantListType)) return to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence; const bool matchByName = !to->isComposite(); @@ -987,6 +1379,24 @@ bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo( 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; } @@ -995,14 +1405,17 @@ QQmlJSRegisterContent QQmlJSTypeResolver::lengthProperty( { QQmlJSMetaProperty prop; prop.setPropertyName(u"length"_s); - prop.setTypeName(u"int"_s); - prop.setType(intType()); + prop.setTypeName(u"qsizetype"_s); + prop.setType(sizeType()); prop.setIsWritable(isWritable); - return QQmlJSRegisterContent::create(intType(), prop, QQmlJSRegisterContent::Builtin, scope); + return QQmlJSRegisterContent::create( + sizeType(), prop, QQmlJSRegisterContent::InvalidLookupIndex, + QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::Builtin, scope); } -QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSScope::ConstPtr &type, - const QString &name) const +QQmlJSRegisterContent QQmlJSTypeResolver::memberType( + const QQmlJSScope::ConstPtr &type, const QString &name, int baseLookupIndex, + int resultLookupIndex) const { QQmlJSRegisterContent result; @@ -1010,14 +1423,26 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSScope::ConstPtr 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, - QQmlJSRegisterContent::JavaScriptObjectProperty, type); + return QQmlJSRegisterContent::create( + jsValueType(), prop, baseLookupIndex, resultLookupIndex, + QQmlJSRegisterContent::GenericObjectProperty, type); } if ((equals(type, stringType()) @@ -1032,7 +1457,7 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSScope::ConstPtr const auto prop = scope->ownProperty(name); result = QQmlJSRegisterContent::create( storedType(prop.type()), - prop, + prop, baseLookupIndex, resultLookupIndex, mode == QQmlJSScope::NotExtension ? QQmlJSRegisterContent::ObjectProperty : QQmlJSRegisterContent::ExtensionObjectProperty, @@ -1050,20 +1475,6 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSScope::ConstPtr scope); return true; } - - if (std::optional<QQmlJSScope::JavaScriptIdentifier> identifier = - scope->findJSIdentifier(name); - identifier.has_value()) { - QQmlJSMetaProperty prop; - prop.setPropertyName(name); - prop.setTypeName(u"QJSValue"_s); - prop.setType(jsValueType()); - prop.setIsWritable(!identifier->isConst); - - result = QQmlJSRegisterContent::create( - jsValueType(), prop, QQmlJSRegisterContent::JavaScriptObject, type); - return true; - } } return checkEnums(scope, name, &result, mode); @@ -1072,6 +1483,23 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSScope::ConstPtr 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)) { @@ -1085,9 +1513,9 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSScope::ConstPtr qmlCompiler, type->sourceLocation()); return {}; } else { - return QQmlJSRegisterContent::create(storedType(attached), attached, - QQmlJSRegisterContent::ObjectAttached, - attachedBase); + return QQmlJSRegisterContent::create( + storedType(attached), attached, resultLookupIndex, + QQmlJSRegisterContent::ObjectAttached, attachedBase); } } } @@ -1110,12 +1538,12 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberEnumType(const QQmlJSScope::Cons return {}; } -QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSRegisterContent &type, - const QString &name) const +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); + const auto result = memberType(content, name, type.resultLookupIndex(), lookupIndex); if (result.isValid()) return result; @@ -1124,12 +1552,12 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSRegisterContent return memberEnumType(type.scopeType(), name); } if (type.isProperty()) - return memberType(type.property().type(), name); + 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(intType()), enumeration, name, + return QQmlJSRegisterContent::create(storedType(enumeration.type()), enumeration, name, QQmlJSRegisterContent::ObjectEnum, type.scopeType()); } if (type.isMethod()) { @@ -1138,9 +1566,9 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSRegisterContent prop.setPropertyName(name); prop.setType(jsValueType()); prop.setIsWritable(true); - return QQmlJSRegisterContent::create(jsValueType(), prop, - QQmlJSRegisterContent::JavaScriptObjectProperty, - jsValueType()); + return QQmlJSRegisterContent::create( + jsValueType(), prop, QQmlJSRegisterContent::InvalidLookupIndex, lookupIndex, + QQmlJSRegisterContent::GenericObjectProperty, jsValueType()); } if (type.isImportNamespace()) { if (type.scopeType()->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) { @@ -1150,13 +1578,35 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSRegisterContent return {}; } - return referenceTypeForName( + return registerContentForName( name, type.scopeType(), type.variant() == QQmlJSRegisterContent::ObjectModulePrefix); } if (type.isConversion()) { - const auto result = memberType(type.conversionResult(), name); - return result.isValid() ? result : memberEnumType(type.scopeType(), name); + 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({}); @@ -1167,13 +1617,22 @@ QQmlJSRegisterContent QQmlJSTypeResolver::valueType(const QQmlJSRegisterContent QQmlJSScope::ConstPtr scope; QQmlJSScope::ConstPtr value; - auto valueType = [this](const QQmlJSScope::ConstPtr &scope) { + auto valueType = [&](const QQmlJSScope::ConstPtr &scope) { if (scope->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) return scope->valueType(); - else if (equals(scope, m_jsValueType) || equals(scope, m_varType)) + + 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; - else if (equals(scope, m_stringType)) + + if (equals(scope, m_stringType)) return m_stringType; + return QQmlJSScope::ConstPtr(); }; @@ -1197,7 +1656,8 @@ QQmlJSRegisterContent QQmlJSTypeResolver::valueType(const QQmlJSRegisterContent property.setType(value); return QQmlJSRegisterContent::create( - storedType(value), property, QQmlJSRegisterContent::ListValue, scope); + storedType(value), property, QQmlJSRegisterContent::InvalidLookupIndex, + QQmlJSRegisterContent::InvalidLookupIndex, QQmlJSRegisterContent::ListValue, scope); } QQmlJSRegisterContent QQmlJSTypeResolver::returnType( @@ -1206,7 +1666,32 @@ QQmlJSRegisterContent QQmlJSTypeResolver::returnType( { Q_ASSERT(variant == QQmlJSRegisterContent::MethodReturnValue || variant == QQmlJSRegisterContent::JavaScriptReturnValue); - return QQmlJSRegisterContent::create(storedType(type), type, variant, scope); + 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( @@ -1275,13 +1760,20 @@ QQmlJSRegisterContent QQmlJSTypeResolver::convert( { if (from.isConversion()) { return QQmlJSRegisterContent::create( - to.storedType(), from.conversionOrigins(), containedType(to), from.variant(), - from.scopeType()); + 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), from.variant(), from.scopeType()); + 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 |