aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAndrei Golubev <andrei.golubev@qt.io>2022-04-28 11:03:09 +0200
committerAndrei Golubev <andrei.golubev@qt.io>2022-06-02 10:07:39 +0200
commit26179e693717535eeff2e7b2a6ee99dc02dcd8f9 (patch)
treed546e470e6047165bff96209e5a8d913bc3abf75 /tools
parentb85ac05e4ab3ef356ef59f6c57f44855edf15c5c (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.cpp1
-rw-r--r--tools/qmltc/qmltccompiler.cpp43
-rw-r--r--tools/qmltc/qmltccompilerpieces.cpp93
-rw-r--r--tools/qmltc/qmltccompilerpieces.h36
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 &current, 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 &current, 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 &current, 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 &current, 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 &current, 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 &current, 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 &current, 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 &current, 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> &parameters,
@@ -231,6 +237,28 @@ inline decltype(auto) QmltcCodeGenerator::generate_initCode(QmltcType &current,
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 = [&current, isDocumentRoot]() {
if (isDocumentRoot) {
current.init.body << u"// 4. finish the document root creation"_s;