diff --git a/tests/auto/qml/qmltc/data/CMakeLists.txt b/tests/auto/qml/qmltc/data/CMakeLists.txt index 816164022e..c58378aa3f 100644 --- a/tests/auto/qml/qmltc/data/CMakeLists.txt +++ b/tests/auto/qml/qmltc/data/CMakeLists.txt @@ -8,6 +8,8 @@ set(cpp_sources cpptypes/private/testprivateproperty_p.h cpptypes/typewithproperties.h cpptypes/typewithproperties.cpp + # deferred: + cpptypes/deferredpropertytypes.h cpptypes/deferredpropertytypes.cpp ) set(qml_sources @@ -65,6 +67,10 @@ set(qml_sources privatePropertySubclass.qml calqlatrBits.qml propertyChangeAndSignalHandlers.qml + deferredProperties.qml + deferredProperties_group.qml + deferredProperties_attached.qml + deferredProperties_complex.qml # support types: DefaultPropertySingleChild.qml @@ -108,6 +114,8 @@ qt6_add_qml_module(qmltc_test_module QML_FILES ${qml_sources} ${js_sources} + DEPENDENCIES + QtQuick ) qt_internal_target_compile_qml_to_cpp(qmltc_test_module NAMESPACE QmltcTest diff --git a/tests/auto/qml/qmltc/data/cpptypes/deferredpropertytypes.cpp b/tests/auto/qml/qmltc/data/cpptypes/deferredpropertytypes.cpp new file mode 100644 index 0000000000..4f66172cca --- /dev/null +++ b/tests/auto/qml/qmltc/data/cpptypes/deferredpropertytypes.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "deferredpropertytypes.h" + +QQuickItem *TypeWithDeferredProperty::deferredProperty() const +{ + return m_deferredProperty; +} + +void TypeWithDeferredProperty::setDeferredProperty(QQuickItem *value) +{ + if (m_deferredProperty != value) + m_deferredProperty = value; +} + +QBindable TypeWithDeferredProperty::bindableDeferredProperty() +{ + return QBindable(&m_deferredProperty); +} + +TestTypeAttachedWithDeferred *DeferredAttached::qmlAttachedProperties(QObject *parent) +{ + return new TestTypeAttachedWithDeferred(parent); +} diff --git a/tests/auto/qml/qmltc/data/cpptypes/deferredpropertytypes.h b/tests/auto/qml/qmltc/data/cpptypes/deferredpropertytypes.h new file mode 100644 index 0000000000..24d7d46aaa --- /dev/null +++ b/tests/auto/qml/qmltc/data/cpptypes/deferredpropertytypes.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2022 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef DEFERREDPROPERTYTYPES_H +#define DEFERREDPROPERTYTYPES_H + +#include +#include +#include +#include + +#include "testgroupedtype.h" +#include "testattachedtype.h" + +// normal properties: + +class TypeWithDeferredProperty : public QObject +{ + Q_OBJECT + QML_ELEMENT + Q_CLASSINFO("DeferredPropertyNames", "deferredProperty") + + Q_PROPERTY(QQuickItem *deferredProperty READ deferredProperty WRITE setDeferredProperty BINDABLE + bindableDeferredProperty) + + QProperty m_deferredProperty { nullptr }; + +public: + TypeWithDeferredProperty(QObject *parent = nullptr) : QObject(parent) { } + + QQuickItem *deferredProperty() const; + void setDeferredProperty(QQuickItem *); + QBindable bindableDeferredProperty(); +}; + +// group properties: + +class TestTypeGroupedWithDeferred : public TestTypeGrouped +{ + Q_OBJECT + Q_PROPERTY(int deferred READ getDeferred WRITE setDeferred BINDABLE bindableDeferred) + QML_ANONYMOUS + Q_CLASSINFO("DeferredPropertyNames", "deferred") + + QProperty m_deferred { 0 }; + +public: + int getDeferred() const { return m_deferred; } + void setDeferred(int v) { m_deferred = v; } + QBindable bindableDeferred() const { return QBindable(&m_deferred); } +}; + +class TypeWithDeferredGroup : public QObject +{ + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(TestTypeGroupedWithDeferred *group READ getGroup) + + TestTypeGroupedWithDeferred m_group; + +public: + TypeWithDeferredGroup(QObject *parent = nullptr) : QObject(parent) { } + + TestTypeGroupedWithDeferred *getGroup() { return &m_group; } +}; + +// attached properties: + +class TestTypeAttachedWithDeferred : public TestTypeAttached +{ + Q_OBJECT + Q_PROPERTY(int deferred READ getDeferred WRITE setDeferred BINDABLE bindableDeferred) + QML_ANONYMOUS + Q_CLASSINFO("DeferredPropertyNames", "deferred") + + QProperty m_deferred { 0 }; + +public: + TestTypeAttachedWithDeferred(QObject *parent) : TestTypeAttached(parent) { } + + int getDeferred() const { return m_deferred; } + void setDeferred(int v) { m_deferred = v; } + QBindable bindableDeferred() const { return QBindable(&m_deferred); } +}; + +class DeferredAttached : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_ATTACHED(TestTypeAttachedWithDeferred) + +public: + DeferredAttached(QObject *parent = nullptr) : QObject(parent) { } + + static TestTypeAttachedWithDeferred *qmlAttachedProperties(QObject *); +}; + +// special: + +class TypeWithDeferredComplexProperties : public TypeWithDeferredGroup +{ + Q_OBJECT + QML_ELEMENT + Q_CLASSINFO("DeferredPropertyNames", "group,DeferredAttached") + +public: + TypeWithDeferredComplexProperties(QObject *parent = nullptr) : TypeWithDeferredGroup(parent) { } +}; + +#endif // DEFERREDPROPERTYTYPES_H diff --git a/tests/auto/qml/qmltc/data/cpptypes/typewithproperties.h b/tests/auto/qml/qmltc/data/cpptypes/typewithproperties.h index 51d7eca015..64625103f4 100644 --- a/tests/auto/qml/qmltc/data/cpptypes/typewithproperties.h +++ b/tests/auto/qml/qmltc/data/cpptypes/typewithproperties.h @@ -26,6 +26,9 @@ ** ****************************************************************************/ +#ifndef TYPEWITHPROPERTIES_H +#define TYPEWITHPROPERTIES_H + #include #include #include @@ -70,3 +73,5 @@ Q_SIGNALS: void cWeirdSignal(QVariant); void dSignal(QString, int); }; + +#endif // TYPEWITHPROPERTIES_H diff --git a/tests/auto/qml/qmltc/data/deferredProperties.qml b/tests/auto/qml/qmltc/data/deferredProperties.qml new file mode 100644 index 0000000000..7b977744e2 --- /dev/null +++ b/tests/auto/qml/qmltc/data/deferredProperties.qml @@ -0,0 +1,16 @@ +import QtQuick +import QmltcTests 1.0 +TypeWithDeferredProperty { + id: root + property int width: 42 + + deferredProperty: Rectangle { + width: root.width * 2 + implicitHeight: 4 + height: implicitHeight + Rectangle { + width: root.width // + parent.width + height: parent.height + } + } +} diff --git a/tests/auto/qml/qmltc/data/deferredProperties_attached.qml b/tests/auto/qml/qmltc/data/deferredProperties_attached.qml new file mode 100644 index 0000000000..875deb5778 --- /dev/null +++ b/tests/auto/qml/qmltc/data/deferredProperties_attached.qml @@ -0,0 +1,6 @@ +import QtQuick +import QmltcTests 1.0 +QtObject { + DeferredAttached.attachedFormula: 43 + 10 - (5 * 2) + DeferredAttached.deferred: 42 +} diff --git a/tests/auto/qml/qmltc/data/deferredProperties_complex.qml b/tests/auto/qml/qmltc/data/deferredProperties_complex.qml new file mode 100644 index 0000000000..ce5429a095 --- /dev/null +++ b/tests/auto/qml/qmltc/data/deferredProperties_complex.qml @@ -0,0 +1,9 @@ +import QtQuick +import QmltcTests 1.0 +TypeWithDeferredComplexProperties { + group.str: "still immediate" + group.deferred: -1 + + DeferredAttached.attachedFormula: Math.abs(10 * 2) + DeferredAttached.deferred: 100 +} diff --git a/tests/auto/qml/qmltc/data/deferredProperties_group.qml b/tests/auto/qml/qmltc/data/deferredProperties_group.qml new file mode 100644 index 0000000000..a162968c86 --- /dev/null +++ b/tests/auto/qml/qmltc/data/deferredProperties_group.qml @@ -0,0 +1,6 @@ +import QtQuick +import QmltcTests 1.0 +TypeWithDeferredGroup { + group.str: "foo" + "bar" + group.deferred: 42 +} diff --git a/tests/auto/qml/qmltc/tst_qmltc.cpp b/tests/auto/qml/qmltc/tst_qmltc.cpp index f9220417eb..ef92dd5c74 100644 --- a/tests/auto/qml/qmltc/tst_qmltc.cpp +++ b/tests/auto/qml/qmltc/tst_qmltc.cpp @@ -40,6 +40,10 @@ #include "importnamespace.h" #include "componenttype.h" #include "componenttypes.h" +#include "deferredproperties.h" +#include "deferredproperties_group.h" +#include "deferredproperties_attached.h" +#include "deferredproperties_complex.h" #include "signalhandlers.h" #include "javascriptfunctions.h" @@ -131,6 +135,10 @@ void tst_qmltc::initTestCase() QUrl("qrc:/QmltcTests/ObjectWithId.qml"), QUrl("qrc:/QmltcTests/documentWithIds.qml"), QUrl("qrc:/QmltcTests/importNamespace.qml"), + QUrl("qrc:/QmltcTests/deferredProperties.qml"), + QUrl("qrc:/QmltcTests/deferredProperties_group.qml"), + QUrl("qrc:/QmltcTests/deferredProperties_attached.qml"), + QUrl("qrc:/QmltcTests/deferredProperties_complex.qml"), QUrl("qrc:/QmltcTests/signalHandlers.qml"), QUrl("qrc:/QmltcTests/javaScriptFunctions.qml"), @@ -584,6 +592,73 @@ void tst_qmltc::componentTypes() } } +void tst_qmltc::deferredProperties() +{ + { + QQmlEngine e; + PREPEND_NAMESPACE(deferredProperties) created(&e); + QVERIFY(created.deferredProperty() + == nullptr); // binding is not applied since it is deferred + + qmlExecuteDeferred(&created); + + QQuickRectangle *rect = qobject_cast(created.deferredProperty()); + QVERIFY(rect); + QCOMPARE(rect->width(), created.width() * 2); + QCOMPARE(rect->implicitHeight(), 4); + QCOMPARE(rect->height(), rect->implicitHeight()); + + QQmlListReference children(rect, "data"); + QCOMPARE(children.size(), 1); + QQuickRectangle *subRect = qobject_cast(children.at(0)); + QVERIFY(subRect); + QCOMPARE(subRect->width(), created.width()); + QCOMPARE(subRect->height(), rect->height()); + } + { + QQmlEngine e; + PREPEND_NAMESPACE(deferredProperties_group) created(&e); + QCOMPARE(created.getGroup()->getStr(), u"foobar"_qs); + QCOMPARE(created.getGroup()->getDeferred(), 0); + // Note: we can't easily evaluate a deferred binding for a + // `group.deferred` here, so just accept the fact the the value is not + // set at all as a successful test + } + { + QQmlEngine e; + PREPEND_NAMESPACE(deferredProperties_attached) created(&e); + TestTypeAttachedWithDeferred *attached = qobject_cast( + qmlAttachedPropertiesObject(&created, false)); + QVERIFY(attached); + + QCOMPARE(attached->getAttachedFormula(), 43); + QCOMPARE(attached->getDeferred(), 0); + // Note: we can't easily evaluate a deferred binding for a + // `group.deferred` here, so just accept the fact the the value is not + // set at all as a successful test + } + { + QQmlEngine e; + PREPEND_NAMESPACE(deferredProperties_complex) created(&e); + + // `group` binding is not deferred as per current behavior outside of + // PropertyChanges and friends. we defer `group.deferred` binding though + QCOMPARE(created.getGroup()->getStr(), u"still immediate"_qs); + QCOMPARE(created.getGroup()->getDeferred(), 0); + + QVERIFY(!qmlAttachedPropertiesObject(&created, false)); + + qmlExecuteDeferred(&created); + + TestTypeAttachedWithDeferred *attached = qobject_cast( + qmlAttachedPropertiesObject(&created, false)); + QVERIFY(attached); + + QCOMPARE(attached->getAttachedFormula(), 20); + QCOMPARE(attached->getDeferred(), 100); + } +} + void tst_qmltc::signalHandlers() { QQmlEngine e; diff --git a/tests/auto/qml/qmltc/tst_qmltc.h b/tests/auto/qml/qmltc/tst_qmltc.h index ffffc6d9b7..5ce8a189da 100644 --- a/tests/auto/qml/qmltc/tst_qmltc.h +++ b/tests/auto/qml/qmltc/tst_qmltc.h @@ -53,6 +53,7 @@ private slots: void ids(); void importNamespace(); void componentTypes(); + void deferredProperties(); void signalHandlers(); void jsFunctions(); diff --git a/tools/qmltc/prototype/codegenerator.cpp b/tools/qmltc/prototype/codegenerator.cpp index 5a05661828..213b68b085 100644 --- a/tools/qmltc/prototype/codegenerator.cpp +++ b/tools/qmltc/prototype/codegenerator.cpp @@ -291,6 +291,7 @@ void CodeGenerator::constructObjects(QSet &requiredCppIncludes) m_ignoredTypes = collectIgnoredTypes(context, objects); }; executor.addPass(setIgnoredTypes); + executor.addPass(&setDeferredBindings); // run all passes: executor.run(m_logger); @@ -650,6 +651,17 @@ void CodeGenerator::compileObject( + u"(engine, /* finalize */ false);"; compiled.endInit.body << u"}"_qs; } + + if (object.irObject->flags & QV4::CompiledData::Object::HasDeferredBindings) { + compiled.endInit.body << u"{ // defer bindings"_qs; + compiled.endInit.body << u"auto ddata = QQmlData::get(this);"_qs; + compiled.endInit.body << u"auto thisContext = ddata->outerContext;"_qs; + compiled.endInit.body << u"Q_ASSERT(thisContext);"_qs; + compiled.endInit.body << u"ddata->deferData(" + QString::number(objectIndex) + u", " + + CodeGeneratorUtility::compilationUnitVariable.name + u", thisContext);"; + compiled.endInit.body << u"}"_qs; + } + // TODO: decide whether begin/end property update group is needed // compiled.endInit.body << u"Qt::beginPropertyUpdateGroup(); // defer binding evaluation"_qs; @@ -1168,6 +1180,29 @@ void CodeGenerator::compileBinding(QQmlJSAotObject ¤t, const QmlIR::Bindin const CodeGenObject &object, const CodeGenerator::AccessorData &accessor) { + // Note: unlike QQmlObjectCreator, we don't have to do a complicated + // deferral logic for bindings: if a binding is deferred, it is not compiled + // (potentially, with all the bindings inside of it), period. + if (binding.flags & QV4::CompiledData::Binding::IsDeferredBinding) { + if (binding.type == QmlIR::Binding::Type_GroupProperty) { + // TODO: we should warn about this in QmlCompiler library + qCWarning(lcCodeGenerator) + << QStringLiteral("Binding at line %1 column %2 is not deferred as it is a " + "binding on a group property.") + .arg(QString::number(binding.location.line), + QString::number(binding.location.column)); + // we do not support PropertyChanges and other types with similar + // behavior yet, so this binding is compiled + } else { + qCDebug(lcCodeGenerator) + << QStringLiteral( + "Binding at line %1 column %2 is deferred and thus not compiled") + .arg(QString::number(binding.location.line), + QString::number(binding.location.column)); + return; + } + } + // TODO: cache property name somehow, so we don't need to look it up again QString propertyName = m_doc->stringAt(binding.propertyNameIndex); if (propertyName.isEmpty()) { @@ -1413,9 +1448,10 @@ void CodeGenerator::compileBinding(QQmlJSAotObject ¤t, const QmlIR::Bindin // compile bindings of the attached property auto sortedBindings = toOrderedSequence( irObject->bindingsBegin(), irObject->bindingsEnd(), irObject->bindingCount()); - for (auto it : qAsConst(sortedBindings)) + for (auto it : qAsConst(sortedBindings)) { compileBinding(current, *it, attachedObject, { object.type, attachedMemberName, propertyName, false }); + } } break; } diff --git a/tools/qmltc/prototype/qml2cppdefaultpasses.cpp b/tools/qmltc/prototype/qml2cppdefaultpasses.cpp index 586cc85754..611c317490 100644 --- a/tools/qmltc/prototype/qml2cppdefaultpasses.cpp +++ b/tools/qmltc/prototype/qml2cppdefaultpasses.cpp @@ -1079,3 +1079,41 @@ QSet collectIgnoredTypes(const Qml2CppContext &context, return ignored; } + +static void setDeferred(const Qml2CppContext &context, qsizetype objectIndex, + QList &objects) +{ + Q_UNUSED(objects); + + Qml2CppObject &o = objects[objectIndex]; + + // c.f. QQmlDeferredAndCustomParserBindingScanner::scanObject() + if (o.irObject->flags & QV4::CompiledData::Object::IsComponent) { + // unlike QmlIR compiler, qmltc should not care about anything within a + // component (let the QQmlComponent wrapper - at runtime anyway - take + // care of this type instead) + return; + } + + const auto setRecursive = [&](QmlIR::Binding &binding) { + if (binding.type >= QmlIR::Binding::Type_Object) + setDeferred(context, binding.value.objectIndex, objects); // Note: recursive call here! + + const QString propName = findPropertyName(context, o.type, binding); + Q_ASSERT(!propName.isEmpty()); + + if (o.type->isNameDeferred(propName)) { + binding.flags |= QV4::CompiledData::Binding::IsDeferredBinding; + o.irObject->flags |= QV4::CompiledData::Object::HasDeferredBindings; + } + }; + + std::for_each(o.irObject->bindingsBegin(), o.irObject->bindingsEnd(), setRecursive); +} + +void setDeferredBindings(const Qml2CppContext &context, QList &objects) +{ + // as we do not support InlineComponents just yet, we can shortcut the logic + // here to only work with root object + setDeferred(context, 0, objects); +} diff --git a/tools/qmltc/prototype/qml2cppdefaultpasses.h b/tools/qmltc/prototype/qml2cppdefaultpasses.h index 29e5acf985..443292b30f 100644 --- a/tools/qmltc/prototype/qml2cppdefaultpasses.h +++ b/tools/qmltc/prototype/qml2cppdefaultpasses.h @@ -86,4 +86,6 @@ findImmediateParents(const Qml2CppContext &context, QList &object QSet collectIgnoredTypes(const Qml2CppContext &context, QList &objects); +void setDeferredBindings(const Qml2CppContext &context, QList &objects); + #endif // QML2CPPPASSES_H