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:
parent
4df79aa9db
commit
68924be5b5
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import QtQuick
|
||||
import QmltcTests 1.0
|
||||
QtObject {
|
||||
DeferredAttached.attachedFormula: 43 + 10 - (5 * 2)
|
||||
DeferredAttached.deferred: 42
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import QtQuick
|
||||
import QmltcTests 1.0
|
||||
TypeWithDeferredGroup {
|
||||
group.str: "foo" + "bar"
|
||||
group.deferred: 42
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -53,6 +53,7 @@ private slots:
|
|||
void ids();
|
||||
void importNamespace();
|
||||
void componentTypes();
|
||||
void deferredProperties();
|
||||
|
||||
void signalHandlers();
|
||||
void jsFunctions();
|
||||
|
|
|
@ -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 ¤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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue