diff options
author | Ulf Hermann <ulf.hermann@qt.io> | 2022-10-19 14:35:24 +0200 |
---|---|---|
committer | Ulf Hermann <ulf.hermann@qt.io> | 2022-11-10 15:00:11 +0100 |
commit | 91c6d455595d245445f28d6d1c27c4f2710ef3c3 (patch) | |
tree | 87c78aea69c6485e9a18f6ca9cf2fa5830b027c7 | |
parent | 42065c0e6eba377d775908affdd2a98805712b13 (diff) | |
download | qtdeclarative-91c6d455595d245445f28d6d1c27c4f2710ef3c3.tar.gz |
QmlCompiler: Allow lists as arguments to methods
Since lists are allowed as property types, you should be able to pass
them as arguments to methods, too. For now we only handle QML-defined
methods, implemented by adding JavaScript functions to your QML
elements. The usual type coercion rules apply if you pass JavaScript
arrays to such methods. That is, it usually works.
We now resolve properties with the "list" flag to their actual types
(QQmlListProperty or QList) already when populating the QQmlJSScope, and
store the list types as members of QQmlJSScope rather than as a special
map in QQmlJSTypeResolver. This allows us to do the same to lists passed
as arguments and simplifies some of the type analysis.
Fixes: QTBUG-107171
Change-Id: Idf71ccdc1d59f472c17084a36b5d7879c4d959c0
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r-- | src/imports/builtins/builtins.qmltypes | 14 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscodegenerator.cpp | 24 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscompiler.cpp | 1 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsfunctioninitializer.cpp | 11 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsimporter.cpp | 5 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsimportvisitor.cpp | 6 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsregistercontent.cpp | 8 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsscope.cpp | 86 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsscope_p.h | 9 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljsstoragegeneralizer.cpp | 6 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljstyperesolver.cpp | 179 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljstyperesolver_p.h | 14 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/listAsArgument.qml | 33 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp | 16 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.cpp | 16 | ||||
-rw-r--r-- | tools/qmltc/qmltccompilerpieces.cpp | 3 | ||||
-rw-r--r-- | tools/qmltc/qmltcpropertyutils.h | 19 |
18 files changed, 275 insertions, 176 deletions
diff --git a/src/imports/builtins/builtins.qmltypes b/src/imports/builtins/builtins.qmltypes index f76acc93e1..7a74279ee8 100644 --- a/src/imports/builtins/builtins.qmltypes +++ b/src/imports/builtins/builtins.qmltypes @@ -154,6 +154,20 @@ Module { } Component { + file: "qqmllist.h" + name: "QQmlListProperty<QObject>" + accessSemantics: "sequence" + valueType: "QObject" + } + + Component { + file: "qobject.h" + name: "QObjectList" + accessSemantics: "sequence" + valueType: "QObject" + } + + Component { name: "int" extension: "Number" exports: ["QML/int 1.0"] diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp index 72db1e11db..d0297a1abb 100644 --- a/src/qmlcompiler/qqmljscodegenerator.cpp +++ b/src/qmlcompiler/qqmljscodegenerator.cpp @@ -1794,9 +1794,21 @@ void QQmlJSCodeGenerator::generate_DefineArray(int argc, int args) registerVariable(args + i)); } - m_body += m_state.accumulatorVariableOut + u" = "_s + stored->internalName() + u'{'; - m_body += initializer.join(u", "_s); - m_body += u"};\n"; + if (stored->isListProperty()) { + reject(u"creating a QQmlListProperty not backed by a property"_s); + + // We can, technically, generate code for this. But it's dangerous: + // + // const QString storage = m_state.accumulatorVariableOut + u"_storage"_s; + // m_body += stored->internalName() + u"::ListType " + storage + // + u" = {"_s + initializer.join(u", "_s) + u"};\n"_s; + // m_body += m_state.accumulatorVariableOut + // + u" = " + stored->internalName() + u"(nullptr, &"_s + storage + u");\n"_s; + } else { + m_body += m_state.accumulatorVariableOut + u" = "_s + stored->internalName() + u'{'; + m_body += initializer.join(u", "_s); + m_body += u"};\n"; + } } void QQmlJSCodeGenerator::generate_DefineObjectLiteral(int internalClassId, int argc, int args) @@ -1994,6 +2006,9 @@ QString QQmlJSCodeGenerator::contentPointer(const QQmlJSRegisterContent &content return u'&' + var; } + if (stored->isListProperty() && m_typeResolver->containedType(content)->isListProperty()) + return u'&' + var; + reject(u"content pointer of non-QVariant wrapper type "_s + content.descriptiveName()); return QString(); } @@ -2017,6 +2032,9 @@ QString QQmlJSCodeGenerator::contentType(const QQmlJSRegisterContent &content, c return metaTypeFromType(m_typeResolver->intType()); } + if (stored->isListProperty() && m_typeResolver->containedType(content)->isListProperty()) + return metaTypeFromType(m_typeResolver->listPropertyType()); + reject(u"content type of non-QVariant wrapper type "_s + content.descriptiveName()); return QString(); } diff --git a/src/qmlcompiler/qqmljscompiler.cpp b/src/qmlcompiler/qqmljscompiler.cpp index 5bd7a7f0b0..53798db475 100644 --- a/src/qmlcompiler/qqmljscompiler.cpp +++ b/src/qmlcompiler/qqmljscompiler.cpp @@ -746,6 +746,7 @@ QQmlJSAotFunction QQmlJSAotCompiler::globalCode() const u"QtQml/qqmlcomponent.h"_s, u"QtQml/qqmlcontext.h"_s, u"QtQml/qqmlengine.h"_s, + u"QtQml/qqmllist.h"_s, u"QtCore/qdatetime.h"_s, u"QtCore/qobject.h"_s, diff --git a/src/qmlcompiler/qqmljsfunctioninitializer.cpp b/src/qmlcompiler/qqmljsfunctioninitializer.cpp index 54bef282ea..b3c448072c 100644 --- a/src/qmlcompiler/qqmljsfunctioninitializer.cpp +++ b/src/qmlcompiler/qqmljsfunctioninitializer.cpp @@ -206,12 +206,11 @@ QQmlJSCompilePass::Function QQmlJSFunctionInitializer::run( } const auto property = m_objectType->property(propertyName); - function.returnType = property.isList() - ? m_typeResolver->listType(property.type(), QQmlJSTypeResolver::UseQObjectList) - : QQmlJSScope::ConstPtr(property.type()); - - - if (!function.returnType) { + if (const QQmlJSScope::ConstPtr propertyType = property.type()) { + function.returnType = propertyType->isListProperty() + ? m_typeResolver->qObjectListType() + : propertyType; + } else { diagnose(u"Cannot resolve property type %1 for binding on %2"_s.arg( property.typeName(), propertyName), QtWarningMsg, bindingLocation, error); diff --git a/src/qmlcompiler/qqmljsimporter.cpp b/src/qmlcompiler/qqmljsimporter.cpp index c5048705c9..f8adeb79c2 100644 --- a/src/qmlcompiler/qqmljsimporter.cpp +++ b/src/qmlcompiler/qqmljsimporter.cpp @@ -462,9 +462,12 @@ void QQmlJSImporter::processImport(const QQmlJSScope::Import &importDescription, // resolve enumerations (which can potentially create new child scopes) // before resolving the type fully const QQmlJSScope::ConstPtr intType = tempTypes.cppNames.type(u"int"_s).scope; + const QQmlJSScope::ConstPtr arrayType = tempTypes.cppNames.type(u"Array"_s).scope; for (auto it = import.objects.begin(); it != import.objects.end(); ++it) { - if (!it->scope.factory()) + if (!it->scope.factory()) { QQmlJSScope::resolveEnums(it->scope, intType); + QQmlJSScope::resolveList(it->scope, arrayType); + } } for (auto it = import.objects.begin(); it != import.objects.end(); ++it) { diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp index 369159e68a..7392d44ad7 100644 --- a/src/qmlcompiler/qqmljsimportvisitor.cpp +++ b/src/qmlcompiler/qqmljsimportvisitor.cpp @@ -558,7 +558,9 @@ void QQmlJSImportVisitor::processDefaultProperties() const QQmlJSMetaProperty defaultProp = parentScope->property(defaultPropertyName); - if (it.value().size() > 1 && !defaultProp.isList()) { + if (it.value().size() > 1 + && !defaultProp.isList() + && !defaultProp.type()->isListProperty()) { m_logger->log( QStringLiteral("Cannot assign multiple objects to a default non-list property"), qmlNonListProperty, it.value().constFirst()->sourceLocation()); @@ -1477,7 +1479,7 @@ bool QQmlJSImportVisitor::visit(UiPublicMember *publicMember) const auto type = isAlias ? QQmlJSScope::ConstPtr() : m_rootScopeImports.type(typeName).scope; if (type) { - prop.setType(type); + prop.setType(prop.isList() ? type->listType() : type); const QString internalName = type->internalName(); prop.setTypeName(internalName.isEmpty() ? typeName : internalName); } else if (!isAlias) { diff --git a/src/qmlcompiler/qqmljsregistercontent.cpp b/src/qmlcompiler/qqmljsregistercontent.cpp index 60f51bc302..1573f889a1 100644 --- a/src/qmlcompiler/qqmljsregistercontent.cpp +++ b/src/qmlcompiler/qqmljsregistercontent.cpp @@ -63,11 +63,9 @@ bool QQmlJSRegisterContent::isList() const case Type: return std::get<QQmlJSScope::ConstPtr>(m_content)->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence; - case Property: { - const auto prop = std::get<QQmlJSMetaProperty>(m_content); - return prop.isList() - || prop.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence; - } + case Property: + return std::get<QQmlJSMetaProperty>(m_content).type()->accessSemantics() + == QQmlJSScope::AccessSemantics::Sequence; case Conversion: return std::get<ConvertedTypes>(m_content).result->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence; diff --git a/src/qmlcompiler/qqmljsscope.cpp b/src/qmlcompiler/qqmljsscope.cpp index 5b064c5233..94177f6672 100644 --- a/src/qmlcompiler/qqmljsscope.cpp +++ b/src/qmlcompiler/qqmljsscope.cpp @@ -379,8 +379,35 @@ QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr> QQmlJSScope::findType( return *type; } + const auto findListType = [&](const QString &prefix, const QString &postfix) + -> ImportedScope<ConstPtr> { + if (name.startsWith(prefix) && name.endsWith(postfix)) { + const qsizetype prefixLength = prefix.length(); + const QString &elementName + = name.mid(prefixLength, name.length() - prefixLength - postfix.length()); + const ImportedScope<ConstPtr> element + = findType(elementName, contextualTypes, usedTypes); + if (element.scope) { + useType(); + return { element.scope->listType(), element.revision }; + } + } + + return {}; + }; + switch (contextualTypes.context()) { case ContextualTypes::INTERNAL: { + if (const auto listType = findListType(u"QList<"_s, u">"_s); + listType.scope && !listType.scope->isReferenceType()) { + return listType; + } + + if (const auto listType = findListType(u"QQmlListProperty<"_s, u">"_s); + listType.scope && listType.scope->isReferenceType()) { + return listType; + } + // look for c++ namescoped enums! const auto colonColon = name.lastIndexOf(QStringLiteral("::")); if (colonColon == -1) @@ -407,6 +434,10 @@ QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr> QQmlJSScope::findType( useType(); return inlineComponent; } + + if (const auto listType = findListType(u"list<"_s, u">"_s); listType.scope) + return listType; + break; } } @@ -417,6 +448,11 @@ QTypeRevision QQmlJSScope::resolveType( const QQmlJSScope::Ptr &self, const QQmlJSScope::ContextualTypes &context, QSet<QString> *usedTypes) { + if (self->accessSemantics() == AccessSemantics::Sequence + && self->internalName().startsWith(u"QQmlListProperty<"_s)) { + self->setIsListProperty(true); + } + const QString baseTypeName = self->baseTypeName(); const auto baseType = findType(baseTypeName, context, usedTypes); if (!self->m_baseType.scope && !baseTypeName.isEmpty()) @@ -447,13 +483,16 @@ QTypeRevision QQmlJSScope::resolveType( continue; if (const auto type = findType(typeName, context, usedTypes); type.scope) { - it->setType(type.scope); + it->setType(it->isList() ? type.scope->listType() : type.scope); continue; } const auto enumeration = self->m_enumerations.find(typeName); - if (enumeration != self->m_enumerations.end()) - it->setType(enumeration->type()); + if (enumeration != self->m_enumerations.end()) { + it->setType(it->isList() + ? enumeration->type()->listType() + : QQmlJSScope::ConstPtr(enumeration->type())); + } } for (auto it = self->m_methods.begin(), end = self->m_methods.end(); it != end; ++it) { @@ -540,6 +579,7 @@ QTypeRevision QQmlJSScope::resolveTypes( const QQmlJSScope::ContextualTypes &contextualTypes, QSet<QString> *usedTypes) { resolveEnums(self, contextualTypes.intType()); + resolveList(self, contextualTypes.arrayType()); return resolveType(self, contextualTypes, usedTypes); }; return resolveTypesInternal(resolveAll, updateChildScope, self, contextualTypes, usedTypes); @@ -570,6 +610,40 @@ void QQmlJSScope::resolveEnums(const QQmlJSScope::Ptr &self, const QQmlJSScope:: } } +void QQmlJSScope::resolveList(const QQmlJSScope::Ptr &self, const QQmlJSScope::ConstPtr &arrayType) +{ + if (self->listType() || self->accessSemantics() == AccessSemantics::Sequence) + return; + + Q_ASSERT(!arrayType.isNull()); + QQmlJSScope::Ptr listType = QQmlJSScope::create(); + listType->setAccessSemantics(AccessSemantics::Sequence); + listType->setValueTypeName(self->internalName()); + + if (self->isComposite()) { + // There is no internalName for this thing. Just set the value type right away + listType->setInternalName(u"QQmlListProperty<>"_s); + listType->m_valueType = QQmlJSScope::ConstPtr(self); + } else if (self->isReferenceType()) { + listType->setInternalName(u"QQmlListProperty<%2>"_s.arg(self->internalName())); + // Do not set a filePath on the list type, so that we have to generalize it + // even in direct mode. + } else { + listType->setInternalName(u"QList<%2>"_s.arg(self->internalName())); + listType->setFilePath(self->filePath()); + } + + const QQmlJSImportedScope element = {self, QTypeRevision()}; + const QQmlJSImportedScope array = {arrayType, QTypeRevision()}; + QQmlJSScope::ContextualTypes contextualTypes( + QQmlJSScope::ContextualTypes::INTERNAL, { { self->internalName(), element }, }, + QQmlJSScope::ConstPtr(), arrayType); + QQmlJSScope::resolveTypes(listType, contextualTypes); + + Q_ASSERT(listType->valueType() == self); + self->m_listType = listType; +} + void QQmlJSScope::resolveGeneralizedGroup( const Ptr &self, const ConstPtr &baseType, const QQmlJSScope::ContextualTypes &contextualTypes, QSet<QString> *usedTypes) @@ -1003,6 +1077,7 @@ void QDeferredFactory<QQmlJSScope>::populate(const QSharedPointer<QQmlJSScope> & m_importer->m_globalWarnings.append(typeReader.errors()); scope->setInternalName(internalName()); QQmlJSScope::resolveEnums(scope, m_importer->builtinInternalNames().intType()); + QQmlJSScope::resolveList(scope, m_importer->builtinInternalNames().arrayType()); if (m_isSingleton && !scope->isSingleton()) { m_importer->m_globalWarnings.append( @@ -1054,7 +1129,10 @@ bool QQmlJSScope::canAssign(const QQmlJSScope::ConstPtr &derived) const return true; } - return internalName() == u"QVariant"_s || internalName() == u"QJSValue"_s; + if (internalName() == u"QVariant"_s || internalName() == u"QJSValue"_s) + return true; + + return isListProperty() && valueType()->canAssign(derived); } bool QQmlJSScope::isInCustomParserParent() const diff --git a/src/qmlcompiler/qqmljsscope_p.h b/src/qmlcompiler/qqmljsscope_p.h index 3a5b26e3f0..0745acd668 100644 --- a/src/qmlcompiler/qqmljsscope_p.h +++ b/src/qmlcompiler/qqmljsscope_p.h @@ -81,6 +81,7 @@ public: WrappedInImplicitComponent = 0x80, HasBaseTypeError = 0x100, HasExtensionNamespace = 0x200, + IsListProperty = 0x400, }; Q_DECLARE_FLAGS(Flags, Flag) Q_FLAGS(Flags); @@ -490,6 +491,8 @@ public: QString valueTypeName() const { return m_valueTypeName; } void setValueTypeName(const QString &name) { m_valueTypeName = name; } QQmlJSScope::ConstPtr valueType() const { return m_valueType; } + QQmlJSScope::ConstPtr listType() const { return m_listType; } + QQmlJSScope::Ptr listType() { return m_listType; } void addOwnRuntimeFunctionIndex(QQmlJSMetaMethod::AbsoluteFunctionIndex index) { @@ -531,6 +534,9 @@ public: void setIsWrappedInImplicitComponent(bool v) { m_flags.setFlag(WrappedInImplicitComponent, v); } void setExtensionIsNamespace(bool v) { m_flags.setFlag(HasExtensionNamespace, v); } + bool isListProperty() const { return m_flags.testFlag(IsListProperty); } + void setIsListProperty(bool v) { m_flags.setFlag(IsListProperty, v); } + void setAccessSemantics(AccessSemantics semantics) { m_semantics = semantics; } AccessSemantics accessSemantics() const { return m_semantics; } bool isReferenceType() const { return m_semantics == QQmlJSScope::AccessSemantics::Reference; } @@ -574,6 +580,8 @@ public: QSet<QString> *usedTypes = nullptr); static void resolveEnums( const QQmlJSScope::Ptr &self, const QQmlJSScope::ConstPtr &intType); + static void resolveList( + const QQmlJSScope::Ptr &self, const QQmlJSScope::ConstPtr &arrayType); static void resolveGeneralizedGroup( const QQmlJSScope::Ptr &self, const QQmlJSScope::ConstPtr &baseType, const QQmlJSScope::ContextualTypes &contextualTypes, @@ -738,6 +746,7 @@ private: */ QString m_valueTypeName; QQmlJSScope::WeakConstPtr m_valueType; + QQmlJSScope::Ptr m_listType; /*! The extension is provided as either a type (QML_{NAMESPACE_}EXTENDED) or as a diff --git a/src/qmlcompiler/qqmljsstoragegeneralizer.cpp b/src/qmlcompiler/qqmljsstoragegeneralizer.cpp index 22cfd4c175..ed3743bc61 100644 --- a/src/qmlcompiler/qqmljsstoragegeneralizer.cpp +++ b/src/qmlcompiler/qqmljsstoragegeneralizer.cpp @@ -43,8 +43,10 @@ QQmlJSCompilePass::InstructionAnnotations QQmlJSStorageGeneralizer::run( const auto transformRegisters = [&](QFlatMap<int, QQmlJSRegisterContent> ®isters) { - for (auto j = registers.begin(), jEnd = registers.end(); j != jEnd; ++j) - transformRegister(j.value()); + for (auto j = registers.begin(), jEnd = registers.end(); j != jEnd; ++j) { + QQmlJSRegisterContent &content = j.value(); + transformRegister(content); + } }; for (QQmlJSRegisterContent &argument : function->argumentTypes) { diff --git a/src/qmlcompiler/qqmljstyperesolver.cpp b/src/qmlcompiler/qqmljstyperesolver.cpp index 8c2e1896de..3bda081121 100644 --- a/src/qmlcompiler/qqmljstyperesolver.cpp +++ b/src/qmlcompiler/qqmljstyperesolver.cpp @@ -21,7 +21,7 @@ Q_LOGGING_CATEGORY(lcTypeResolver, "qt.qml.compiler.typeresolver", QtInfoMsg); QQmlJSTypeResolver::QQmlJSTypeResolver(QQmlJSImporter *importer) : m_imports(importer->builtinInternalNames()) - , m_typeTracker(std::make_unique<TypeTracker>()) + , m_trackedTypes(std::make_unique<QHash<QQmlJSScope::ConstPtr, TrackedType>>()) { const QQmlJSImporter::ImportedTypes &builtinTypes = m_imports; m_voidType = builtinTypes.type(u"void"_s).scope; @@ -39,6 +39,8 @@ QQmlJSTypeResolver::QQmlJSTypeResolver(QQmlJSImporter *importer) m_variantListType = builtinTypes.type(u"QVariantList"_s).scope; m_varType = builtinTypes.type(u"QVariant"_s).scope; m_jsValueType = builtinTypes.type(u"QJSValue"_s).scope; + m_listPropertyType = builtinTypes.type(u"QQmlListProperty<QObject>"_s).scope; + m_qObjectListType = builtinTypes.type(u"QObjectList"_s).scope; QQmlJSScope::Ptr emptyListType = QQmlJSScope::create(); emptyListType->setInternalName(u"void*"_s); @@ -53,15 +55,6 @@ QQmlJSTypeResolver::QQmlJSTypeResolver(QQmlJSImporter *importer) jsPrimitiveType->setAccessSemantics(QQmlJSScope::AccessSemantics::Value); m_jsPrimitiveType = jsPrimitiveType; - QQmlJSScope::Ptr listPropertyType = QQmlJSScope::create(); - listPropertyType->setInternalName(u"QQmlListProperty<QObject>"_s); - listPropertyType->setFilePath(u"qqmllist.h"_s); - listPropertyType->setAccessSemantics(QQmlJSScope::AccessSemantics::Sequence); - listPropertyType->setValueTypeName(u"QObject"_s); - QQmlJSScope::resolveTypes(listPropertyType, builtinTypes); - Q_ASSERT(!listPropertyType->extensionType().scope.isNull()); - m_listPropertyType = listPropertyType; - QQmlJSScope::Ptr metaObjectType = QQmlJSScope::create(); metaObjectType->setInternalName(u"const QMetaObject"_s); metaObjectType->setFilePath(u"qmetaobject.h"_s); @@ -126,49 +119,18 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::scopeForId( return m_objectsById.scope(id, referrer); } -QQmlJSScope::ConstPtr QQmlJSTypeResolver::listType( - const QQmlJSScope::ConstPtr &elementType, ListMode mode) const -{ - if (elementType.isNull()) - return QQmlJSScope::ConstPtr(); - - auto it = m_typeTracker->listTypes.find(elementType); - if (it != m_typeTracker->listTypes.end()) - return *it; - - switch (elementType->accessSemantics()) { - case QQmlJSScope::AccessSemantics::Reference: - if (mode == UseListReference) - return m_listPropertyType; - if (elementType->internalName() != u"QObject"_s) - return listType(genericType(elementType), mode); - Q_FALLTHROUGH(); - case QQmlJSScope::AccessSemantics::Value: { - QQmlJSScope::Ptr listType = QQmlJSScope::create(); - listType->setAccessSemantics(QQmlJSScope::AccessSemantics::Sequence); - listType->setValueTypeName(elementType->internalName()); - listType->setInternalName(u"QList<%1>"_s.arg(elementType->augmentedInternalName())); - listType->setFilePath(elementType->filePath()); - const QQmlJSImportedScope element = {elementType, QTypeRevision()}; - const QQmlJSImportedScope array = {m_arrayType, QTypeRevision()}; - QQmlJSScope::ContextualTypes contextualTypes( - QQmlJSScope::ContextualTypes::INTERNAL, - { { elementType->internalName(), element } }, - m_intType, m_arrayType); - QQmlJSScope::resolveTypes(listType, contextualTypes); - Q_ASSERT(equals(listType->valueType(), elementType)); - m_typeTracker->listTypes[elementType] = listType; - return listType; - } - default: - break; - } - return QQmlJSScope::ConstPtr(); -} - QQmlJSScope::ConstPtr QQmlJSTypeResolver::typeFromAST(QQmlJS::AST::Type *type) const { - return m_imports.type(QmlIR::IRBuilder::asString(type->typeId)).scope; + const QString typeId = QmlIR::IRBuilder::asString(type->typeId); +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + if (!type->typeArgument) + return m_imports.type(typeId).scope; + if (typeId == u"list"_s) + return typeForName(type->typeArgument->toString())->listType(); + return QQmlJSScope::ConstPtr(); +#else + return m_imports.type(typeId).scope; +#endif } QQmlJSScope::ConstPtr QQmlJSTypeResolver::typeForConst(QV4::ReturnedValue rv) const @@ -309,12 +271,8 @@ QQmlJSTypeResolver::containedType(const QQmlJSRegisterContent &container) const { if (container.isType()) return container.type(); - if (container.isProperty()) { - const QQmlJSMetaProperty prop = container.property(); - return prop.isList() - ? listType(prop.type(), UseListReference) - : QQmlJSScope::ConstPtr(prop.type()); - } + if (container.isProperty()) + return container.property().type(); if (container.isEnumeration()) return container.enumeration().type(); if (container.isMethod()) @@ -335,33 +293,17 @@ QQmlJSTypeResolver::containedType(const QQmlJSRegisterContent &container) const Q_UNREACHABLE_RETURN({}); } -void QQmlJSTypeResolver::trackListPropertyType( - const QQmlJSScope::ConstPtr &trackedListElementType) const -{ - if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes) - return; - - if (m_typeTracker->trackedTypes.contains(trackedListElementType) - && !m_typeTracker->listTypes.contains(trackedListElementType)) { - const QQmlJSScope::ConstPtr list = listType( - comparableType(trackedListElementType), UseListReference); - QQmlJSScope::Ptr clone = QQmlJSScope::clone(list); - m_typeTracker->listTypes[trackedListElementType] = clone; - m_typeTracker->trackedTypes[clone] = { list, QQmlJSScope::ConstPtr(), clone }; - } -} - 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_typeTracker->trackedTypes.find(type); - QQmlJSScope::ConstPtr orig = (it == m_typeTracker->trackedTypes.end()) ? type : it->original; + const auto it = m_trackedTypes->find(type); + QQmlJSScope::ConstPtr orig = (it == m_trackedTypes->end()) ? type : it->original; QQmlJSScope::Ptr clone = QQmlJSScope::clone(orig); - m_typeTracker->trackedTypes[clone] = { std::move(orig), QQmlJSScope::ConstPtr(), clone }; + m_trackedTypes->insert(clone, { std::move(orig), QQmlJSScope::ConstPtr(), clone }); return clone; } @@ -378,8 +320,6 @@ QQmlJSRegisterContent QQmlJSTypeResolver::transformed( if (origin.isProperty()) { QQmlJSMetaProperty prop = origin.property(); prop.setType((this->*op)(prop.type())); - if (prop.isList()) - trackListPropertyType(prop.type()); return QQmlJSRegisterContent::create( (this->*op)(origin.storedType()), prop, origin.variant(), (this->*op)(origin.scopeType())); @@ -487,7 +427,7 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::trackedContainedType( const QQmlJSRegisterContent &container) const { const QQmlJSScope::ConstPtr type = containedType(container); - return m_typeTracker->trackedTypes.contains(type) ? type : QQmlJSScope::ConstPtr(); + return m_trackedTypes->contains(type) ? type : QQmlJSScope::ConstPtr(); } QQmlJSScope::ConstPtr QQmlJSTypeResolver::originalContainedType( @@ -502,8 +442,8 @@ void QQmlJSTypeResolver::adjustTrackedType( if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes) return; - const auto it = m_typeTracker->trackedTypes.find(tracked); - Q_ASSERT(it != m_typeTracker->trackedTypes.end()); + const auto it = m_trackedTypes->find(tracked); + Q_ASSERT(it != m_trackedTypes->end()); it->replacement = comparableType(conversion); *it->clone = std::move(*QQmlJSScope::clone(conversion)); } @@ -514,8 +454,8 @@ void QQmlJSTypeResolver::adjustTrackedType( if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes) return; - const auto it = m_typeTracker->trackedTypes.find(tracked); - Q_ASSERT(it != m_typeTracker->trackedTypes.end()); + 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) @@ -534,8 +474,8 @@ void QQmlJSTypeResolver::generalizeType(const QQmlJSScope::ConstPtr &type) const if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes) return; - const auto it = m_typeTracker->trackedTypes.find(type); - Q_ASSERT(it != m_typeTracker->trackedTypes.end()); + 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); @@ -714,8 +654,9 @@ bool QQmlJSTypeResolver::canHoldUndefined(const QQmlJSRegisterContent &content) return false; } -QQmlJSScope::ConstPtr QQmlJSTypeResolver::genericType(const QQmlJSScope::ConstPtr &type, - ComponentIsGeneric allowComponent) const +QQmlJSScope::ConstPtr QQmlJSTypeResolver::genericType( + const QQmlJSScope::ConstPtr &type, + ComponentIsGeneric allowComponent) const { if (type->isScript()) return m_jsValueType; @@ -757,7 +698,10 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::genericType(const QQmlJSScope::ConstPt return m_jsValueType; } - if (isPrimitive(type) || equals(type, m_jsValueType) || equals(type, m_listPropertyType) + if (type->isListProperty()) + return m_listPropertyType; + + if (isPrimitive(type) || equals(type, m_jsValueType) || equals(type, m_urlType) || equals(type, m_dateTimeType) || equals(type, m_variantListType) || equals(type, m_varType) || equals(type, m_stringListType) || equals(type, m_emptyListType) @@ -772,10 +716,16 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::genericType(const QQmlJSScope::ConstPt return m_realType; if (type->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) { - if (equals(type, m_listPropertyType)) - return type; - if (const QQmlJSScope::ConstPtr valueType = type->valueType()) - return listType(genericType(valueType), UseQObjectList); + 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_varType; @@ -868,9 +818,7 @@ QQmlJSRegisterContent QQmlJSTypeResolver::scopedType(const QQmlJSScope::ConstPtr } } result = QQmlJSRegisterContent::create( - prop.isList() - ? listType(prop.type(), UseListReference) - : storedType(prop.type()), + storedType(prop.type()), prop, scopeContentVariant(mode, false), scope); return true; } @@ -1062,9 +1010,7 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSScope::ConstPtr if (scope->hasOwnProperty(name)) { const auto prop = scope->ownProperty(name); result = QQmlJSRegisterContent::create( - prop.isList() - ? listType(prop.type(), UseListReference) - : storedType(prop.type()), + storedType(prop.type()), prop, mode == QQmlJSScope::NotExtension ? QQmlJSRegisterContent::ObjectProperty @@ -1156,16 +1102,8 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSRegisterContent // we might have an enum of the attaching type. return memberEnumType(type.scopeType(), name); } - if (type.isProperty()) { - const auto prop = type.property(); - if (prop.isList()) { - const QQmlJSScope::ConstPtr propType = listType(prop.type(), UseListReference); - if (name == u"length"_s) - return lengthProperty(true, propType); - return memberType(propType, name); - } - return memberType(prop.type(), name); - } + if (type.isProperty()) + return memberType(type.property().type(), name); if (type.isEnumeration()) { const auto enumeration = type.enumeration(); if (!type.enumMember().isEmpty() || !enumeration.hasKey(name)) @@ -1225,13 +1163,8 @@ QQmlJSRegisterContent QQmlJSTypeResolver::valueType(const QQmlJSRegisterContent value = valueType(list.conversionResult()); } else if (list.isProperty()) { const auto prop = list.property(); - if (prop.isList()) { - scope = listType(prop.type(), UseListReference); - value = prop.type(); - } else { - scope = prop.type(); - value = valueType(scope); - } + scope = prop.type(); + value = valueType(scope); } if (value.isNull()) @@ -1268,12 +1201,8 @@ bool QQmlJSTypeResolver::registerContains(const QQmlJSRegisterContent ®, return equals(reg.type(), type); if (reg.isConversion()) return equals(reg.conversionResult(), type); - if (reg.isProperty()) { - const auto prop = reg.property(); - return prop.isList() - ? equals(type, listType(prop.type(), UseListReference)) - : equals(type, prop.type()); - } + if (reg.isProperty()) + return equals(type, reg.property().type()); if (reg.isEnumeration()) return equals(type, reg.enumeration().type()); if (reg.isMethod()) @@ -1303,8 +1232,8 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::storedType(const QQmlJSScope::ConstPtr QQmlJSScope::ConstPtr QQmlJSTypeResolver::originalType(const QQmlJSScope::ConstPtr &type) const { - const auto it = m_typeTracker->trackedTypes.find(type); - return it == m_typeTracker->trackedTypes.end() ? type : it->original; + const auto it = m_trackedTypes->find(type); + return it == m_trackedTypes->end() ? type : it->original; } /*! @@ -1336,8 +1265,8 @@ QQmlJSRegisterContent QQmlJSTypeResolver::convert( QQmlJSScope::ConstPtr QQmlJSTypeResolver::comparableType(const QQmlJSScope::ConstPtr &type) const { - const auto it = m_typeTracker->trackedTypes.constFind(type); - if (it == m_typeTracker->trackedTypes.constEnd()) + const auto it = m_trackedTypes->constFind(type); + if (it == m_trackedTypes->constEnd()) return type; return it->replacement ? it->replacement : it->original; } diff --git a/src/qmlcompiler/qqmljstyperesolver_p.h b/src/qmlcompiler/qqmljstyperesolver_p.h index b18937fa6e..993529464f 100644 --- a/src/qmlcompiler/qqmljstyperesolver_p.h +++ b/src/qmlcompiler/qqmljstyperesolver_p.h @@ -32,6 +32,7 @@ class Q_QMLCOMPILER_PRIVATE_EXPORT QQmlJSTypeResolver public: enum ParentMode { UseDocumentParent, UseParentProperty }; enum CloneMode { CloneTypes, DoNotCloneTypes }; + enum ListMode { UseListProperty, UseQObjectList }; QQmlJSTypeResolver(QQmlJSImporter *importer); @@ -59,6 +60,7 @@ public: QQmlJSScope::ConstPtr metaObjectType() const { return m_metaObjectType; } QQmlJSScope::ConstPtr functionType() const { return m_functionType; } QQmlJSScope::ConstPtr jsGlobalObject() const { return m_jsGlobalObject; } + QQmlJSScope::ConstPtr qObjectListType() const { return m_qObjectListType; } QQmlJSScope::ConstPtr scopeForLocation(const QV4::CompiledData::Location &location) const; QQmlJSScope::ConstPtr scopeForId( @@ -69,8 +71,6 @@ public: return m_imports.hasType(name) && !m_imports.type(name).scope; } - enum ListMode { UseListReference, UseQObjectList }; - QQmlJSScope::ConstPtr listType(const QQmlJSScope::ConstPtr &elementType, ListMode mode) const; QQmlJSScope::ConstPtr typeForName(const QString &name) const { return m_imports.type(name).scope; @@ -169,7 +169,6 @@ protected: bool canPrimitivelyConvertFromTo( const QQmlJSScope::ConstPtr &from, const QQmlJSScope::ConstPtr &to) const; QQmlJSRegisterContent lengthProperty(bool isWritable, const QQmlJSScope::ConstPtr &scope) const; - void trackListPropertyType(const QQmlJSScope::ConstPtr &trackedListElementType) const; QQmlJSRegisterContent transformed( const QQmlJSRegisterContent &origin, QQmlJSScope::ConstPtr (QQmlJSTypeResolver::*op)(const QQmlJSScope::ConstPtr &) const) const; @@ -199,6 +198,7 @@ protected: QQmlJSScope::ConstPtr m_jsValueType; QQmlJSScope::ConstPtr m_jsPrimitiveType; QQmlJSScope::ConstPtr m_listPropertyType; + QQmlJSScope::ConstPtr m_qObjectListType; QQmlJSScope::ConstPtr m_metaObjectType; QQmlJSScope::ConstPtr m_functionType; QQmlJSScope::ConstPtr m_jsGlobalObject; @@ -225,13 +225,7 @@ protected: QQmlJSScope::Ptr clone; }; - struct TypeTracker - { - QHash<QQmlJSScope::ConstPtr, QQmlJSScope::Ptr> listTypes; - QHash<QQmlJSScope::ConstPtr, TrackedType> trackedTypes; - }; - - std::unique_ptr<TypeTracker> m_typeTracker; + std::unique_ptr<QHash<QQmlJSScope::ConstPtr, TrackedType>> m_trackedTypes; }; QT_END_NAMESPACE diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 07728048c2..8469ba5200 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -107,6 +107,7 @@ set(qml_files jsmoduleimport.qml layouts.qml library.js + listAsArgument.qml listIndices.qml listPropertyAsModel.qml listlength.qml diff --git a/tests/auto/qml/qmlcppcodegen/data/listAsArgument.qml b/tests/auto/qml/qmlcppcodegen/data/listAsArgument.qml new file mode 100644 index 0000000000..0558f06aad --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/listAsArgument.qml @@ -0,0 +1,33 @@ +pragma Strict +import QtQml + +QtObject { + function selectSecondInt(a: list<int>): int { + return a[1] + } + + function returnInts1(): list<int> { + return l + } + + function returnInts2(): list<int> { + return [1, 2, 3, 4] + } + + function selectSecondDummy(a: list<Dummy>): Dummy { + return a[1] + } + + property list<int> l: [5, 4, 3, 2, 1] + property int i: selectSecondInt(l) + property int j: selectSecondInt([1, 2, 3, 4, 5]) + property int i1: returnInts1()[3] + property int i2: returnInts2()[3] + + property list<Dummy> dummies: [ + Dummy{ objectName: "a"}, + Dummy{ objectName: "this one" }, + Dummy{ } + ] + property Dummy d: selectSecondDummy(dummies) +} diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index 1a0d211989..c017ba140f 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -141,6 +141,7 @@ private slots: void inaccessibleProperty(); void typePropagationLoop(); void signatureIgnored(); + void listAsArgument(); }; void tst_QmlCppCodegen::initTestCase() @@ -2750,6 +2751,21 @@ void tst_QmlCppCodegen::signatureIgnored() QCOMPARE(ignored->property("n").toInt(), 67); } +void tst_QmlCppCodegen::listAsArgument() +{ + QQmlEngine engine; + + QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/listAsArgument.qml"_s)); + QVERIFY2(c.isReady(), qPrintable(c.errorString())); + + QScopedPointer<QObject> o(c.create()); + QCOMPARE(o->property("i").toInt(), 4); + QCOMPARE(o->property("j").toInt(), 2); + QCOMPARE(o->property("i1").toInt(), 2); + QCOMPARE(o->property("i2").toInt(), 4); + QCOMPARE(o->property("d").value<QObject *>()->objectName(), u"this one"_s); +} + QTEST_MAIN(tst_QmlCppCodegen) #include "tst_qmlcppcodegen.moc" diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp index 10345f8e3b..10d67e12ba 100644 --- a/tools/qmltc/qmltccompiler.cpp +++ b/tools/qmltc/qmltccompiler.cpp @@ -17,7 +17,9 @@ using namespace Qt::StringLiterals; bool qIsReferenceTypeList(const QQmlJSMetaProperty &p) { - return p.isList() && p.type()->isReferenceType(); + if (QQmlJSScope::ConstPtr type = p.type()) + return type->isListProperty(); + return false; } Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg); @@ -474,6 +476,7 @@ void QmltcCompiler::compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m, void QmltcCompiler::compileExtraListMethods(QmltcType ¤t, const QQmlJSMetaProperty &p) { QmltcPropertyData data(p); + const QString valueType = p.type()->valueType()->internalName() + u'*'; const QString variableName = data.read + u"()"_s; const QStringList ownershipWarning = { u"\\note {This method does not change the ownership of its argument."_s, @@ -490,7 +493,7 @@ void QmltcCompiler::compileExtraListMethods(QmltcType ¤t, const QQmlJSMeta append.comments << ownershipWarning; append.returnType = u"void"_s; append.name = u"%1Append"_s.arg(data.read); - append.parameterList.emplaceBack(u"%1*"_s.arg(p.type()->internalName()), u"toBeAppended"_s); + append.parameterList.emplaceBack(valueType, u"toBeAppended"_s); append.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName); append.body @@ -519,7 +522,7 @@ void QmltcCompiler::compileExtraListMethods(QmltcType ¤t, const QQmlJSMeta { QmltcMethod at{}; at.comments.emplaceBack(u"\\brief Access an element in %1."_s.arg(data.read)); - at.returnType = u"%1*"_s.arg(p.type()->internalName()); + at.returnType = valueType; at.name = u"%1At"_s.arg(data.read); at.parameterList.emplaceBack(u"qsizetype"_s, u"position"_s, QString()); @@ -553,7 +556,7 @@ void QmltcCompiler::compileExtraListMethods(QmltcType ¤t, const QQmlJSMeta replace.returnType = u"void"_s; replace.name = u"%1Replace"_s.arg(data.read); replace.parameterList.emplaceBack(u"qsizetype"_s, u"position"_s, QString()); - replace.parameterList.emplaceBack(u"%1*"_s.arg(p.type()->internalName()), u"element"_s, + replace.parameterList.emplaceBack(valueType, u"element"_s, QString()); replace.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName); @@ -593,8 +596,9 @@ void QmltcCompiler::compileProperty(QmltcType ¤t, const QQmlJSMetaProperty const QString underlyingType = getUnderlyingType(p); if (qIsReferenceTypeList(p)) { const QString storageName = variableName + u"_storage"; - current.variables.emplaceBack(u"QList<" + p.type()->internalName() + u"*>", storageName, - QString()); + current.variables.emplaceBack( + u"QList<" + p.type()->valueType()->internalName() + u"*>", storageName, + QString()); current.baselineCtor.initializerList.emplaceBack(variableName + u"(" + underlyingType + u"(this, std::addressof(" + storageName + u")))"); diff --git a/tools/qmltc/qmltccompilerpieces.cpp b/tools/qmltc/qmltccompilerpieces.cpp index 8d6f938aeb..601cf1bbed 100644 --- a/tools/qmltc/qmltccompilerpieces.cpp +++ b/tools/qmltc/qmltccompilerpieces.cpp @@ -97,7 +97,8 @@ void QmltcCodeGenerator::generate_assignToListProperty( type, p, QmltcCodeGenerator::wrap_privateClass(accessor, p)); qmlListVarName = u"listprop_%1"_s.arg(p.propertyName()); - *block << u"QQmlListProperty<%1> %2;"_s.arg(p.type()->internalName(), qmlListVarName); + QQmlJSScope::ConstPtr valueType = p.type()->valueType(); + *block << u"QQmlListProperty<%1> %2;"_s.arg(valueType->internalName(), qmlListVarName); *block << extensionPrologue; *block << u"%1 = %2->%3();"_s.arg(qmlListVarName, extensionAccessor, p.read()); *block << extensionEpilogue; diff --git a/tools/qmltc/qmltcpropertyutils.h b/tools/qmltc/qmltcpropertyutils.h index 7f5b1ff2dd..db26c498dd 100644 --- a/tools/qmltc/qmltcpropertyutils.h +++ b/tools/qmltc/qmltcpropertyutils.h @@ -5,6 +5,7 @@ #define QMLTCPROPERTYUTILS_H #include <private/qqmljsmetatypes_p.h> +#include <private/qqmljsscope_p.h> QT_BEGIN_NAMESPACE @@ -15,19 +16,15 @@ QT_BEGIN_NAMESPACE */ inline QString getUnderlyingType(const QQmlJSMetaProperty &p) { - QString underlyingType = p.type()->internalName(); - // NB: can be a pointer or a list, can't be both (list automatically assumes - // that it holds pointers though). check isList() first, as list<QtObject> - // would be both a list and a pointer (weird). if (p.isList()) { - if (p.type()->isReferenceType()) - underlyingType = u"QQmlListProperty<" + underlyingType + u">"; - else - underlyingType = u"QList<" + underlyingType + u">"; - } else if (p.type()->isReferenceType()) { - underlyingType += u'*'; + // We cannot just use p.type()->internalName() here because it may be + // a list property of something that only receives a C++ name from qmltc. + const QQmlJSScope::ConstPtr valueType = p.type()->valueType(); + return (valueType->isReferenceType() ? u"QQmlListProperty<" : u"QList<") + + valueType->internalName() + u'>'; } - return underlyingType; + + return p.type()->augmentedInternalName(); } // simple class that, for a given property, creates information for the |