aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qmltc
diff options
context:
space:
mode:
authorAndrei Golubev <andrei.golubev@qt.io>2021-12-14 17:20:15 +0100
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2021-12-18 16:12:32 +0000
commitc5efa5bdba3c563933faf02185b0c2be16f2dccb (patch)
tree3649d10c3d5e95bc27fc18fb8c739791eb628ff8 /tools/qmltc
parent5834e8356606d6c560ebbca7949b81ac61f601d1 (diff)
qmltc: Be aware of Component-wrapped types
While implicit components (the ones bound to Component-based property) are covered, cases like `Component { Text{} }` are not, so fix that Revisit the logic in the QmlIR / QQmlJSScope passes and code generator as part of this This might be a fairly used pattern in QML so no reason not to address this right away, especially given that we have most of the infrastructure in place already While at it, bring over extra tests that some other (non-merged) commit introduced. These give good coverage for missing cases and also exercise the feature supported here Task-number: QTBUG-84368 Change-Id: I8f5c74fc79380566475b1139d4cc5560fac123e3 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> (cherry picked from commit 362b4bf3d6df1e1799a163d3e6d7ee39f75ad080) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
Diffstat (limited to 'tools/qmltc')
-rw-r--r--tools/qmltc/prototype/codegenerator.cpp61
-rw-r--r--tools/qmltc/prototype/codegenerator.h4
-rw-r--r--tools/qmltc/prototype/codegeneratorutil.cpp9
-rw-r--r--tools/qmltc/prototype/codegeneratorutil.h2
-rw-r--r--tools/qmltc/prototype/qml2cppdefaultpasses.cpp87
-rw-r--r--tools/qmltc/prototype/qml2cppdefaultpasses.h7
6 files changed, 137 insertions, 33 deletions
diff --git a/tools/qmltc/prototype/codegenerator.cpp b/tools/qmltc/prototype/codegenerator.cpp
index abb99ab401..70fddf2fe8 100644
--- a/tools/qmltc/prototype/codegenerator.cpp
+++ b/tools/qmltc/prototype/codegenerator.cpp
@@ -270,12 +270,17 @@ void CodeGenerator::constructObjects(QSet<QString> &requiredCppIncludes)
requiredCppIncludes = findCppIncludes(context, objects);
};
executor.addPass(populateCppIncludes);
+ const auto resolveExplicitComponents = [&](const Qml2CppContext &context,
+ QList<Qml2CppObject> &objects) {
+ m_componentIndices.insert(findAndResolveExplicitComponents(context, objects));
+ };
const auto resolveImplicitComponents = [&](const Qml2CppContext &context,
QList<Qml2CppObject> &objects) {
- m_implicitComponentMapping = findAndResolveImplicitComponents(context, objects);
+ m_componentIndices.insert(findAndResolveImplicitComponents(context, objects));
};
+ executor.addPass(resolveExplicitComponents);
executor.addPass(resolveImplicitComponents);
- executor.addPass(&setObjectIds);
+ executor.addPass(&setObjectIds); // NB: must be after Component resolution
const auto setImmediateParents = [&](const Qml2CppContext &context,
QList<Qml2CppObject> &objects) {
m_immediateParents = findImmediateParents(context, objects);
@@ -544,10 +549,11 @@ void CodeGenerator::compileObject(QQmlJSAotObject &compiled, const CodeGenObject
}
// 3. set id if it's present in the QML document
if (!m_doc->stringAt(object.irObject->idNameIndex).isEmpty()) {
- Q_ASSERT(object.irObject->id >= 0);
compiled.init.body << u"// 3. set id since it exists"_qs;
- compiled.init.body << u"context->setIdValue(" + QString::number(object.irObject->id)
- + u", this);";
+ compiled.init.body << CodeGeneratorUtility::generate_setIdValue(
+ u"context"_qs, object.irObject->id, u"this"_qs,
+ m_doc->stringAt(object.irObject->idNameIndex))
+ + u";";
}
// TODO: we might want to optimize storage space when there are no object
@@ -1311,17 +1317,21 @@ void CodeGenerator::compileBinding(QQmlJSAotObject &current, const QmlIR::Bindin
return;
}
+ if (p.isList() || (binding.flags & QmlIR::Binding::IsListItem)) {
+ const QString refName = u"listref_" + propertyName;
+ const auto uniqueId = UniqueStringId(current, refName);
+ if (!m_listReferencesCreated.contains(uniqueId)) {
+ m_listReferencesCreated.insert(uniqueId);
+ // TODO: figure if Unicode support is needed here
+ current.endInit.body << u"QQmlListReference " + refName + u"(" + objectAddr
+ + u", QByteArrayLiteral(\"" + propertyName + u"\"));";
+ current.endInit.body << u"Q_ASSERT(" + refName + u".canAppend());";
+ }
+ }
+
const auto setObjectBinding = [&](const QString &value) {
if (p.isList() || (binding.flags & QmlIR::Binding::IsListItem)) {
const QString refName = u"listref_" + propertyName;
- const auto uniqueId = UniqueStringId(current, refName);
- if (!m_listReferencesCreated.contains(uniqueId)) {
- m_listReferencesCreated.insert(uniqueId);
- // TODO: figure if Unicode support is needed here
- current.endInit.body << u"QQmlListReference " + refName + u"(" + objectAddr
- + u", QByteArrayLiteral(\"" + propertyName + u"\"));";
- current.endInit.body << u"Q_ASSERT(" + refName + u".canAppend());";
- }
current.endInit.body << refName + u".append(" + value + u");";
} else {
addPropertyLine(propertyName, p, value, /* through QVariant = */ true);
@@ -1333,23 +1343,34 @@ void CodeGenerator::compileBinding(QQmlJSAotObject &current, const QmlIR::Bindin
// object binding separation also does not apply here
const QString objectName = makeGensym(u"sc"_qs);
Q_ASSERT(m_typeToObjectIndex.contains(bindingObject.type));
- Q_ASSERT(m_implicitComponentMapping.contains(
- int(m_typeToObjectIndex[bindingObject.type])));
- const int index =
- m_implicitComponentMapping[int(m_typeToObjectIndex[bindingObject.type])];
+ const int objectIndex = int(m_typeToObjectIndex[bindingObject.type]);
+ Q_ASSERT(m_componentIndices.contains(objectIndex));
+ const int index = m_componentIndices[objectIndex];
current.endInit.body << u"{"_qs;
current.endInit.body << QStringLiteral(
"auto thisContext = QQmlData::get(%1)->outerContext;")
.arg(qobjectParent);
current.endInit.body << QStringLiteral(
"auto %1 = QQmlObjectCreator::createComponent(engine, "
- "QQmlEnginePrivate::get(engine)->"
- "compilationUnitFromUrl(%2()), %3, %4, thisContext);")
- .arg(objectName, m_urlMethod.name,
+ "%2, %3, %4, thisContext);")
+ .arg(objectName,
+ CodeGeneratorUtility::compilationUnitVariable.name,
QString::number(index), qobjectParent);
current.endInit.body << QStringLiteral("thisContext->installContext(QQmlData::get(%1), "
"QQmlContextData::OrdinaryObject);")
.arg(objectName);
+ const auto isExplicitComponent = [](const QQmlJSScope::ConstPtr &type) {
+ auto base = QQmlJSScope::nonCompositeBaseType(type);
+ return base && base->internalName() == u"QQmlComponent"_qs;
+ };
+ if (!m_doc->stringAt(bindingObject.irObject->idNameIndex).isEmpty()
+ && isExplicitComponent(bindingObject.type)) {
+ current.endInit.body
+ << CodeGeneratorUtility::generate_setIdValue(
+ u"thisContext"_qs, bindingObject.irObject->id, objectName,
+ m_doc->stringAt(bindingObject.irObject->idNameIndex))
+ + u";";
+ }
setObjectBinding(objectName);
current.endInit.body << u"}"_qs;
break;
diff --git a/tools/qmltc/prototype/codegenerator.h b/tools/qmltc/prototype/codegenerator.h
index 5d37d7f78a..2d0a4e5388 100644
--- a/tools/qmltc/prototype/codegenerator.h
+++ b/tools/qmltc/prototype/codegenerator.h
@@ -74,8 +74,8 @@ private:
QHash<QQmlJSScope::ConstPtr, qsizetype> m_typeToObjectIndex; // TODO: remove this
// parents for each type that will (also) create the type
QHash<QQmlJSScope::ConstPtr, QQmlJSScope::ConstPtr> m_immediateParents;
- // mapping from to-be-wrapped object to the wrapper's object pseudo-index
- QHash<int, int> m_implicitComponentMapping;
+ // mapping from component-wrapped object to component index (real or not)
+ QHash<int, int> m_componentIndices;
QQmlJSAotMethod m_urlMethod;
diff --git a/tools/qmltc/prototype/codegeneratorutil.cpp b/tools/qmltc/prototype/codegeneratorutil.cpp
index 248a40eebf..3d7300e0d8 100644
--- a/tools/qmltc/prototype/codegeneratorutil.cpp
+++ b/tools/qmltc/prototype/codegeneratorutil.cpp
@@ -236,3 +236,12 @@ QString CodeGeneratorUtility::generate_getPrivateClass(const QString &accessor,
const QString privateType = p.privateClass();
return u"static_cast<" + privateType + u" *>(QObjectPrivate::get(" + accessor + u"))";
}
+
+QString CodeGeneratorUtility::generate_setIdValue(const QString &context, qsizetype index,
+ const QString &accessor, const QString &idString)
+{
+ Q_ASSERT(index >= 0);
+ const QString idComment = u"/* id: " + idString + u" */";
+ return context + u"->setIdValue(" + QString::number(index) + idComment + u", " + accessor
+ + u")";
+}
diff --git a/tools/qmltc/prototype/codegeneratorutil.h b/tools/qmltc/prototype/codegeneratorutil.h
index 74529f6853..3730f29abd 100644
--- a/tools/qmltc/prototype/codegeneratorutil.h
+++ b/tools/qmltc/prototype/codegeneratorutil.h
@@ -121,6 +121,8 @@ struct CodeGeneratorUtility
const QString &overloaded);
static QString generate_addressof(const QString &addressed);
static QString generate_getPrivateClass(const QString &accessor, const QQmlJSMetaProperty &p);
+ static QString generate_setIdValue(const QString &context, qsizetype index,
+ const QString &accessor, const QString &idString);
};
#endif // CODEGENERATORUTIL_H
diff --git a/tools/qmltc/prototype/qml2cppdefaultpasses.cpp b/tools/qmltc/prototype/qml2cppdefaultpasses.cpp
index 85edec372c..22767eee45 100644
--- a/tools/qmltc/prototype/qml2cppdefaultpasses.cpp
+++ b/tools/qmltc/prototype/qml2cppdefaultpasses.cpp
@@ -164,6 +164,12 @@ static decltype(auto) findIrLocation(const QmlIR::Document *doc, InputIterator f
Q_LOGGING_CATEGORY(lcDefaultPasses, "qml.qmltc.compilerpasses", QtWarningMsg);
+static bool isComponent(const QQmlJSScope::ConstPtr &type)
+{
+ auto base = QQmlJSScope::nonCompositeBaseType(type);
+ return base && base->internalName() == u"QQmlComponent"_qs;
+}
+
void verifyTypes(const Qml2CppContext &context, QList<Qml2CppObject> &objects)
{
const auto verifyProperty = [&](const QQmlJSMetaProperty &property,
@@ -241,6 +247,13 @@ void verifyTypes(const Qml2CppContext &context, QList<Qml2CppObject> &objects)
const auto verifyBinding = [&](const QmlIR::Binding &binding,
const QQmlJSScope::ConstPtr &type) {
+ // QQmlComponent-wrapped types are special. consider:
+ // `Component { QtObject {} }`
+ // Component doesn't have a default property so this is an error in
+ // normal code
+ if (isComponent(type))
+ return;
+
QString propName = context.document->stringAt(binding.propertyNameIndex);
if (propName.isEmpty()) {
Q_ASSERT(type);
@@ -840,6 +853,23 @@ QSet<QString> findCppIncludes(const Qml2CppContext &context, QList<Qml2CppObject
return cppIncludes;
}
+QHash<int, int> findAndResolveExplicitComponents(const Qml2CppContext &context,
+ QList<Qml2CppObject> &objects)
+{
+ QHash<int, int> identity;
+ // NB: unlike in the case of implicit components, we only need to look at
+ // the objects array and ignore the bindings
+ for (Qml2CppObject &o : objects) {
+ if (isComponent(o.type)) {
+ o.irObject->flags |= QV4::CompiledData::Object::IsComponent;
+ Q_ASSERT(context.typeIndices->contains(o.type));
+ const int index = int(context.typeIndices->value(o.type, -1));
+ identity[index] = index;
+ }
+ }
+ return identity;
+}
+
template<typename Update>
static void updateImplicitComponents(const Qml2CppContext &context, Qml2CppObject &object,
QList<Qml2CppObject> &objects, Update update)
@@ -878,11 +908,15 @@ static void updateImplicitComponents(const Qml2CppContext &context, Qml2CppObjec
QHash<int, int> findAndResolveImplicitComponents(const Qml2CppContext &context,
QList<Qml2CppObject> &objects)
{
- // TODO: this pass is incomplete. Other cases include component definitions:
- // `Component { Item {} }` and maybe something else
int syntheticComponentCount = 0;
QHash<int, int> indexMapping;
const auto setQQmlComponentFlag = [&](Qml2CppObject &object, int objectIndex) {
+ if (object.irObject->flags & QV4::CompiledData::Object::IsComponent) {
+ // this ir object is *already* marked as Component. which means it
+ // is the case of explicit component bound to Component property:
+ // property Component p: Component{ ... }
+ return;
+ }
object.irObject->flags |= QV4::CompiledData::Object::IsComponent;
Q_ASSERT(!indexMapping.contains(objectIndex));
// TODO: the mapping construction is very ad-hoc, it could be that the
@@ -898,15 +932,36 @@ QHash<int, int> findAndResolveImplicitComponents(const Qml2CppContext &context,
return indexMapping;
}
-static void setObjectId(const Qml2CppContext &context, int objectIndex,
- QHash<int, int> &idToObjectIndex)
+static void setObjectId(const Qml2CppContext &context, const QList<Qml2CppObject> &objects,
+ int objectIndex, QHash<int, int> &idToObjectIndex)
{
- // TODO: this method is basically a copy-paste of
+ // TODO: this method is basically a (modified) version of
// QQmlComponentAndAliasResolver::collectIdsAndAliases()
- QmlIR::Object *irObject = context.document->objectAt(objectIndex);
+ const auto isImplicitComponent = [](const Qml2CppObject &object) {
+ // special (to this code) way to detect implicit components after
+ // findAndResolveImplicitComponents() is run: unlike
+ // QQmlComponentAndAliasResolver we do *not* create synthetic
+ // components, but instead mark existing objects with IsComponent flag.
+ // this gives a bad side effect (for the logic here) that we cannot
+ // really distinguish between implicit and explicit components anymore
+ return object.irObject->flags & QV4::CompiledData::Object::IsComponent
+ && !isComponent(object.type);
+ };
+
+ const Qml2CppObject &object = objects.at(objectIndex);
+ Q_ASSERT(object.irObject == context.document->objectAt(objectIndex));
+ QmlIR::Object *irObject = object.irObject;
+ Q_ASSERT(object.type); // assume verified
Q_ASSERT(irObject); // assume verified
+ if (isImplicitComponent(object)) {
+ // Note: somehow QmlIR ensures that implicit components have no
+ // idNameIndex set when setting ids for the document root. this logic
+ // can't do it, so reject implicit components straight away instead
+ return;
+ }
+
if (irObject->idNameIndex != 0) {
if (idToObjectIndex.contains(irObject->idNameIndex)) {
context.recordError(irObject->location, u"Object id is not unique"_qs);
@@ -928,18 +983,28 @@ static void setObjectId(const Qml2CppContext &context, int objectIndex,
&& binding.type != QV4::CompiledData::Binding::Type_GroupProperty) {
return;
}
- setObjectId(context, binding.value.objectIndex, idToObjectIndex);
+ setObjectId(context, objects, binding.value.objectIndex, idToObjectIndex);
});
}
void setObjectIds(const Qml2CppContext &context, QList<Qml2CppObject> &objects)
{
Q_UNUSED(objects);
+
QHash<int, int> idToObjectIndex;
- Q_ASSERT(objects.at(0).irObject == context.document->objectAt(0));
- // NB: unlike QQmlTypeCompiler, only set id for the root, completely
- // ignoring the Components
- setObjectId(context, 0, idToObjectIndex);
+ const auto set = [&](int index) {
+ idToObjectIndex.clear();
+ Q_ASSERT(objects.at(index).irObject == context.document->objectAt(index));
+ setObjectId(context, objects, index, idToObjectIndex);
+ };
+
+ // NB: in reality, we need to do the same also for implicit components, but
+ // for now this is good enough
+ for (qsizetype i = 1; i < objects.size(); ++i) {
+ if (isComponent(objects[i].type))
+ set(i);
+ }
+ set(0);
}
QHash<QQmlJSScope::ConstPtr, QQmlJSScope::ConstPtr>
diff --git a/tools/qmltc/prototype/qml2cppdefaultpasses.h b/tools/qmltc/prototype/qml2cppdefaultpasses.h
index 068ab0b24d..c178ef7eff 100644
--- a/tools/qmltc/prototype/qml2cppdefaultpasses.h
+++ b/tools/qmltc/prototype/qml2cppdefaultpasses.h
@@ -64,6 +64,13 @@ QSet<QQmlJSMetaProperty> deferredResolveValidateAliases(const Qml2CppContext &co
// finds all required C++ include files that are needed for the generated C++
QSet<QString> findCppIncludes(const Qml2CppContext &context, QList<Qml2CppObject> &objects);
+// finds and resolves explicit QQmlComponent types. returns (essentially) a set
+// of QmlIR::Object indices that represent types derived from QQmlComponent. the
+// return value is a QHash<> to be compatible with
+// findAndResolveImplicitComponents()
+QHash<int, int> findAndResolveExplicitComponents(const Qml2CppContext &context,
+ QList<Qml2CppObject> &objects);
+
// finds and resolves implicit QQmlComponent types. returns a mapping from
// QmlIR::Object that is being wrapped into a QQmlComponent to an index of that
// implicit wrapper, which is a synthetic QmlIR::Object