diff options
author | Andrei Golubev <andrei.golubev@qt.io> | 2021-07-23 16:53:27 +0200 |
---|---|---|
committer | Andrei Golubev <andrei.golubev@qt.io> | 2022-01-26 08:10:08 +0100 |
commit | f03f6eec9ac6467cdf601a8c056e76b3905472f3 (patch) | |
tree | eac49736e8afa364989f65423ff592fad7347771 /tests/auto/qml/qmltc_manual | |
parent | 8ec6b990e13c8b41d0415af83c5890ef7a9ee227 (diff) |
Prototype private property access in tst_qmltc_manual
In theory, private properties could be supported in the compilers
through the QMetaProperty. Since we already know each C++ property's
index (and QML properties cannot be private), we can use that index
for a fast-ish QMetaProperty lookup. QMetaProperty itself has read and
write methods, which should be sufficient for all the use cases:
* read a value
* write a value
* create a binding
In fact, binding creation on non-grouped private properties is already
supported (or almost supported) since bindings already rely on
QMetaProperty information.
As a downside, QMetaProperty (even without name lookup) is still an
overkill compared to
static_cast<ClassPrivate *>(QObjectPrivate::get(this)) and then calling
READ/WRITE/etc. of a property directly
Task-number: QTBUG-91956
Change-Id: If77d2783ac161cb9bdd0bd9d0b397fe88e9c471d
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'tests/auto/qml/qmltc_manual')
-rw-r--r-- | tests/auto/qml/qmltc_manual/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tests/auto/qml/qmltc_manual/data/anchors.qml | 5 | ||||
-rw-r--r-- | tests/auto/qml/qmltc_manual/testclasses.h | 30 | ||||
-rw-r--r-- | tests/auto/qml/qmltc_manual/tst_qmltc_manual.cpp | 110 |
4 files changed, 146 insertions, 1 deletions
diff --git a/tests/auto/qml/qmltc_manual/CMakeLists.txt b/tests/auto/qml/qmltc_manual/CMakeLists.txt index 9df9b8ca12..a898a77c26 100644 --- a/tests/auto/qml/qmltc_manual/CMakeLists.txt +++ b/tests/auto/qml/qmltc_manual/CMakeLists.txt @@ -11,7 +11,7 @@ qt_internal_add_test(tst_qmltc_manual LIBRARIES Qt::CorePrivate Qt::QmlPrivate - Qt::Quick + Qt::QuickPrivate Qt::QuickTestUtilsPrivate ) diff --git a/tests/auto/qml/qmltc_manual/data/anchors.qml b/tests/auto/qml/qmltc_manual/data/anchors.qml new file mode 100644 index 0000000000..9250ee49cd --- /dev/null +++ b/tests/auto/qml/qmltc_manual/data/anchors.qml @@ -0,0 +1,5 @@ +import QtQuick 2.0 +Item { + property int value: 42 + anchors.topMargin: value // binding on anchors (private property) +} diff --git a/tests/auto/qml/qmltc_manual/testclasses.h b/tests/auto/qml/qmltc_manual/testclasses.h index eecc9287ae..c91f82c3f2 100644 --- a/tests/auto/qml/qmltc_manual/testclasses.h +++ b/tests/auto/qml/qmltc_manual/testclasses.h @@ -475,4 +475,34 @@ public: void finalize(QQmlEngine *e); }; +class ANON_anchors : public QQuickItem +{ + Q_OBJECT + QML_ANONYMOUS + Q_PROPERTY(int value READ getValue WRITE setValue NOTIFY valueChanged) +protected: + ANON_anchors(QObject *parent = nullptr); + +public: + // test workaround: the url is resolved by the test base class, so use + // member variable to store the resolved url used as argument in engine + // evaluation of runtime functions + static QUrl url; + + ANON_anchors(QQmlEngine *e, QObject *parent = nullptr); + QQmlRefPointer<QQmlContextData> init(QQmlEngine *e, + const QQmlRefPointer<QQmlContextData> &parentContext); + void finalize(QQmlEngine *e); + + QProperty<int> value; + int getValue() { return value; } + void setValue(int v) + { + value = v; + Q_EMIT valueChanged(); + } +Q_SIGNALS: + void valueChanged(); +}; + #endif // TESTCLASSES_H diff --git a/tests/auto/qml/qmltc_manual/tst_qmltc_manual.cpp b/tests/auto/qml/qmltc_manual/tst_qmltc_manual.cpp index 5628b6e1d4..c90e51ba61 100644 --- a/tests/auto/qml/qmltc_manual/tst_qmltc_manual.cpp +++ b/tests/auto/qml/qmltc_manual/tst_qmltc_manual.cpp @@ -39,9 +39,16 @@ #include <QtCore/qproperty.h> #include <QtQuickTestUtils/private/qmlutils_p.h> +#include <QtCore/qobjectdefs.h> +#include <QtCore/qmetaobject.h> + #include <private/qqmlengine_p.h> #include <private/qqmltypedata_p.h> #include <private/qqmlvmemetaobject_p.h> +#include <private/qqmlanybinding_p.h> +#include <private/qquickitem_p.h> +#include <private/qv4qmlcontext_p.h> +#include <private/qqmlproperty_p.h> #include <array> #include <memory> @@ -65,6 +72,7 @@ private slots: void locallyImported(); void localImport(); void neighbors(); + void anchors(); private: void signalHandlers_impl(const QUrl &url); @@ -496,6 +504,19 @@ void tst_qmltc_manual::neighbors() } } +void tst_qmltc_manual::anchors() +{ + QQmlEngine e; + ANON_anchors::url = testFileUrl("anchors.qml"); + ANON_anchors created(&e); + + QQuickAnchors *anchors = + static_cast<QQuickItemPrivate *>(QObjectPrivate::get(&created))->anchors(); + QCOMPARE(anchors->topMargin(), 42); + created.setValue(7); + QCOMPARE(anchors->topMargin(), 7); +} + // test workaround: hardcode runtime function indices. because they could be // rather unexpected and passing wrong ones leads to UB and flakiness. // @@ -541,6 +562,8 @@ static constexpr int LOCAL_IMPORT_LOCAL_GET_MAGIC_VALUE = 1; static constexpr int NEIGHBOUR_IDS_CHILD1_P2_BINDING = 0; static constexpr int NEIGHBOUR_IDS_CHILD2_P_BINDING = 1; + +static constexpr int ANCHORS_ANCHORS_TOP_MARGIN_BINDING = 0; }; // test utility function for type erasure. the "real" code would be @@ -565,6 +588,35 @@ static void typeEraseArguments(std::array<void *, Size> &a, std::array<QMetaType t = { /* types */ QMetaType::fromType<std::decay_t<IOArgs>>()... }; } +// test utility that fetches a QV4::Function from the engine +inline QV4::Function *getRuntimeFunction(QQmlEngine *engine, const QUrl &url, qsizetype index) +{ + QQmlEnginePrivate *priv = QQmlEnginePrivate::get(engine); + Q_ASSERT(priv); + const auto unit = priv->compilationUnitFromUrl(url); + return unit->runtimeFunctions.value(index, nullptr); +} + +// test utility that sets up the binding call arguments +template<typename CreateBinding> +inline decltype(auto) createBindingInScope(QQmlEngine *qmlengine, QObject *thisObject, + CreateBinding create) +{ + QV4::ExecutionEngine *v4 = qmlengine->handle(); + Q_ASSERT(v4); + + QQmlContext *ctx = qmlengine->contextForObject(thisObject); + if (!ctx) + ctx = qmlengine->rootContext(); + QV4::Scope scope(v4); + QV4::ExecutionContext *executionCtx = v4->scriptContext(); + QQmlRefPointer<QQmlContextData> ctxtdata = QQmlContextData::get(ctx); + QV4::Scoped<QV4::QmlContext> qmlContext( + scope, QV4::QmlContext::create(executionCtx, ctxtdata, thisObject)); + + return create(ctxtdata, qmlContext); +} + ContextRegistrator::ContextRegistrator(QQmlEngine *engine, QObject *This) { Q_ASSERT(engine && This); @@ -1172,6 +1224,64 @@ void ANON_neighbors::finalize(QQmlEngine *e) QUrl ANON_neighbors::url = QUrl(); // workaround +ANON_anchors::ANON_anchors(QObject *parent) : QQuickItem() +{ + setParent(parent); +} + +ANON_anchors::ANON_anchors(QQmlEngine *e, QObject *parent) : ANON_anchors(parent) +{ + // NB: use e->rootContext() as this object is document root + init(e, QQmlContextData::get(e->rootContext())); +} + +QQmlRefPointer<QQmlContextData> +ANON_anchors::init(QQmlEngine *e, const QQmlRefPointer<QQmlContextData> &parentContext) +{ + constexpr int componentIndex = 0; // root index + auto context = ContextRegistrator::create(e, url, parentContext, componentIndex); + ContextRegistrator::set(this, context, QQmlContextData::DocumentRoot); + finalize(e); // call here because it's document root + return context; +} + +void ANON_anchors::finalize(QQmlEngine *e) +{ + Q_UNUSED(e); + + this->value = 42; + + // create binding (in a proper way) on Item's anchors through + // QQmlAnyBinding. if this is achievable through C++, then compiler could do + // it as well (by generating roughly the same code). + const QMetaObject *mo = this->metaObject(); + QVERIFY(mo); + // fetching QMetaProperty is possible through the compiler + QMetaProperty anchorsProperty = mo->property(mo->indexOfProperty("anchors")); + QQuickAnchors *anchors = qvariant_cast<QQuickAnchors *>(anchorsProperty.read(this)); + QVERIFY(anchors); + + // below is binding-specific code that is part of a special qmltc library + auto v4Func = getRuntimeFunction(qmlEngine(this), url, + FunctionIndices::ANCHORS_ANCHORS_TOP_MARGIN_BINDING); + QVERIFY(v4Func); + const QMetaObject *anchorsMo = anchors->metaObject(); + QVERIFY(anchorsMo); + QMetaProperty topMarginProperty = anchorsMo->property(anchorsMo->indexOfProperty("topMargin")); + QVERIFY(QByteArray(topMarginProperty.name()) == "topMargin"); + + createBindingInScope( + qmlEngine(this), this, + [&](const QQmlRefPointer<QQmlContextData> &ctxt, QV4::ExecutionContext *scope) { + QQmlBinding *binding = QQmlBinding::create(topMarginProperty.metaType(), v4Func, + this, ctxt, scope); + binding->setTarget(anchors, topMarginProperty.propertyIndex(), false, -1); + QQmlPropertyPrivate::setBinding(binding); + }); +} + +QUrl ANON_anchors::url = QUrl(); // workaround + QTEST_MAIN(tst_qmltc_manual) #include "tst_qmltc_manual.moc" |