diff options
author | Andrei Golubev <andrei.golubev@qt.io> | 2022-04-28 11:03:09 +0200 |
---|---|---|
committer | Andrei Golubev <andrei.golubev@qt.io> | 2022-06-02 10:07:39 +0200 |
commit | 26179e693717535eeff2e7b2a6ee99dc02dcd8f9 (patch) | |
tree | d546e470e6047165bff96209e5a8d913bc3abf75 /tools | |
parent | b85ac05e4ab3ef356ef59f6c57f44855edf15c5c (diff) |
Address extension types in qmltc
Whenever working with properties (reading, writing, aliasing, etc.)
of the type that has extensions, prefer the same-named properties
from extension objects over type-owned properties (this is the internal
QML mechanism)
To achieve that, we need to query the extension object:
* for Q_GADGETs use a dummy model of assuming we can cast the object
to the extension type and use that
* for Q_OBJECTs use a qmlExtendedObject() with additional logic of
figuring out which extension should be picked in each specific case
Create QQmlProxyMetaObject via a custom dynamic meta object API for
qmltc-compiled objects that are derived from base types with
extensions
Task-number: QTBUG-91956
Change-Id: I5e783768ae2abdb9dddf894de7e79960244352bd
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'tools')
-rw-r--r-- | tools/qmltc/qmltccodewriter.cpp | 1 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.cpp | 43 | ||||
-rw-r--r-- | tools/qmltc/qmltccompilerpieces.cpp | 93 | ||||
-rw-r--r-- | tools/qmltc/qmltccompilerpieces.h | 36 |
4 files changed, 159 insertions, 14 deletions
diff --git a/tools/qmltc/qmltccodewriter.cpp b/tools/qmltc/qmltccodewriter.cpp index 946ac8a735..471348b44d 100644 --- a/tools/qmltc/qmltccodewriter.cpp +++ b/tools/qmltc/qmltccodewriter.cpp @@ -168,6 +168,7 @@ void QmltcCodeWriter::writeGlobalHeader(QmltcOutputWrapper &code, const QString code.rawAppendToCpp(u""); code.rawAppendToCpp(u"#include <private/qobject_p.h>"); // NB: for private properties code.rawAppendToCpp(u"#include <private/qqmlobjectcreator_p.h>"); // for finalize callbacks + code.rawAppendToCpp(u"#include <QtQml/qqmlprivate.h>"); // QQmlPrivate::qmlExtendedObject() code.rawAppendToCpp(u""); // blank line code.rawAppendToCpp(u"QT_USE_NAMESPACE // avoid issues with QT_NAMESPACE"); diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp index 95dabe7f5c..99e43bc161 100644 --- a/tools/qmltc/qmltccompiler.cpp +++ b/tools/qmltc/qmltccompiler.cpp @@ -194,6 +194,7 @@ void QmltcCompiler::compileType( typeCountMethod.body << u"return " + generator.generate_typeCount() + u";"; current.typeCount = typeCountMethod; } + // make QQmltcObjectCreationHelper a friend of every type since it provides // useful helper methods for all types current.otherCode << u"friend class QT_PREPEND_NAMESPACE(QQmltcObjectCreationHelper);"_s; @@ -535,6 +536,7 @@ struct AliasResolutionFrame { static QString inVar; QStringList prologue; + QStringList epilogue; QStringList epilogueForWrite; QString outVar; }; @@ -584,7 +586,8 @@ void QmltcCompiler::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &a i = 0; // we use it in property processing // first frame is a dummy one: - frames.push(AliasResolutionFrame { QStringList(), QStringList(), u"this"_s }); + frames.push( + AliasResolutionFrame { QStringList(), QStringList(), QStringList(), u"this"_s }); }; aliasVisitor.processResolvedId = [&](const QQmlJSScope::ConstPtr &type) { Q_ASSERT(type); @@ -616,10 +619,15 @@ void QmltcCompiler::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &a } }; aliasVisitor.processResolvedProperty = [&](const QQmlJSMetaProperty &p, - const QQmlJSScope::ConstPtr &) { + const QQmlJSScope::ConstPtr &owner) { AliasResolutionFrame queryPropertyFrame {}; - QString inVar = QmltcCodeGenerator::wrap_privateClass(AliasResolutionFrame::inVar, p); + auto [extensionPrologue, extensionAccessor, extensionEpilogue] = + QmltcCodeGenerator::wrap_extensionType( + owner, p, + QmltcCodeGenerator::wrap_privateClass(AliasResolutionFrame::inVar, p)); + QString inVar = extensionAccessor; + queryPropertyFrame.prologue += extensionPrologue; if (p.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Value) { // we need to read the property to a local variable and then // write the updated value once the actual operation is done @@ -636,6 +644,7 @@ void QmltcCompiler::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &a inVar += u"->" + p.read() + u"()"; // update } queryPropertyFrame.outVar = inVar; + queryPropertyFrame.epilogue += extensionEpilogue; frames.push(queryPropertyFrame); }; @@ -652,14 +661,22 @@ void QmltcCompiler::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &a // instead, add a custom frame AliasResolutionFrame customFinalFrame {}; - customFinalFrame.outVar = - QmltcCodeGenerator::wrap_privateClass(frames.top().outVar, result.property); + auto [extensionPrologue, extensionAccessor, extensionEpilogue] = + QmltcCodeGenerator::wrap_extensionType( + result.owner, result.property, + QmltcCodeGenerator::wrap_privateClass(frames.top().outVar, + result.property)); + customFinalFrame.prologue = extensionPrologue; + customFinalFrame.outVar = extensionAccessor; + customFinalFrame.epilogue = extensionEpilogue; frames.push(customFinalFrame); } const QString latestAccessor = frames.top().outVar; const QStringList prologue = joinFrames(frames, [](const AliasResolutionFrame &frame) { return frame.prologue; }); + const QStringList epilogue = + joinFrames(frames, [](const AliasResolutionFrame &frame) { return frame.epilogue; }); const QString underlyingType = (result.kind == QQmlJSUtils::AliasTarget_Property) ? getUnderlyingType(result.property) : result.owner->internalName() + u" *"; @@ -678,6 +695,7 @@ void QmltcCompiler::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &a getter.body << u"return " + latestAccessor + u"->" + result.property.read() + u"();"; else // AliasTarget_Object getter.body << u"return " + latestAccessor + u";"; + getter.body += epilogue; getter.userVisible = true; current.functions.emplaceBack(getter); mocLines << u"READ"_s << getter.name; @@ -710,6 +728,7 @@ void QmltcCompiler::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &a + u"(%1)"_s.arg(commaSeparatedParameterNames) + u";"; setter.body += joinFrames( frames, [](const AliasResolutionFrame &frame) { return frame.epilogueForWrite; }); + setter.body += epilogue; // NB: *after* epilogueForWrite - see prologue construction setter.userVisible = true; current.functions.emplaceBack(setter); mocLines << u"WRITE"_s << setter.name; @@ -722,6 +741,7 @@ void QmltcCompiler::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &a bindable.name = compilationData.bindable; bindable.body += prologue; bindable.body << u"return " + latestAccessor + u"->" + bindableName + u"()" + u";"; + bindable.body += epilogue; bindable.userVisible = true; current.functions.emplaceBack(bindable); mocLines << u"BINDABLE"_s << bindable.name; @@ -730,17 +750,26 @@ void QmltcCompiler::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &a if (QString notifyName = result.property.notify(); !notifyName.isEmpty()) { Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise + auto notifyFrames = frames; + notifyFrames.pop(); // we don't need the last frame at all in this case + + const QStringList notifyPrologue = joinFrames( + frames, [](const AliasResolutionFrame &frame) { return frame.prologue; }); + const QStringList notifyEpilogue = joinFrames( + frames, [](const AliasResolutionFrame &frame) { return frame.epilogue; }); + // notify is very special current.endInit.body << u"{ // alias notify connection:"_s; - current.endInit.body += prologue; + current.endInit.body += notifyPrologue; // TODO: use non-private accessor since signals must exist on the public // type, not on the private one -- otherwise, you can't connect to a // private property signal in C++ and so it is useless (hence, use // public type) - const QString latestAccessorNonPrivate = frames[frames.size() - 2].outVar; + const QString latestAccessorNonPrivate = notifyFrames.top().outVar; current.endInit.body << u"QObject::connect(" + latestAccessorNonPrivate + u", &" + result.owner->internalName() + u"::" + notifyName + u", this, &" + current.cppType + u"::" + compilationData.notify + u");"; + current.endInit.body += notifyEpilogue; current.endInit.body << u"}"_s; } diff --git a/tools/qmltc/qmltccompilerpieces.cpp b/tools/qmltc/qmltccompilerpieces.cpp index d7ccd7cbf0..5df293892c 100644 --- a/tools/qmltc/qmltccompilerpieces.cpp +++ b/tools/qmltc/qmltccompilerpieces.cpp @@ -36,6 +36,87 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; +static QString scopeName(const QQmlJSScope::ConstPtr &scope) +{ + Q_ASSERT(scope->isFullyResolved()); + const auto scopeType = scope->scopeType(); + if (scopeType == QQmlJSScope::GroupedPropertyScope + || scopeType == QQmlJSScope::AttachedPropertyScope) { + return scope->baseType()->internalName(); + } + return scope->internalName(); +} + +QmltcCodeGenerator::PreparedValue +QmltcCodeGenerator::wrap_extensionType(const QQmlJSScope::ConstPtr &type, + const QQmlJSMetaProperty &p, const QString &accessor) +{ + Q_ASSERT(type->isFullyResolved()); + + QStringList prologue; + QString value = accessor; + QStringList epilogue; + + auto [owner, ownerKind] = QQmlJSScope::ownerOfProperty(type, p.propertyName()); + Q_ASSERT(owner); + Q_ASSERT(owner->isFullyResolved()); + + // properties are only visible when we use QML_{NAMESPACE_}EXTENDED + if (ownerKind == QQmlJSScope::ExtensionType) { + // extensions is a C++-only feature: + Q_ASSERT(!owner->isComposite()); + + // have to wrap the property into an extension, but we need to figure + // out whether the type is QObject-based or not + bool inheritsQObject = false; + for (auto t = QQmlJSScope::nonCompositeBaseType(type); t; t = t->baseType()) { + if (t->internalName() == u"QObject"_s) { + inheritsQObject = true; + break; + } + } + + prologue << u"{"_s; + const QString extensionObjectName = u"extObject"_s; + if (inheritsQObject) { + // we have a Q_OBJECT. in this case, we call qmlExtendedObject() + // function that should return us the extension object. for that we + // have to figure out which specific extension we want here + + int extensionIndex = 0; + auto cppBase = QQmlJSScope::nonCompositeBaseType(type); + for (auto t = cppBase; t; t = t->baseType()) { + if (auto [ext, kind] = t->extensionType(); kind != QQmlJSScope::NotExtension) { + if (ext->isSameType(owner)) + break; + ++extensionIndex; + } + } + + prologue << u"static_assert(std::is_base_of<%1, %2>::value);"_s.arg(u"QObject"_s, + scopeName(type)); + prologue << u"auto %1 = qobject_cast<%2 *>(QQmlPrivate::qmlExtendedObject(%3, %4));"_s + .arg(extensionObjectName, owner->internalName(), accessor, + QString::number(extensionIndex)); + } else { + // we have a Q_GADGET. the assumption for extension types is that we + // can reinterpret_cast a Q_GADGET object into an extension type + // object and then interact with the extension object right away + prologue << u"static_assert(sizeof(%1) == sizeof(%2));"_s.arg(scopeName(type), + owner->internalName()); + prologue << u"static_assert(alignof(%1) == alignof(%2));"_s.arg(scopeName(type), + owner->internalName()); + prologue << u"auto %1 = reinterpret_cast<%2 *>(%3);"_s.arg( + extensionObjectName, owner->internalName(), accessor); + } + prologue << u"Q_ASSERT(%1);"_s.arg(extensionObjectName); + value = extensionObjectName; + epilogue << u"}"_s; + } + + return { prologue, value, epilogue }; +} + void QmltcCodeGenerator::generate_assignToProperty(QStringList *block, const QQmlJSScope::ConstPtr &type, const QQmlJSMetaProperty &p, @@ -61,8 +142,14 @@ void QmltcCodeGenerator::generate_assignToProperty(QStringList *block, auto [prologue, wrappedValue, epilogue] = QmltcCodeGenerator::wrap_mismatchingTypeConversion(p, value); *block += prologue; - *block << QmltcCodeGenerator::wrap_privateClass(accessor, p) + u"->" + propertySetter + u"(" - + wrappedValue + u");"; + + auto [extensionPrologue, extensionAccessor, extensionEpilogue] = + QmltcCodeGenerator::wrap_extensionType( + type, p, QmltcCodeGenerator::wrap_privateClass(accessor, p)); + *block += extensionPrologue; + *block << extensionAccessor + u"->" + propertySetter + u"(" + wrappedValue + u");"; + *block += extensionEpilogue; + *block += epilogue; } else { // TODO: we should remove this branch eventually // this property is weird, fallback to `setProperty` @@ -154,7 +241,7 @@ void QmltcCodeGenerator::generate_createBindingOnProperty( } } -std::tuple<QStringList, QString, QStringList> +QmltcCodeGenerator::PreparedValue QmltcCodeGenerator::wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p, QString value) { auto isDerivedFromBuiltin = [](QQmlJSScope::ConstPtr t, const QString &builtin) { diff --git a/tools/qmltc/qmltccompilerpieces.h b/tools/qmltc/qmltccompilerpieces.h index 1e0f19ad19..2544e5fd9f 100644 --- a/tools/qmltc/qmltccompilerpieces.h +++ b/tools/qmltc/qmltccompilerpieces.h @@ -38,8 +38,6 @@ #include "qmltcoutputir.h" #include "qmltcvisitor.h" -#include <tuple> - QT_BEGIN_NAMESPACE /*! @@ -104,8 +102,16 @@ struct QmltcCodeGenerator static inline void generate_getCompilationUnitFromUrl(); - static std::tuple<QStringList, QString, QStringList> - wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p, QString value); + struct PreparedValue + { + QStringList prologue; + QString value; + QStringList epilogue; + }; + + static PreparedValue wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p, QString value); + static PreparedValue wrap_extensionType(const QQmlJSScope::ConstPtr &type, + const QQmlJSMetaProperty &p, const QString &accessor); static QString wrap_privateClass(const QString &accessor, const QQmlJSMetaProperty &p); static QString wrap_qOverload(const QList<QmltcVariable> ¶meters, @@ -231,6 +237,28 @@ inline decltype(auto) QmltcCodeGenerator::generate_initCode(QmltcType ¤t, u"<unknown>"_s); } + // if type has an extension, create a dynamic meta object for it + bool hasExtension = false; + for (auto cppBase = QQmlJSScope::nonCompositeBaseType(type); cppBase; + cppBase = cppBase->baseType()) { + // QObject is special: we have a pseudo-extension on it due to builtins + if (cppBase->internalName() == u"QObject"_s) + break; + if (cppBase->extensionType().extensionSpecifier != QQmlJSScope::NotExtension) { + hasExtension = true; + break; + } + } + if (hasExtension) { + current.init.body << u"{"_s; + current.init.body << u"auto cppData = QmltcTypeData(this);"_s; + current.init.body + << QStringLiteral( + "qmltcCreateDynamicMetaObject(this, &%1::staticMetaObject, cppData);") + .arg(type->internalName()); + current.init.body << u"}"_s; + } + const auto generateFinalLines = [¤t, isDocumentRoot]() { if (isDocumentRoot) { current.init.body << u"// 4. finish the document root creation"_s; |