From 362b4bf3d6df1e1799a163d3e6d7ee39f75ad080 Mon Sep 17 00:00:00 2001 From: Andrei Golubev Date: Tue, 14 Dec 2021 17:20:15 +0100 Subject: [PATCH] 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 Pick-to: 6.3 Change-Id: I8f5c74fc79380566475b1139d4cc5560fac123e3 Reviewed-by: Ulf Hermann --- tests/auto/qml/qmltc/CMakeLists.txt | 1 + tests/auto/qml/qmltc/data/documentWithIds.qml | 96 ++++++++++ tests/auto/qml/qmltc/data/properties.qml | 39 +++- tests/auto/qml/qmltc/tst_qmltc.cpp | 173 +++++++++++++++++- tests/auto/qml/qmltc/tst_qmltc.h | 2 +- tools/qmltc/prototype/codegenerator.cpp | 61 ++++-- tools/qmltc/prototype/codegenerator.h | 4 +- tools/qmltc/prototype/codegeneratorutil.cpp | 9 + tools/qmltc/prototype/codegeneratorutil.h | 2 + .../qmltc/prototype/qml2cppdefaultpasses.cpp | 87 +++++++-- tools/qmltc/prototype/qml2cppdefaultpasses.h | 7 + 11 files changed, 436 insertions(+), 45 deletions(-) create mode 100644 tests/auto/qml/qmltc/data/documentWithIds.qml diff --git a/tests/auto/qml/qmltc/CMakeLists.txt b/tests/auto/qml/qmltc/CMakeLists.txt index 1f58eaa3c9..92573f3fc6 100644 --- a/tests/auto/qml/qmltc/CMakeLists.txt +++ b/tests/auto/qml/qmltc/CMakeLists.txt @@ -20,6 +20,7 @@ set(qml_sources data/methods.qml data/properties.qml data/ObjectWithId.qml + data/documentWithIds.qml data/signalHandlers.qml data/javaScriptFunctions.qml diff --git a/tests/auto/qml/qmltc/data/documentWithIds.qml b/tests/auto/qml/qmltc/data/documentWithIds.qml new file mode 100644 index 0000000000..5936be96bd --- /dev/null +++ b/tests/auto/qml/qmltc/data/documentWithIds.qml @@ -0,0 +1,96 @@ +import QtQuick + +Item { + property QtObject rectProperty: Rectangle { + id: rectangle + objectName: "rectangle" + } + + Row { + id: row + objectName: "row" + + Rectangle { + Text { + id: textInRectangle + objectName: "textInRectangle" + } + } + + property list listOfObjects: [ + Item { + id: itemInList + objectName: "itemInList" + + property QtObject foobarProperty: QtObject { id: foobar; objectName: "foobar" } + }, + QtObject { + id: objectInList + objectName: "objectInList" + } + ] + + Item { + id: item + objectName: "item" + } + } + + property QtObject gridProperty: GridView { + id: gridView + objectName: "gridView" + } + + TableView { + id: tableView + objectName: "tableView" + + property Component before: Component { + id: beforeDelegate + Text { + id: beforeDelegateText + objectName: "beforeDelegateText" + } + } + Component { + id: beforeDelegateDefaultProperty + Text { + id: beforeDelegateDefaultPropertyText + objectName: "beforeDelegateDefaultPropertyText" + } + } + + delegate: Rectangle { + id: delegateRect + objectName: "delegateRect" + } + + Component { + id: afterDelegateDefaultProperty + Text { + id: afterDelegateDefaultPropertyText + objectName: "afterDelegateDefaultPropertyText" + } + } + property Component after: Component { + id: afterDelegate + Text { + id: afterDelegateText + objectName: "afterDelegateText" + } + } + } + + property QtObject explicitCompProperty: Component { + id: explicitComponent + Text { + id: explicitText + objectName: "explicitText" + } + } + + property QtObject sentinelProperty: QtObject { + id: sentinel + objectName: "sentinel" + } +} diff --git a/tests/auto/qml/qmltc/data/properties.qml b/tests/auto/qml/qmltc/data/properties.qml index 77ab781e5d..896cd88eac 100644 --- a/tests/auto/qml/qmltc/data/properties.qml +++ b/tests/auto/qml/qmltc/data/properties.qml @@ -5,6 +5,18 @@ QtObject { property double doubleP: 0.5 property int intP: 42 property list listQtObjP // always list of QML objects + listQtObjP: [ + Text { + id: listQtObjP_child_0 + text: "child0" + }, + QtObject { + property string what: "child1" + }, + Item { + Rectangle { id: listQtObjP_child_2_rect } + } + ] property real realP: 2.32 property string stringP: "hello, world" property url urlP: "https://www.qt.io/" @@ -27,10 +39,33 @@ QtObject { required property real requiredRealP // extra: - property Timer timerP - property list listNumP + property Timer timerP: Timer { + interval: 42 + } + property list listCompP // special: property QtObject nullObjP: null property var nullVarP: null + + // Component-wrapped + property QtObject table: TableView { + property Component before: Component { Text { text: "beforeDelegate" } } + delegate: Text { // implicit component + text: "delegate" + } + property Component after: Component { Text { text: "afterDelegate" } } + } + + property QtObject explicitCompP: Component { // explicit component + Text { + id: explicitText + text: "not a delegate" + } + } + + property QtObject sentinelForComponent: QtObject { + id: sentinel + property string text: "should be correctly created" + } } diff --git a/tests/auto/qml/qmltc/tst_qmltc.cpp b/tests/auto/qml/qmltc/tst_qmltc.cpp index 0a2622bc6b..56ca309491 100644 --- a/tests/auto/qml/qmltc/tst_qmltc.cpp +++ b/tests/auto/qml/qmltc/tst_qmltc.cpp @@ -36,6 +36,7 @@ #include "methods.h" #include "properties.h" #include "objectwithid.h" +#include "documentwithids.h" #include "signalhandlers.h" #include "javascriptfunctions.h" @@ -131,6 +132,7 @@ void tst_qmltc::initTestCase() QUrl("qrc:/QmltcTests/data/methods.qml"), QUrl("qrc:/QmltcTests/data/properties.qml"), QUrl("qrc:/QmltcTests/data/ObjectWithId.qml"), + QUrl("qrc:/QmltcTests/data/documentWithIds.qml"), QUrl("qrc:/QmltcTests/data/signalHandlers.qml"), QUrl("qrc:/QmltcTests/data/javaScriptFunctions.qml"), @@ -339,7 +341,11 @@ void tst_qmltc::properties() // extra: QCOMPARE(propertyMetaType("timerP"), QMetaType::fromType()); - QCOMPARE(propertyMetaType("listNumP"), QMetaType::fromType>()); + QCOMPARE(propertyMetaType("listCompP"), QMetaType::fromType>()); + + QCOMPARE(propertyMetaType("table"), QMetaType::fromType()); + QCOMPARE(propertyMetaType("explicitCompP"), QMetaType::fromType()); + QCOMPARE(propertyMetaType("sentinelForComponent"), QMetaType::fromType()); // now, test property values: QCOMPARE(created.boolP(), true); @@ -357,21 +363,170 @@ void tst_qmltc::properties() QCOMPARE(created.readonlyStringP(), u"foobar"_qs); + // object bindinds: + const auto objectCtx = e.contextForObject(&created); + QQmlListReference listQtObj(&created, "listQtObjP"); + QCOMPARE(listQtObj.size(), 3); + { + QQuickText *child0 = qobject_cast(listQtObj.at(0)); + QVERIFY(child0); + QCOMPARE(child0->text(), u"child0"_qs); + QCOMPARE(objectCtx->objectForName("listQtObjP_child_0"), child0); + + QObject *child1 = listQtObj.at(1); + QVERIFY(child1); + QCOMPARE(child1->property("what").toString(), u"child1"_qs); + + QQuickItem *child2 = qobject_cast(listQtObj.at(2)); + QVERIFY(child2); + QQmlListReference data(child2, "data"); + QCOMPARE(data.size(), 1); + QQuickRectangle *child2Rect = qobject_cast(data.at(0)); + QVERIFY(child2Rect); + QCOMPARE(objectCtx->objectForName("listQtObjP_child_2_rect"), child2Rect); + } + + QQmlTimer *timer = created.timerP(); + QVERIFY(timer); + QCOMPARE(timer->interval(), 42); + // nulls: QCOMPARE(created.nullObjP(), nullptr); QCOMPARE(created.nullVarP(), QVariant::fromValue(nullptr)); + + QQuickTableView *table = qobject_cast(created.table()); + QVERIFY(table); + { + QQmlComponent *beforeDelegate = qvariant_cast(table->property("before")); + QVERIFY(beforeDelegate); + QQmlComponent *delegate = table->delegate(); + QVERIFY(delegate); + QQmlComponent *afterDelegate = qvariant_cast(table->property("after")); + QVERIFY(afterDelegate); + + QScopedPointer beforeDelegateObject(beforeDelegate->create()); + QVERIFY(beforeDelegateObject); + QVERIFY(qobject_cast(beforeDelegateObject.get())); + QCOMPARE(beforeDelegateObject->property("text").toString(), u"beforeDelegate"_qs); + + QScopedPointer delegateObject(delegate->create()); + QVERIFY(delegateObject); + QVERIFY(qobject_cast(delegateObject.get())); + QCOMPARE(delegateObject->property("text").toString(), u"delegate"_qs); + + QScopedPointer afterDelegateObject(afterDelegate->create()); + QVERIFY(afterDelegateObject); + QVERIFY(qobject_cast(afterDelegateObject.get())); + QCOMPARE(afterDelegateObject->property("text").toString(), u"afterDelegate"_qs); + } + + QQmlComponent *explicitComp = qobject_cast(created.explicitCompP()); + QVERIFY(explicitComp); + QScopedPointer explicitCompObject(explicitComp->create()); + QVERIFY(explicitCompObject); + QVERIFY(qobject_cast(explicitCompObject.get())); + QCOMPARE(explicitCompObject->property("text").toString(), u"not a delegate"_qs); + + QObject *sentinelForComponent = created.sentinelForComponent(); + QVERIFY(sentinelForComponent); + QCOMPARE(sentinelForComponent->property("text").toString(), u"should be correctly created"_qs); } -void tst_qmltc::id() +void tst_qmltc::ids() { - QQmlEngine e; - PREPEND_NAMESPACE(ObjectWithId) created(&e); // shouldn't crash here + { + QQmlEngine e; + PREPEND_NAMESPACE(ObjectWithId) created(&e); // shouldn't crash here - auto objectCtx = QQmlContextData::get(e.contextForObject(&created)); - QVERIFY(objectCtx); - QCOMPARE(objectCtx->parent(), QQmlContextData::get(e.rootContext())); - QCOMPARE(objectCtx->asQQmlContext()->objectForName("objectWithId"), &created); - QCOMPARE(objectCtx->contextObject(), &created); + auto objectCtx = QQmlContextData::get(e.contextForObject(&created)); + QVERIFY(objectCtx); + QCOMPARE(objectCtx->parent(), QQmlContextData::get(e.rootContext())); + QCOMPARE(objectCtx->asQQmlContext()->objectForName("objectWithId"), &created); + QCOMPARE(objectCtx->contextObject(), &created); + } + + { + QQmlEngine e; + PREPEND_NAMESPACE(documentWithIds) created(&e); // shouldn't crash here + + auto ctx = e.contextForObject(&created); + QVERIFY(ctx); + auto objectCtx = QQmlContextData::get(ctx); + QVERIFY(objectCtx); + QCOMPARE(objectCtx->parent(), QQmlContextData::get(e.rootContext())); + QCOMPARE(objectCtx->contextObject(), &created); + + // first check that ids match object names + const auto objectNameById = [&ctx](const QString &id) { + auto object = ctx->objectForName(id); + if (!object) + return QString(); + return object->objectName(); + }; + + QCOMPARE(objectNameById("rectangle"), u"rectangle"_qs); + QCOMPARE(objectNameById("row"), u"row"_qs); + QCOMPARE(objectNameById("textInRectangle"), u"textInRectangle"_qs); + QCOMPARE(objectNameById("itemInList"), u"itemInList"_qs); + QCOMPARE(objectNameById("objectInList"), u"objectInList"_qs); + QCOMPARE(objectNameById("item"), u"item"_qs); + QCOMPARE(objectNameById("gridView"), u"gridView"_qs); + QCOMPARE(objectNameById("tableView"), u"tableView"_qs); + QCOMPARE(objectNameById("sentinel"), u"sentinel"_qs); + + const auto verifyComponent = [&](QQmlComponent *component, const QString &componentId, + const QString &objectId) { + QVERIFY(component); + if (!componentId.isEmpty()) // empty string for implicit components + QCOMPARE(ctx->objectForName(componentId), component); + QCOMPARE(ctx->objectForName(objectId), nullptr); + + QScopedPointer root(component->create()); + QCOMPARE(root->objectName(), objectId); + auto rootCtx = e.contextForObject(root.get()); + QVERIFY(rootCtx); + QCOMPARE(rootCtx->objectForName(objectId), root.get()); + }; + + auto explicitComponent = qobject_cast(created.explicitCompProperty()); + verifyComponent(explicitComponent, u"explicitComponent"_qs, u"explicitText"_qs); + + QQmlListReference children(&created, "data"); + QCOMPARE(children.size(), 2); + QQuickTableView *table = qobject_cast(children.at(1)); + QVERIFY(table); + QCOMPARE(ctx->objectForName(u"tableView"_qs), table); + QCOMPARE(table->objectName(), u"tableView"_qs); + + auto before = qvariant_cast(table->property("before")); + verifyComponent(before, u"beforeDelegate"_qs, u"beforeDelegateText"_qs); + auto after = qvariant_cast(table->property("after")); + verifyComponent(after, u"afterDelegate"_qs, u"afterDelegateText"_qs); + + auto delegate = table->delegate(); + verifyComponent(delegate, /* implicit component */ QString(), u"delegateRect"_qs); + + // TableView is really special when you add Component to a default + // property. see QQuickFlickablePrivate::data_append + QQmlComponent *beforeChild = nullptr; + QQmlComponent *afterChild = nullptr; + const auto tableChildren = table->children(); // QObject::children() + QVERIFY(tableChildren.size() >= 2); + for (QObject *child : tableChildren) { + auto comp = qobject_cast(child); + if (!comp) + continue; + // this is bad, but there doesn't seem to be any better choice + if (ctx->objectForName(u"beforeDelegateDefaultProperty"_qs) == comp) + beforeChild = comp; + else if (ctx->objectForName(u"afterDelegateDefaultProperty"_qs) == comp) + afterChild = comp; + } + // we just used ctx->objectForName() to find these components, so + // there's no point in checking the same condition in verifyComponent() + verifyComponent(beforeChild, QString(), u"beforeDelegateDefaultPropertyText"_qs); + verifyComponent(afterChild, QString(), u"afterDelegateDefaultPropertyText"_qs); + } } void tst_qmltc::signalHandlers() diff --git a/tests/auto/qml/qmltc/tst_qmltc.h b/tests/auto/qml/qmltc/tst_qmltc.h index f6c64a3160..9e0d732d4d 100644 --- a/tests/auto/qml/qmltc/tst_qmltc.h +++ b/tests/auto/qml/qmltc/tst_qmltc.h @@ -50,7 +50,7 @@ private slots: void enumerations(); void methods(); void properties(); - void id(); + void ids(); void signalHandlers(); void jsFunctions(); 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 &requiredCppIncludes) requiredCppIncludes = findCppIncludes(context, objects); }; executor.addPass(populateCppIncludes); + const auto resolveExplicitComponents = [&](const Qml2CppContext &context, + QList &objects) { + m_componentIndices.insert(findAndResolveExplicitComponents(context, objects)); + }; const auto resolveImplicitComponents = [&](const Qml2CppContext &context, QList &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 &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 ¤t, 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 ¤t, 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 m_typeToObjectIndex; // TODO: remove this // parents for each type that will (also) create the type QHash m_immediateParents; - // mapping from to-be-wrapped object to the wrapper's object pseudo-index - QHash m_implicitComponentMapping; + // mapping from component-wrapped object to component index (real or not) + QHash 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 &objects) { const auto verifyProperty = [&](const QQmlJSMetaProperty &property, @@ -241,6 +247,13 @@ void verifyTypes(const Qml2CppContext &context, QList &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 findCppIncludes(const Qml2CppContext &context, QList findAndResolveExplicitComponents(const Qml2CppContext &context, + QList &objects) +{ + QHash 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 static void updateImplicitComponents(const Qml2CppContext &context, Qml2CppObject &object, QList &objects, Update update) @@ -878,11 +908,15 @@ static void updateImplicitComponents(const Qml2CppContext &context, Qml2CppObjec QHash findAndResolveImplicitComponents(const Qml2CppContext &context, QList &objects) { - // TODO: this pass is incomplete. Other cases include component definitions: - // `Component { Item {} }` and maybe something else int syntheticComponentCount = 0; QHash 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 findAndResolveImplicitComponents(const Qml2CppContext &context, return indexMapping; } -static void setObjectId(const Qml2CppContext &context, int objectIndex, - QHash &idToObjectIndex) +static void setObjectId(const Qml2CppContext &context, const QList &objects, + int objectIndex, QHash &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 &objects) { Q_UNUSED(objects); + QHash 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 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 deferredResolveValidateAliases(const Qml2CppContext &co // finds all required C++ include files that are needed for the generated C++ QSet findCppIncludes(const Qml2CppContext &context, QList &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 findAndResolveExplicitComponents(const Qml2CppContext &context, + QList &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