qmltc: Handle (simple) deferred properties correctly

Simple deferred properties occur quite often in QML (throughout
Qt Quick, for example), so qmltc should be able to deal with
them as with deferred properties

Ignore generalized group properties, PropertyChanges and similar
types for now. They require more testing and are well out of
scope of the tech preview

Pick-to: 6.3
Task-number: QTBUG-100053
Change-Id: I0f3588789d188cd6bec81de0b61d3205b665a917
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Andrei Golubev 2021-12-30 13:29:41 +01:00
parent 4df79aa9db
commit 68924be5b5
13 changed files with 389 additions and 1 deletions

View File

@ -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

View File

@ -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<QQuickItem *> TypeWithDeferredProperty::bindableDeferredProperty()
{
return QBindable<QQuickItem *>(&m_deferredProperty);
}
TestTypeAttachedWithDeferred *DeferredAttached::qmlAttachedProperties(QObject *parent)
{
return new TestTypeAttachedWithDeferred(parent);
}

View File

@ -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 <QtCore/qobject.h>
#include <QtCore/qproperty.h>
#include <QtQml/qqmlregistration.h>
#include <QtQuick/qquickitem.h>
#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<QQuickItem *> m_deferredProperty { nullptr };
public:
TypeWithDeferredProperty(QObject *parent = nullptr) : QObject(parent) { }
QQuickItem *deferredProperty() const;
void setDeferredProperty(QQuickItem *);
QBindable<QQuickItem *> 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<int> m_deferred { 0 };
public:
int getDeferred() const { return m_deferred; }
void setDeferred(int v) { m_deferred = v; }
QBindable<int> bindableDeferred() const { return QBindable<int>(&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<int> m_deferred { 0 };
public:
TestTypeAttachedWithDeferred(QObject *parent) : TestTypeAttached(parent) { }
int getDeferred() const { return m_deferred; }
void setDeferred(int v) { m_deferred = v; }
QBindable<int> bindableDeferred() const { return QBindable<int>(&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

View File

@ -26,6 +26,9 @@
**
****************************************************************************/
#ifndef TYPEWITHPROPERTIES_H
#define TYPEWITHPROPERTIES_H
#include <QtCore/qobject.h>
#include <QtCore/qproperty.h>
#include <QtCore/qstring.h>
@ -70,3 +73,5 @@ Q_SIGNALS:
void cWeirdSignal(QVariant);
void dSignal(QString, int);
};
#endif // TYPEWITHPROPERTIES_H

View File

@ -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
}
}
}

View File

@ -0,0 +1,6 @@
import QtQuick
import QmltcTests 1.0
QtObject {
DeferredAttached.attachedFormula: 43 + 10 - (5 * 2)
DeferredAttached.deferred: 42
}

View File

@ -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
}

View File

@ -0,0 +1,6 @@
import QtQuick
import QmltcTests 1.0
TypeWithDeferredGroup {
group.str: "foo" + "bar"
group.deferred: 42
}

View File

@ -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<QQuickRectangle *>(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<QQuickRectangle *>(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<TestTypeAttachedWithDeferred *>(
qmlAttachedPropertiesObject<DeferredAttached>(&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<DeferredAttached>(&created, false));
qmlExecuteDeferred(&created);
TestTypeAttachedWithDeferred *attached = qobject_cast<TestTypeAttachedWithDeferred *>(
qmlAttachedPropertiesObject<DeferredAttached>(&created, false));
QVERIFY(attached);
QCOMPARE(attached->getAttachedFormula(), 20);
QCOMPARE(attached->getDeferred(), 100);
}
}
void tst_qmltc::signalHandlers()
{
QQmlEngine e;

View File

@ -53,6 +53,7 @@ private slots:
void ids();
void importNamespace();
void componentTypes();
void deferredProperties();
void signalHandlers();
void jsFunctions();

View File

@ -291,6 +291,7 @@ void CodeGenerator::constructObjects(QSet<QString> &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 &current, 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 &current, 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;
}

View File

@ -1079,3 +1079,41 @@ QSet<QQmlJSScope::ConstPtr> collectIgnoredTypes(const Qml2CppContext &context,
return ignored;
}
static void setDeferred(const Qml2CppContext &context, qsizetype objectIndex,
QList<Qml2CppObject> &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<Qml2CppObject> &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);
}

View File

@ -86,4 +86,6 @@ findImmediateParents(const Qml2CppContext &context, QList<Qml2CppObject> &object
QSet<QQmlJSScope::ConstPtr> collectIgnoredTypes(const Qml2CppContext &context,
QList<Qml2CppObject> &objects);
void setDeferredBindings(const Qml2CppContext &context, QList<Qml2CppObject> &objects);
#endif // QML2CPPPASSES_H