aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAndrei Golubev <andrei.golubev@qt.io>2022-05-12 14:39:38 +0200
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2022-06-10 12:06:49 +0000
commit7373e6149f7e28fff1dfc5b5642d63aa4bb5a886 (patch)
tree90359e9ef7b2e130ac8df3ffab1289da7d4bc0df /tools
parent7360c1af181dc6ef3ae82a885ada0dc6919c85f7 (diff)
qmltc: Move from QmlIR::Binding to QQmlJSMetaPropertyBinding
This symbolizes the last piece of QmlIR dependency that qmltc has. We do still have some implicit dependencies on QmlIR, though, but that should go way once the remaining prototype code's logic is migrated to QmltcVisitor, QQmlJSScope and friends. This, however, is not attempted here as the patch itself is rather large In the process of switching to QQmlJSMetaPropertyBinding, observe and fix issues in QmltcVisitor and surroundings Change-Id: I752b68a7f57baf354de16dc0bb466a3f693a4e49 Reviewed-by: Maximilian Goldstein <max.goldstein@qt.io> (cherry picked from commit bf7aaeda87d409253f8d114273cc71f4244973af) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
Diffstat (limited to 'tools')
-rw-r--r--tools/qmltc/prototype/codegenerator.cpp931
-rw-r--r--tools/qmltc/prototype/codegenerator.h89
-rw-r--r--tools/qmltc/prototype/qml2cppdefaultpasses.cpp44
-rw-r--r--tools/qmltc/prototype/qml2cppdefaultpasses.h12
-rw-r--r--tools/qmltc/qmltccompiler.cpp699
-rw-r--r--tools/qmltc/qmltccompiler.h62
-rw-r--r--tools/qmltc/qmltccompilerpieces.cpp12
-rw-r--r--tools/qmltc/qmltccompilerpieces.h8
-rw-r--r--tools/qmltc/qmltcvisitor.cpp74
-rw-r--r--tools/qmltc/qmltcvisitor.h7
10 files changed, 743 insertions, 1195 deletions
diff --git a/tools/qmltc/prototype/codegenerator.cpp b/tools/qmltc/prototype/codegenerator.cpp
index 27585b75f7..9113f8c267 100644
--- a/tools/qmltc/prototype/codegenerator.cpp
+++ b/tools/qmltc/prototype/codegenerator.cpp
@@ -53,173 +53,6 @@ using namespace Qt::StringLiterals;
Q_LOGGING_CATEGORY(lcCodeGenerator, "qml.qmltc.compiler", QtWarningMsg);
-static QString figureReturnType(const QQmlJSMetaMethod &m)
-{
- const bool isVoidMethod =
- m.returnTypeName() == u"void" || m.methodType() == QQmlJSMetaMethod::Signal;
- Q_ASSERT(isVoidMethod || m.returnType());
- // TODO: should really be m.returnTypeName(). for now, avoid inconsistencies
- // due to QML/C++ name collisions and unique C++ name generation
- QString type;
- if (isVoidMethod) {
- type = u"void"_s;
- } else {
- type = m.returnType()->augmentedInternalName();
- }
- return type;
-}
-
-static QList<QmltcVariable>
-compileMethodParameters(const QStringList &names,
- const QList<QSharedPointer<const QQmlJSScope>> &types,
- bool allowUnnamed = false)
-{
- QList<QmltcVariable> paramList;
- const auto size = names.size();
- paramList.reserve(size);
- for (qsizetype i = 0; i < size; ++i) {
- Q_ASSERT(types[i]); // assume verified
- QString name = names[i];
- Q_ASSERT(allowUnnamed || !name.isEmpty()); // assume verified
- if (name.isEmpty() && allowUnnamed)
- name = u"unnamed_" + QString::number(i);
- paramList.emplaceBack(QmltcVariable { types[i]->augmentedInternalName(), name, QString() });
- }
- return paramList;
-}
-
-// returns an (absolute) index into a QV4::Function array of the compilation
-// unit which is used by QQmlEngine's executeRuntimeFunction() and friends
-static qsizetype relativeToAbsoluteRuntimeIndex(const QmlIR::Object *irObject, qsizetype relative)
-{
- return irObject->runtimeFunctionIndices.at(relative);
-}
-
-// finds property for given scope and returns it together with the absolute
-// property index in the property array of the corresponding QMetaObject.
-static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope,
- const QString &propertyName)
-{
- auto owner = QQmlJSScope::ownerOfProperty(scope, propertyName).scope;
- Q_ASSERT(owner);
- auto p = owner->ownProperty(propertyName);
- if (!p.isValid())
- return { p, -1 };
- int index = p.index();
- if (index < 0) // this property doesn't have index - comes from QML
- return { p, -1 };
- // owner is not included in absolute index
- for (QQmlJSScope::ConstPtr base = owner->baseType(); base; base = base->baseType())
- index += int(base->ownProperties().size());
- return { p, index };
-}
-
-struct QmlIrBindingCompare
-{
-private:
- using T = QmlIR::Binding;
- using I = typename QmlIR::PoolList<T>::Iterator;
- static QHash<uint, qsizetype> orderTable;
-
-public:
- bool operator()(const I &x, const I &y) const
- {
- return orderTable[x->type()] < orderTable[y->type()];
- }
- bool operator()(const T &x, const T &y) const
- {
- return orderTable[x.type()] < orderTable[y.type()];
- }
-};
-
-QHash<uint, qsizetype> QmlIrBindingCompare::orderTable = {
- { QmlIR::Binding::Type_Invalid, 100 },
- // value assignments (and object bindings) are "equal"
- { QmlIR::Binding::Type_Boolean, 0 },
- { QmlIR::Binding::Type_Number, 0 },
- { QmlIR::Binding::Type_String, 0 },
- { QmlIR::Binding::Type_Object, 0 },
- { QmlIR::Binding::Type_Null, 0 },
- // translations are also "equal" between themselves, but come after
- // assignments
- { QmlIR::Binding::Type_Translation, 1 },
- { QmlIR::Binding::Type_TranslationById, 1 },
- // attached properties and group properties might contain assignments
- // inside, so come next
- { QmlIR::Binding::Type_AttachedProperty, 2 },
- { QmlIR::Binding::Type_GroupProperty, 2 },
- // JS bindings come last because they can use values from other categories
- { QmlIR::Binding::Type_Script, 3 },
-};
-
-QList<typename QmlIR::PoolList<QmlIR::Binding>::Iterator>
-CodeGenerator::toOrderedSequence(typename QmlIR::PoolList<QmlIR::Binding>::Iterator first,
- typename QmlIR::PoolList<QmlIR::Binding>::Iterator last,
- qsizetype n)
-{
- // bindings actually have to sorted so that e.g. value assignments come
- // before script bindings. this is important for the code generator as it
- // would just emit instructions one by one, regardless of the ordering
- using I = typename QmlIR::PoolList<QmlIR::Binding>::Iterator;
- QList<I> sorted;
- sorted.reserve(n);
- for (auto it = first; it != last; ++it)
- sorted << it;
- // NB: use stable sort for bindings, because this matters: relative order of
- // bindings must be preserved - this might affect UI
- std::stable_sort(sorted.begin(), sorted.end(),
- QmlIrBindingCompare {}); // TODO: use stable_partition instead
- return sorted;
-}
-
-/* code generator helper functions: */
-static QStringList generate_assignToSpecialAlias(const QQmlJSScope::ConstPtr &type,
- const QString &propertyName,
- const QQmlJSMetaProperty &p, const QString &value,
- const QString &accessor)
-{
- Q_UNUSED(type);
- Q_UNUSED(propertyName);
-
- Q_ASSERT(p.isValid());
- Q_ASSERT(!p.isList()); // NB: this code does not handle list properties
- Q_ASSERT(p.isAlias());
- Q_ASSERT(p.type());
-
- QStringList code;
- code.reserve(6); // should always be enough
- // pretend there's a WRITE function
- QmltcPropertyData data(p);
- auto [prologue, wrappedValue, epilogue] =
- QmltcCodeGenerator::wrap_mismatchingTypeConversion(p, value);
- code += prologue;
- code << QmltcCodeGenerator::wrap_privateClass(accessor, p) + u"->" + data.write + u"("
- + wrappedValue + u");";
- code += epilogue;
- return code;
-}
-
-// magic variable, necessary for correct handling of object bindings: since
-// object creation and binding setup are separated across functions, we
-// fetch a subobject by: QObject::children().at(offset + localIndex)
-// ^
-// childrenOffsetVariable
-// this variable would often be equal to 0, but there's no guarantee. and it
-// is required due to non-trivial aliases dependencies: aliases can
-// reference any object in the document by id, which automatically means
-// that all ids have to be set up before we get to finalization (and the
-// only place for it is init)
-// NB: this variable would behave correctly as long as QML init and QML finalize
-// are non-virtual functions
-static const QmltcVariable childrenOffsetVariable { u"qsizetype"_s, u"QML_choffset"_s,
- QString() };
-
-// represents QV4::ExecutableCompilationUnit
-static const QmltcVariable compilationUnitVariable { u"QV4::ExecutableCompilationUnit *"_s,
- u"QML_cu"_s, QString() };
-
-Q_LOGGING_CATEGORY(lcCodeGen, "qml.compiler.CodeGenerator", QtWarningMsg);
-
CodeGenerator::CodeGenerator(const QString &url, QQmlJSLogger *logger, QmlIR::Document *doc,
const QmltcTypeResolver *localResolver, const QmltcVisitor *visitor,
const QmltcCompilerInfo *info)
@@ -264,18 +97,13 @@ void CodeGenerator::constructObjects(QSet<QString> &requiredCppIncludes)
m_typeToObjectIndex);
executor.addPass(&verifyTypes);
executor.addPass(&checkForNamingCollisionsWithCpp);
- executor.addPass([this](const Qml2CppContext &context, QList<Qml2CppObject> &objects) {
- m_typeCounts = makeUniqueCppNames(context, objects);
- });
+ executor.addPass(&makeUniqueCppNames);
const auto setupQmlBaseTypes = [&](const Qml2CppContext &context,
QList<Qml2CppObject> &objects) {
m_qmlCompiledBaseTypes = setupQmlCppTypes(context, objects);
};
executor.addPass(setupQmlBaseTypes);
- const auto resolveAliases = [&](const Qml2CppContext &context, QList<Qml2CppObject> &objects) {
- m_aliasesToIds = deferredResolveValidateAliases(context, objects);
- };
- executor.addPass(resolveAliases);
+ executor.addPass(&deferredResolveValidateAliases);
const auto populateCppIncludes = [&](const Qml2CppContext &context,
QList<Qml2CppObject> &objects) {
requiredCppIncludes = findCppIncludes(context, objects);
@@ -292,11 +120,6 @@ void CodeGenerator::constructObjects(QSet<QString> &requiredCppIncludes)
executor.addPass(resolveExplicitComponents);
executor.addPass(resolveImplicitComponents);
executor.addPass(&setObjectIds); // NB: must be after Component resolution
- const auto setImmediateParents = [&](const Qml2CppContext &context,
- QList<Qml2CppObject> &objects) {
- m_immediateParents = findImmediateParents(context, objects);
- };
- executor.addPass(setImmediateParents);
const auto setIgnoredTypes = [&](const Qml2CppContext &context, QList<Qml2CppObject> &objects) {
m_ignoredTypes = collectIgnoredTypes(context, objects);
};
@@ -308,10 +131,6 @@ void CodeGenerator::constructObjects(QSet<QString> &requiredCppIncludes)
void CodeGenerator::prepare(QSet<QString> *cppIncludes)
{
- const QString rootClassName = QFileInfo(m_url).baseName();
- Q_ASSERT(!rootClassName.isEmpty());
- m_isAnonymous = rootClassName.at(0).isLower();
-
constructObjects(*cppIncludes); // this populates all the codegen objects
}
@@ -327,752 +146,6 @@ bool CodeGenerator::ignoreObject(const CodeGenObject &object) const
return false;
}
-static QString generate_callCompilationUnit(const QString &urlMethodName)
-{
- // NB: assume `engine` variable always exists
- return u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(%1())"_s.arg(urlMethodName);
-}
-
-void CodeGenerator::compileBinding(QmltcType &current, const QmlIR::Binding &binding,
- const CodeGenObject &object,
- const CodeGenerator::AccessorData &accessor)
-{
- // 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()) {
- // if empty, try default property
- for (QQmlJSScope::ConstPtr t = object.type->baseType(); t && propertyName.isEmpty();
- t = t->baseType())
- propertyName = t->defaultPropertyName();
- }
- Q_ASSERT(!propertyName.isEmpty());
- QQmlJSMetaProperty p = object.type->property(propertyName);
- QQmlJSScope::ConstPtr propertyType = p.type();
- // Q_ASSERT(propertyType); // TODO: doesn't work with signals
-
- // 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 (object.type->isNameDeferred(propertyName)) {
- 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;
- }
- }
-
- const auto addPropertyLine = [&](const QString &propertyName, const QQmlJSMetaProperty &p,
- const QString &value, bool constructFromQObject = false) {
- // TODO: there mustn't be this special case. instead, alias resolution
- // must be done in QQmlJSImportVisitor subclass, that would handle this
- // mess (see resolveValidateOrSkipAlias() in qml2cppdefaultpasses.cpp)
- if (p.isAlias() && m_qmlCompiledBaseTypes.contains(object.type->baseTypeName())) {
- qCDebug(lcCodeGenerator) << u"Property '" + propertyName + u"' is an alias on type '"
- + object.type->internalName()
- + u"' which is a QML type compiled to C++. The assignment is special"
- + u"in this case";
- current.endInit.body += generate_assignToSpecialAlias(object.type, propertyName, p,
- value, accessor.name);
- } else {
- QmltcCodeGenerator::generate_assignToProperty(&current.endInit.body, object.type, p,
- value, accessor.name,
- constructFromQObject);
- }
- };
-
- switch (binding.type()) {
- case QmlIR::Binding::Type_Boolean: {
- addPropertyLine(propertyName, p, binding.value.b ? u"true"_s : u"false"_s);
- break;
- }
- case QmlIR::Binding::Type_Number: {
- QString value = m_doc->javaScriptCompilationUnit.bindingValueAsString(&binding);
- addPropertyLine(propertyName, p, value);
- break;
- }
- case QmlIR::Binding::Type_String: {
- const QString str = m_doc->stringAt(binding.stringIndex);
- addPropertyLine(propertyName, p, QQmlJSUtils::toLiteral(str));
- break;
- }
- case QmlIR::Binding::Type_TranslationById: { // TODO: add test
- const QV4::CompiledData::TranslationData &translation =
- m_doc->javaScriptCompilationUnit.unitData()
- ->translations()[binding.value.translationDataIndex];
- const QString id = m_doc->stringAt(binding.stringIndex);
- addPropertyLine(propertyName, p,
- u"qsTrId(\"" + id + u"\", " + QString::number(translation.number) + u")");
- break;
- }
- case QmlIR::Binding::Type_Translation: { // TODO: add test
- const QV4::CompiledData::TranslationData &translation =
- m_doc->javaScriptCompilationUnit.unitData()
- ->translations()[binding.value.translationDataIndex];
- int lastSlash = m_url.lastIndexOf(QChar(u'/'));
- const QStringView context = (lastSlash > -1)
- ? QStringView { m_url }.mid(lastSlash + 1, m_url.length() - lastSlash - 5)
- : QStringView();
- const QString comment = m_doc->stringAt(translation.commentIndex);
- const QString text = m_doc->stringAt(translation.stringIndex);
-
- addPropertyLine(propertyName, p,
- u"QCoreApplication::translate(\"" + context + u"\", \"" + text + u"\", \""
- + comment + u"\", " + QString::number(translation.number) + u")");
- break;
- }
- case QmlIR::Binding::Type_Script: {
- QString bindingSymbolName = object.type->internalName() + u'_' + propertyName + u"_binding";
- bindingSymbolName.replace(u'.', u'_'); // can happen with group properties
- compileScriptBinding(current, binding, bindingSymbolName, object, propertyName,
- propertyType, accessor);
- break;
- }
- case QmlIR::Binding::Type_Object: {
- // NB: object is compiled with compileObject, here just need to use it
- const CodeGenObject &bindingObject = m_objects.at(binding.value.objectIndex);
-
- // Note: despite a binding being set for `accessor`, we use "this" as a
- // parent of a created object. Both attached and grouped properties are
- // parented by "this", so lifetime-wise we should be fine. If not, we're
- // in a fairly deep trouble, actually, since it's essential to use
- // `this` (at least at present it seems to be so) - e.g. the whole logic
- // of separating object binding into object creation and object binding
- // setup relies on the fact that we use `this`
- const QString qobjectParent = u"this"_s;
- const QString objectAddr = accessor.name;
-
- if (binding.hasFlag(QmlIR::Binding::IsOnAssignment)) {
- const QString onAssignmentName = u"onAssign_" + propertyName;
- const auto uniqueId = UniqueStringId(current, onAssignmentName);
- if (!m_onAssignmentObjectsCreated.contains(uniqueId)) {
- m_onAssignmentObjectsCreated.insert(uniqueId);
- current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg(
- onAssignmentName, bindingObject.type->internalName(), qobjectParent);
- current.init.body << u"creator->set(%1, %2);"_s.arg(
- QString::number(m_visitor->creationIndex(bindingObject.type)),
- onAssignmentName);
-
- // static_cast is fine, because we (must) know the exact type
- current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg(
- onAssignmentName, bindingObject.type->internalName(),
- QString::number(m_visitor->creationIndex(bindingObject.type)));
-
- m_localChildrenToEndInit.append(onAssignmentName);
- m_localChildrenToFinalize.append(bindingObject.type);
- }
-
- // NB: we expect one "on" assignment per property, so creating
- // QQmlProperty each time should be fine (unlike QQmlListReference)
- current.endInit.body << u"{"_s;
- current.endInit.body << u"QQmlProperty qmlprop(" + objectAddr + u", QStringLiteral(\""
- + propertyName + u"\"));";
- current.endInit.body << u"QT_PREPEND_NAMESPACE(QQmlCppOnAssignmentHelper)::set("
- + onAssignmentName + u", qmlprop);";
- current.endInit.body << u"}"_s;
- break;
- }
-
- if (!propertyType) {
- recordError(binding.location,
- u"Binding on property '" + propertyName + u"' of unknown type");
- return;
- }
-
- if (p.isList() || binding.hasFlag(QmlIR::Binding::IsListItem)) {
- const QString refName = u"listref_" + propertyName;
- const auto uniqueId = UniqueStringId(current, refName);
- if (!m_listReferencesCreated.contains(uniqueId)) {
- m_listReferencesCreated.insert(uniqueId);
- // TODO: figure if Unicode support is needed here
- current.endInit.body << u"QQmlListReference " + refName + u"(" + objectAddr + u", "
- + QQmlJSUtils::toLiteral(propertyName, u"QByteArrayLiteral")
- + u");";
- current.endInit.body << u"Q_ASSERT(" + refName + u".canAppend());";
- }
- }
-
- const auto setObjectBinding = [&](const QString &value) {
- if (p.isList() || binding.hasFlag(QmlIR::Binding::IsListItem)) {
- const QString refName = u"listref_" + propertyName;
- current.endInit.body << refName + u".append(" + value + u");";
- } else {
- addPropertyLine(propertyName, p, value, /* fromQObject = */ true);
- }
- };
-
- if (bindingObject.irObject->flags & QV4::CompiledData::Object::IsComponent) {
- // NB: this one is special - and probably becomes a view delegate.
- // object binding separation also does not apply here
- const QString objectName = makeGensym(u"sc"_s);
- Q_ASSERT(m_typeToObjectIndex.contains(bindingObject.type));
- const int objectIndex = int(m_typeToObjectIndex[bindingObject.type]);
- Q_ASSERT(m_componentIndices.contains(objectIndex));
- const int index = m_componentIndices[objectIndex];
- current.endInit.body << u"{"_s;
- current.endInit.body << QStringLiteral(
- "auto thisContext = QQmlData::get(%1)->outerContext;")
- .arg(qobjectParent);
- current.endInit.body << QStringLiteral(
- "auto %1 = QQmlObjectCreator::createComponent(engine, "
- "%2, %3, %4, thisContext);")
- .arg(objectName,
- generate_callCompilationUnit(m_urlMethodName),
- QString::number(index), qobjectParent);
- current.endInit.body << QStringLiteral("thisContext->installContext(QQmlData::get(%1), "
- "QQmlContextData::OrdinaryObject);")
- .arg(objectName);
- const auto isComponentBased = [](const QQmlJSScope::ConstPtr &type) {
- auto base = QQmlJSScope::nonCompositeBaseType(type);
- return base && base->internalName() == u"QQmlComponent"_s;
- };
- if (!m_doc->stringAt(bindingObject.irObject->idNameIndex).isEmpty()
- && isComponentBased(bindingObject.type)) {
-
- QmltcCodeGenerator::generate_setIdValue(
- &current.endInit.body, u"thisContext"_s, bindingObject.irObject->id,
- objectName, m_doc->stringAt(bindingObject.irObject->idNameIndex));
- }
- setObjectBinding(objectName);
- current.endInit.body << u"}"_s;
- break;
- }
-
- const QString objectName = makeGensym(u"o"_s);
- current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg(
- objectName, bindingObject.type->internalName(), qobjectParent);
- current.init.body << u"creator->set(%1, %2);"_s.arg(
- QString::number(m_visitor->creationIndex(bindingObject.type)), objectName);
-
- // refetch the same object during endInit to set the bindings
- current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg(
- objectName, bindingObject.type->internalName(),
- QString::number(m_visitor->creationIndex(bindingObject.type)));
- setObjectBinding(objectName);
-
- m_localChildrenToEndInit.append(objectName);
- m_localChildrenToFinalize.append(bindingObject.type);
- break;
- }
- case QmlIR::Binding::Type_AttachedProperty: {
- Q_ASSERT(accessor.name == u"this"_s); // TODO: doesn't have to hold, in fact
- const auto attachedObject = m_objects.at(binding.value.objectIndex);
- const auto &irObject = attachedObject.irObject;
- const auto &type = attachedObject.type;
- Q_ASSERT(irObject && type);
- if (propertyName == u"Component"_s) {
- // TODO: there's a special QQmlComponentAttached, which has to be
- // called? c.f. qqmlobjectcreator.cpp's finalize()
- const auto compileComponent = [&](const QmlIR::Binding &b) {
- Q_ASSERT(b.type() == QmlIR::Binding::Type_Script);
- compileScriptBindingOfComponent(current, irObject, type, b,
- m_doc->stringAt(b.propertyNameIndex));
- };
- std::for_each(irObject->bindingsBegin(), irObject->bindingsEnd(), compileComponent);
- } else {
- const QString attachingTypeName = propertyName; // acts as an identifier
- auto attachingType = m_localTypeResolver->typeForName(attachingTypeName);
- Q_ASSERT(attachingType); // an error somewhere else
-
- QString attachedTypeName = type->attachedTypeName(); // TODO: check if == internalName?
- if (attachedTypeName.isEmpty()) // TODO: shouldn't happen ideally
- attachedTypeName = type->baseTypeName();
- const QString attachedMemberName = u"m_" + attachingTypeName;
- const auto uniqueId = UniqueStringId(current, attachedMemberName);
- // add attached type as a member variable to allow noop lookup
- if (!m_attachedTypesAlreadyRegistered.contains(uniqueId)) {
- m_attachedTypesAlreadyRegistered.insert(uniqueId);
- current.variables.emplaceBack(attachedTypeName + u" *", attachedMemberName,
- u"nullptr"_s);
- // Note: getting attached property is fairly expensive
- const QString getAttachedPropertyLine = u"qobject_cast<" + attachedTypeName
- + u" *>(qmlAttachedPropertiesObject<" + attachingType->internalName()
- + u">(this, /* create = */ true))";
- current.endInit.body
- << attachedMemberName + u" = " + getAttachedPropertyLine + u";";
- }
-
- // compile bindings of the attached property
- auto sortedBindings = toOrderedSequence(
- irObject->bindingsBegin(), irObject->bindingsEnd(), irObject->bindingCount());
- for (auto it : qAsConst(sortedBindings)) {
- compileBinding(current, *it, attachedObject,
- { object.type, attachedMemberName, propertyName, false });
- }
- }
- break;
- }
- case QmlIR::Binding::Type_GroupProperty: {
- Q_ASSERT(accessor.name == u"this"_s); // TODO: doesn't have to hold, in fact
- if (p.read().isEmpty()) {
- recordError(binding.location,
- u"READ function of group property '" + propertyName + u"' is unknown");
- return;
- }
-
- const auto groupedObject = m_objects.at(binding.value.objectIndex);
- const auto &irObject = groupedObject.irObject;
- const auto &type = groupedObject.type;
- Q_ASSERT(irObject && type);
-
- const bool isValueType =
- propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Value;
- if (!isValueType
- && propertyType->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
- recordError(binding.location,
- u"Group property '" + propertyName + u"' has unsupported access semantics");
- return;
- }
-
- QString groupAccessor =
- QmltcCodeGenerator::wrap_privateClass(accessor.name, p) + u"->" + p.read() + u"()";
- // NB: used when isValueType == true
- QString groupPropertyVarName = accessor.name + u"_group_" + propertyName;
-
- const auto isGroupAffectingBinding = [](const QmlIR::Binding &b) {
- // script bindings do not require value type group property variable
- // to actually be present.
- switch (b.type()) {
- case QmlIR::Binding::Type_Invalid:
- case QmlIR::Binding::Type_Script:
- return false;
- default:
- return true;
- }
- };
- const bool generateValueTypeCode = isValueType
- && std::any_of(irObject->bindingsBegin(), irObject->bindingsEnd(),
- isGroupAffectingBinding);
-
- // value types are special
- if (generateValueTypeCode) {
- if (p.write().isEmpty()) { // just reject this
- recordError(binding.location,
- u"Group property '" + propertyName
- + u"' is a value type without a setter");
- return;
- }
-
- current.endInit.body << u"auto " + groupPropertyVarName + u" = " + groupAccessor + u";";
- // TODO: addressof operator is a crutch to make the binding logic
- // work, which expects that `accessor.name` is a pointer type.
- groupAccessor = QmltcCodeGenerator::wrap_addressof(groupPropertyVarName);
- }
-
- // compile bindings of the grouped property
- auto sortedBindings = toOrderedSequence(irObject->bindingsBegin(), irObject->bindingsEnd(),
- irObject->bindingCount());
- const auto compile = [&](typename QmlIR::PoolList<QmlIR::Binding>::Iterator it) {
- compileBinding(current, *it, groupedObject,
- { object.type, groupAccessor, propertyName, isValueType });
- };
-
- // NB: can't use lower_bound since it only accepts a value, not a unary
- // predicate
- auto scriptBindingsBegin =
- std::find_if(sortedBindings.cbegin(), sortedBindings.cend(),
- [](auto it) { return it->type() == QmlIR::Binding::Type_Script; });
- auto it = sortedBindings.cbegin();
- for (; it != scriptBindingsBegin; ++it) {
- Q_ASSERT((*it)->type() != QmlIR::Binding::Type_Script);
- compile(*it);
- }
-
- // NB: script bindings are special on group properties. if our group is
- // a value type, the binding would be installed on the *object* that
- // holds the value type and not on the value type itself. this may cause
- // subtle side issues (esp. when script binding is actually a simple
- // enum value assignment - which is not recognized specially):
- //
- // auto valueTypeGroupProperty = getCopy();
- // installBinding(valueTypeGroupProperty, "subproperty1"); // changes subproperty1 value
- // setCopy(valueTypeGroupProperty); // oops, subproperty1 value changed to old again
- if (generateValueTypeCode) { // write the value type back
- current.endInit.body << QmltcCodeGenerator::wrap_privateClass(accessor.name, p) + u"->"
- + p.write() + u"(" + groupPropertyVarName + u");";
- }
-
- // once the value is written back, process the script bindings
- for (; it != sortedBindings.cend(); ++it) {
- Q_ASSERT((*it)->type() == QmlIR::Binding::Type_Script);
- compile(*it);
- }
- break;
- }
- case QmlIR::Binding::Type_Null: {
- // poor check: null bindings are only supported for var and objects
- if (propertyType != m_localTypeResolver->varType()
- && propertyType->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
- // TODO: this should really be done before the compiler here
- recordError(binding.location, u"Cannot assign null to incompatible property"_s);
- } else if (propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
- addPropertyLine(p.propertyName(), p, u"nullptr"_s);
- } else {
- addPropertyLine(p.propertyName(), p, u"QVariant::fromValue(nullptr)"_s);
- }
- break;
- }
- case QmlIR::Binding::Type_Invalid: {
- recordError(binding.valueLocation,
- u"Binding on property '" + propertyName + u"' is invalid or unsupported");
- break;
- }
- }
-}
-
-QString CodeGenerator::makeGensym(const QString &base)
-{
- QString gensym = base;
- gensym.replace(QLatin1String("."), QLatin1String("_"));
- while (gensym.startsWith(QLatin1Char('_')) && gensym.size() >= 2
- && (gensym[1].isUpper() || gensym[1] == QLatin1Char('_'))) {
- gensym.remove(0, 1);
- }
-
- if (!m_typeCounts.contains(gensym)) {
- m_typeCounts.insert(gensym, 1);
- } else {
- gensym += u"_" + QString::number(m_typeCounts[gensym]++);
- }
-
- return gensym;
-}
-
-// returns compiled script binding for "property changed" handler in a form of object type
-static QmltcType compileScriptBindingPropertyChangeHandler(
- const QmlIR::Binding &binding, const QmlIR::Object *irObject,
- const QString &urlMethodName, const QString &functorCppType, const QString &objectCppType,
- const QList<QmltcVariable> &slotParameters)
-{
- QmltcType bindingFunctor {};
- bindingFunctor.cppType = functorCppType;
- bindingFunctor.ignoreInit = true;
-
- // default member variable and ctor:
- const QString pointerToObject = objectCppType + u" *";
- bindingFunctor.variables.emplaceBack(
- QmltcVariable { pointerToObject, u"m_self"_s, u"nullptr"_s });
- bindingFunctor.baselineCtor.name = functorCppType;
- bindingFunctor.baselineCtor.parameterList.emplaceBack(
- QmltcVariable { pointerToObject, u"self"_s, QString() });
- bindingFunctor.baselineCtor.initializerList.emplaceBack(u"m_self(self)"_s);
-
- // call operator:
- QmltcMethod callOperator {};
- callOperator.returnType = u"void"_s;
- callOperator.name = u"operator()"_s;
- callOperator.parameterList = slotParameters;
- callOperator.modifiers << u"const"_s;
- QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
- &callOperator.body, urlMethodName + u"()",
- relativeToAbsoluteRuntimeIndex(irObject, binding.value.compiledScriptIndex),
- u"m_self"_s, u"void"_s, slotParameters);
-
- bindingFunctor.functions.emplaceBack(std::move(callOperator));
-
- return bindingFunctor;
-}
-
-static std::optional<QQmlJSMetaProperty>
-propertyForChangeHandler(const QQmlJSScope::ConstPtr &scope, QString name)
-{
- if (!name.endsWith(QLatin1String("Changed")))
- return {};
- constexpr int length = int(sizeof("Changed") / sizeof(char)) - 1;
- name.chop(length);
- auto p = scope->property(name);
- const bool isBindable = !p.bindable().isEmpty();
- const bool canNotify = !p.notify().isEmpty();
- if (p.isValid() && (isBindable || canNotify))
- return p;
- return {};
-}
-
-void CodeGenerator::compileScriptBinding(QmltcType &current, const QmlIR::Binding &binding,
- const QString &bindingSymbolName,
- const CodeGenObject &object, const QString &propertyName,
- const QQmlJSScope::ConstPtr &propertyType,
- const CodeGenerator::AccessorData &accessor)
-{
- auto objectType = object.type;
-
- // returns signal by name. signal exists if std::optional<> has value
- const auto signalByName = [&](const QString &name) -> std::optional<QQmlJSMetaMethod> {
- const auto signalMethods = objectType->methods(name, QQmlJSMetaMethod::Signal);
- if (signalMethods.isEmpty())
- return {};
- // if (signalMethods.size() != 1) return {}; // error somewhere else
- QQmlJSMetaMethod s = signalMethods.at(0);
- Q_ASSERT(s.methodType() == QQmlJSMetaMethod::Signal);
- return s;
- };
-
- const auto resolveSignal = [&](const QString &name) -> std::optional<QQmlJSMetaMethod> {
- auto signal = signalByName(name);
- if (signal) // found signal
- return signal;
- auto property = propertyForChangeHandler(objectType, name);
- if (!property) // nor signal nor property change handler
- return {}; // error somewhere else
- if (auto notify = property->notify(); !notify.isEmpty())
- return signalByName(notify);
- return {};
- };
-
- // TODO: add Invalid case which would reject garbage handlers
- enum BindingKind {
- JustProperty, // is a binding on property
- SignalHandler, // is a slot related to some signal
- BindablePropertyChangeHandler, // is a slot related to property's bindable
- };
-
- // these only make sense when binding is on signal handler
- QList<QmltcVariable> slotParameters;
- QString signalName;
- QString signalReturnType;
-
- const BindingKind kind = [&]() -> BindingKind {
- if (!QmlIR::IRBuilder::isSignalPropertyName(propertyName))
- return BindingKind::JustProperty;
-
- if (auto name = QQmlJSUtils::signalName(propertyName); name.has_value())
- signalName = *name;
-
- std::optional<QQmlJSMetaMethod> possibleSignal = resolveSignal(signalName);
- if (possibleSignal) { // signal with signalName exists
- QQmlJSMetaMethod s = possibleSignal.value();
-
- // Note: update signal name since it may be different from the one
- // used to resolve a signal
- signalName = s.methodName();
-
- const auto paramNames = s.parameterNames();
- const auto paramTypes = s.parameterTypes();
- Q_ASSERT(paramNames.size() == paramTypes.size());
- slotParameters = compileMethodParameters(paramNames, paramTypes, true);
- signalReturnType = figureReturnType(s);
- return BindingKind::SignalHandler;
- } else if (propertyName.endsWith(u"Changed"_s)) {
- return BindingKind::BindablePropertyChangeHandler;
- }
- return BindingKind::JustProperty;
- }();
-
- switch (kind) {
- case BindingKind::JustProperty: {
- if (!propertyType) {
- recordError(binding.location,
- u"Binding on property '" + propertyName + u"' of unknown type");
- return;
- }
-
- auto [property, absoluteIndex] = getMetaPropertyIndex(object.type, propertyName);
- if (absoluteIndex < 0) {
- recordError(binding.location, u"Binding on unknown property '" + propertyName + u"'");
- return;
- }
-
- QString bindingTarget = accessor.name;
-
- int valueTypeIndex = -1;
- if (accessor.isValueType) {
- Q_ASSERT(accessor.scope != object.type);
- bindingTarget = u"this"_s; // TODO: not necessarily "this"?
- auto [groupProperty, groupPropertyIndex] =
- getMetaPropertyIndex(accessor.scope, accessor.propertyName);
- if (groupPropertyIndex < 0) {
- recordError(binding.location,
- u"Binding on group property '" + accessor.propertyName
- + u"' of unknown type");
- return;
- }
- valueTypeIndex = absoluteIndex;
- absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name
- }
-
- QmltcCodeGenerator::generate_createBindingOnProperty(
- &current.endInit.body, generate_callCompilationUnit(m_urlMethodName),
- u"this"_s, // NB: always using enclosing object as a scope for the binding
- relativeToAbsoluteRuntimeIndex(object.irObject, binding.value.compiledScriptIndex),
- bindingTarget, // binding target
- absoluteIndex, property, valueTypeIndex, accessor.name);
- break;
- }
- case BindingKind::SignalHandler: {
- QString This_signal = u"this"_s;
- QString This_slot = u"this"_s;
- QString objectClassName_signal = objectType->internalName();
- QString objectClassName_slot = objectType->internalName();
- // TODO: ugly crutch to make stuff work
- if (accessor.name != u"this"_s) { // e.g. if attached property
- This_signal = accessor.name;
- This_slot = u"this"_s; // still
- objectClassName_signal = objectType->baseTypeName();
- objectClassName_slot = current.cppType; // real base class where slot would go
- }
- Q_ASSERT(!objectClassName_signal.isEmpty());
- Q_ASSERT(!objectClassName_slot.isEmpty());
- const QString slotName = makeGensym(signalName + u"_slot");
-
- // SignalHander specific:
- QmltcMethod slotMethod {};
- slotMethod.returnType = signalReturnType;
- slotMethod.name = slotName;
- slotMethod.parameterList = slotParameters;
-
- QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
- &slotMethod.body, m_urlMethodName + u"()",
- relativeToAbsoluteRuntimeIndex(object.irObject,
- binding.value.compiledScriptIndex),
- u"this"_s, // Note: because script bindings always use current QML object scope
- signalReturnType, slotParameters);
- slotMethod.type = QQmlJSMetaMethod::Slot;
-
- current.functions << std::move(slotMethod);
- current.endInit.body << u"QObject::connect(" + This_signal + u", "
- + QmltcCodeGenerator::wrap_qOverload(slotParameters,
- u"&" + objectClassName_signal + u"::"
- + signalName)
- + u", " + This_slot + u", &" + objectClassName_slot + u"::" + slotName
- + u");";
- break;
- }
- case BindingKind::BindablePropertyChangeHandler: {
- const QString objectClassName = objectType->internalName();
- const QString bindingFunctorName = makeGensym(bindingSymbolName + u"Functor");
-
- QString actualPropertyName;
- QRegularExpression onChangedRegExp(u"on(\\w+)Changed"_s);
- QRegularExpressionMatch match = onChangedRegExp.match(propertyName);
- if (match.hasMatch()) {
- actualPropertyName = match.captured(1);
- actualPropertyName[0] = actualPropertyName.at(0).toLower();
- } else {
- // an error somewhere else
- return;
- }
- if (!objectType->hasProperty(actualPropertyName)) {
- recordError(binding.location, u"No property named " + actualPropertyName);
- return;
- }
- QQmlJSMetaProperty actualProperty = objectType->property(actualPropertyName);
- const auto actualPropertyType = actualProperty.type();
- if (!actualPropertyType) {
- recordError(binding.location,
- u"Binding on property '" + actualPropertyName + u"' of unknown type");
- return;
- }
-
- QString bindableString = actualProperty.bindable();
- if (bindableString.isEmpty()) { // TODO: always should come from prop.bindalbe()
- recordError(binding.location,
- u"Property '" + actualPropertyName
- + u"' is non-bindable. Change handlers for such properties are not "
- u"supported");
- break;
- }
-
- QString typeOfQmlBinding =
- u"std::unique_ptr<QPropertyChangeHandler<" + bindingFunctorName + u">>";
-
- current.children << compileScriptBindingPropertyChangeHandler(
- binding, object.irObject, m_urlMethodName, bindingFunctorName,
- objectClassName, slotParameters);
-
- // TODO: this could be dropped if QQmlEngine::setContextForObject() is
- // done before currently generated C++ object is constructed
- current.endInit.body << bindingSymbolName + u".reset(new QPropertyChangeHandler<"
- + bindingFunctorName + u">("
- + QmltcCodeGenerator::wrap_privateClass(accessor.name, actualProperty)
- + u"->" + bindableString + u"().onValueChanged(" + bindingFunctorName + u"("
- + accessor.name + u"))));";
-
- current.variables.emplaceBack(
- QmltcVariable { typeOfQmlBinding, bindingSymbolName, QString() });
- break;
- }
- }
-}
-
-// TODO: should use "compileScriptBinding" instead of custom code
-void CodeGenerator::compileScriptBindingOfComponent(QmltcType &current,
- const QmlIR::Object *irObject,
- const QQmlJSScope::ConstPtr objectType,
- const QmlIR::Binding &binding,
- const QString &propertyName)
-{
- // returns signal by name. signal exists if std::optional<> has value
- const auto signalByName = [&](const QString &name) -> std::optional<QQmlJSMetaMethod> {
- const QList<QQmlJSMetaMethod> signalMethods = objectType->methods(name);
- // TODO: no clue how to handle redefinition, so just record an error
- if (signalMethods.size() != 1) {
- recordError(binding.location, u"Binding on redefined signal '" + name + u"'");
- return {};
- }
- QQmlJSMetaMethod s = signalMethods.at(0);
- if (s.methodType() != QQmlJSMetaMethod::Signal)
- return {};
- return s;
- };
-
- QString signalName;
- QString signalReturnType;
-
- Q_ASSERT(QmlIR::IRBuilder::isSignalPropertyName(propertyName));
- const auto offset = static_cast<uint>(strlen("on"));
- signalName = propertyName[offset].toLower() + propertyName.mid(offset + 1);
-
- std::optional<QQmlJSMetaMethod> possibleSignal = signalByName(signalName);
- if (!possibleSignal) {
- recordError(binding.location, u"Component signal '" + signalName + u"' is not recognized");
- return;
- }
- Q_ASSERT(possibleSignal); // Component's script binding is always a signal handler
- QQmlJSMetaMethod s = possibleSignal.value();
- Q_ASSERT(s.parameterNames().isEmpty()); // Component signals do not have parameters
- signalReturnType = figureReturnType(s);
-
- const QString slotName = makeGensym(signalName + u"_slot");
-
- // SignalHander specific:
- QmltcMethod slotMethod {};
- slotMethod.returnType = signalReturnType;
- slotMethod.name = slotName;
-
- // Component is special:
- int runtimeIndex =
- relativeToAbsoluteRuntimeIndex(irObject, binding.value.compiledScriptIndex);
- QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
- &slotMethod.body, m_urlMethodName + u"()", runtimeIndex, u"this"_s, signalReturnType);
- slotMethod.type = QQmlJSMetaMethod::Slot;
-
- // TODO: there's actually an attached type, which has completed/destruction
- // signals that are typically emitted -- do we care enough about supporting
- // that? see QQmlComponentAttached
- if (signalName == u"completed"_s) {
- current.handleOnCompleted.body << slotName + u"();";
- } else if (signalName == u"destruction"_s) {
- if (!current.dtor) {
- // TODO: double-check that this stuff is actually correct now:
- current.dtor = QmltcDtor {};
- current.dtor->name = u"~" + current.cppType;
- }
- current.dtor->body << slotName + u"();";
- }
- current.functions << std::move(slotMethod);
-}
-
void CodeGenerator::recordError(const QQmlJS::SourceLocation &location, const QString &message)
{
m_logger->log(message, Log_Compiler, location);
diff --git a/tools/qmltc/prototype/codegenerator.h b/tools/qmltc/prototype/codegenerator.h
index c8c5fff195..a0f7e57fd5 100644
--- a/tools/qmltc/prototype/codegenerator.h
+++ b/tools/qmltc/prototype/codegenerator.h
@@ -61,7 +61,6 @@ public:
// initializes code generator
void prepare(QSet<QString> *cppIncludes);
- void setUrlMethodName(const QString &name) { m_urlMethodName = name; }
const QList<CodeGenObject> &objects() const { return m_objects; }
bool ignoreObject(const CodeGenObject &object) const;
@@ -84,6 +83,11 @@ public:
toOrderedSequence(typename QmlIR::PoolList<QmlIR::Binding>::Iterator first,
typename QmlIR::PoolList<QmlIR::Binding>::Iterator last, qsizetype n);
+ bool hasQmlCompiledBaseType(const QQmlJSScope::ConstPtr &type) const
+ {
+ return m_qmlCompiledBaseTypes.contains(type->baseTypeName());
+ }
+
private:
QString m_url; // document url
QQmlJSLogger *m_logger = nullptr;
@@ -97,102 +101,19 @@ private:
QList<CodeGenObject> m_objects;
// mapping from type to m_objects index
QHash<QQmlJSScope::ConstPtr, qsizetype> m_typeToObjectIndex; // TODO: remove this
- // parents for each type that will (also) create the type
- QHash<QQmlJSScope::ConstPtr, QQmlJSScope::ConstPtr> m_immediateParents;
// mapping from component-wrapped object to component index (real or not)
QHash<int, int> m_componentIndices;
// types ignored by the code generator
QSet<QQmlJSScope::ConstPtr> m_ignoredTypes;
- QString m_urlMethodName;
-
- // helper struct used for unique string generation
- struct UniqueStringId
- {
- QString combined;
- UniqueStringId(const QmltcType &compiled, const QString &value)
- : combined(compiled.cppType + u"_" + value)
- {
- Q_ASSERT(!compiled.cppType.isEmpty());
- }
- operator QString() { return combined; }
-
- friend bool operator==(const UniqueStringId &x, const UniqueStringId &y)
- {
- return x.combined == y.combined;
- }
- friend bool operator!=(const UniqueStringId &x, const UniqueStringId &y)
- {
- return !(x == y);
- }
- friend bool operator<(const UniqueStringId &x, const UniqueStringId &y)
- {
- return x.combined < y.combined;
- }
- friend size_t qHash(const UniqueStringId &x) { return qHash(x.combined); }
- };
-
- // QML attached types that are already added as member variables (faster lookup)
- QSet<UniqueStringId> m_attachedTypesAlreadyRegistered;
-
- // machinery for unique names generation
- QHash<QString, qsizetype> m_typeCounts;
- QString makeGensym(const QString &base);
-
- // crutch to remember QQmlListReference names, the unique naming convention
- // is used for these so there's never a conflict
- QSet<UniqueStringId> m_listReferencesCreated;
-
// native QML base type names of the to-be-compiled objects which happen to
// also be generated (but separately)
QSet<QString> m_qmlCompiledBaseTypes;
- // a set of objects that participate in "on" assignments
- QSet<UniqueStringId> m_onAssignmentObjectsCreated;
-
- // a vector of names of children that need to be end-initialized within type
- QList<QString> m_localChildrenToEndInit;
- // a vector of children that need to be finalized within type
- QList<QQmlJSScope::ConstPtr> m_localChildrenToFinalize;
-
- // a crutch (?) to enforce special generated code for aliases to ids, for
- // example: `property alias p: root`
- QSet<QQmlJSMetaProperty> m_aliasesToIds;
-
// init function that constructs m_objects
void constructObjects(QSet<QString> &requiredCppIncludes);
- bool m_isAnonymous = false; // crutch to distinguish QML_ELEMENT from QML_ANONYMOUS
-
-public:
- // helper structure that holds the information necessary for most bindings,
- // such as accessor name, which is used to reference the properties like:
- // (accessor.name)->(propertyName): this->myProperty. it is also used in
- // more advanced scenarios by attached and grouped properties
- struct AccessorData
- {
- QQmlJSScope::ConstPtr scope; // usually object.type
- QString name; // usually "this"
- QString propertyName; // usually empty
- bool isValueType = false; // usually false
- };
- void compileBinding(QmltcType &current, const QmlIR::Binding &binding,
- const CodeGenObject &object, const AccessorData &accessor);
-
private:
- // special case (for simplicity)
- void compileScriptBinding(QmltcType &current, const QmlIR::Binding &binding,
- const QString &bindingSymbolName, const CodeGenObject &object,
- const QString &propertyName,
- const QQmlJSScope::ConstPtr &propertyType,
- const AccessorData &accessor);
-
- // TODO: remove this special case
- void compileScriptBindingOfComponent(QmltcType &current, const QmlIR::Object *object,
- const QQmlJSScope::ConstPtr objectType,
- const QmlIR::Binding &binding,
- const QString &propertyName);
-
// helper methods:
void recordError(const QQmlJS::SourceLocation &location, const QString &message);
void recordError(const QV4::CompiledData::Location &location, const QString &message);
diff --git a/tools/qmltc/prototype/qml2cppdefaultpasses.cpp b/tools/qmltc/prototype/qml2cppdefaultpasses.cpp
index 5933a4e510..72c58ca1d8 100644
--- a/tools/qmltc/prototype/qml2cppdefaultpasses.cpp
+++ b/tools/qmltc/prototype/qml2cppdefaultpasses.cpp
@@ -513,10 +513,8 @@ static void updateInternalName(QQmlJSScope::Ptr root, QString prefix,
}
}
-QHash<QString, qsizetype> makeUniqueCppNames(const Qml2CppContext &context,
- QList<Qml2CppObject> &objects)
+void makeUniqueCppNames(const Qml2CppContext &context, QList<Qml2CppObject> &objects)
{
- // TODO: fix return type names of the methods as well
Q_UNUSED(objects);
QHash<QString, qsizetype> typeCounts;
@@ -530,16 +528,15 @@ QHash<QString, qsizetype> makeUniqueCppNames(const Qml2CppContext &context,
if (typeCounts.contains(cppName)) {
context.recordError(root->sourceLocation(),
u"Root object name '" + cppName + u"' is reserved");
- return typeCounts;
+ return;
}
if (cppName.isEmpty()) {
context.recordError(root->sourceLocation(), u"Root object's name is empty"_s);
- return typeCounts;
+ return;
}
typeCounts.insert(cppName, 1);
updateInternalName(root, cppName, typeCounts);
- return typeCounts;
}
static void setupQmlCppType(const Qml2CppContext &context, const QQmlJSScope::Ptr &type,
@@ -748,11 +745,8 @@ static void resolveValidateOrSkipAlias(const Qml2CppContext &context,
aliasOwner.type->addOwnProperty(alias);
}
-QSet<QQmlJSMetaProperty> deferredResolveValidateAliases(const Qml2CppContext &context,
- QList<Qml2CppObject> &objects)
+void deferredResolveValidateAliases(const Qml2CppContext &context, QList<Qml2CppObject> &objects)
{
- QSet<QQmlJSMetaProperty> aliasesToId;
-
QSet<QQmlJSMetaProperty> unresolved;
for (const auto &object : objects) {
const auto [irObject, type] = object;
@@ -773,7 +767,6 @@ QSet<QQmlJSMetaProperty> deferredResolveValidateAliases(const Qml2CppContext &co
p.setRead(compiledData.read);
// NB: id-pointing aliases are read-only
type->addOwnProperty(p);
- aliasesToId.insert(p);
continue;
}
unresolved.insert(p);
@@ -792,8 +785,6 @@ QSet<QQmlJSMetaProperty> deferredResolveValidateAliases(const Qml2CppContext &co
}
}
}
-
- return aliasesToId;
}
static void addFirstCppIncludeFromType(QSet<QString> &cppIncludes,
@@ -1044,33 +1035,6 @@ void setObjectIds(const Qml2CppContext &context, QList<Qml2CppObject> &objects)
set(0);
}
-QHash<QQmlJSScope::ConstPtr, QQmlJSScope::ConstPtr>
-findImmediateParents(const Qml2CppContext &context, QList<Qml2CppObject> &objects)
-{
- Q_UNUSED(context);
-
- QSet<QQmlJSScope::ConstPtr> suitableParents;
- std::transform(objects.cbegin(), objects.cend(),
- std::inserter(suitableParents, suitableParents.end()),
- [](const Qml2CppObject &object) { return object.type; });
-
- QHash<QQmlJSScope::ConstPtr, QQmlJSScope::ConstPtr> immediateParents;
-
- // suitable parents are the ones that would eventually create the child
- // types (through recursive logic), so the first such parent in a hierarchy
- // is an immediate parent
- for (const Qml2CppObject &object : objects) {
- for (auto parent = object.type->parentScope(); parent; parent = parent->parentScope()) {
- if (suitableParents.contains(parent)) {
- immediateParents.insert(object.type, parent);
- break;
- }
- }
- }
-
- return immediateParents;
-}
-
QSet<QQmlJSScope::ConstPtr> collectIgnoredTypes(const Qml2CppContext &context,
QList<Qml2CppObject> &objects)
{
diff --git a/tools/qmltc/prototype/qml2cppdefaultpasses.h b/tools/qmltc/prototype/qml2cppdefaultpasses.h
index 5f4b750227..e7fe501be8 100644
--- a/tools/qmltc/prototype/qml2cppdefaultpasses.h
+++ b/tools/qmltc/prototype/qml2cppdefaultpasses.h
@@ -43,12 +43,9 @@ void verifyTypes(const Qml2CppContext &context, QList<Qml2CppObject> &objects);
// twice)
void checkForNamingCollisionsWithCpp(const Qml2CppContext &context, QList<Qml2CppObject> &objects);
-// TODO: the below passes are not "default" (cannot be directly added)
-
// ensures that all QQmlJSScope objects have unique internalName() and checks
// whether some name is C++ reserved keyword
-QHash<QString, qsizetype> makeUniqueCppNames(const Qml2CppContext &context,
- QList<Qml2CppObject> &objects);
+void makeUniqueCppNames(const Qml2CppContext &context, QList<Qml2CppObject> &objects);
// sets up QML-originated base types of \a objects and \a objects themselves.
// processed types are expected to be generated to C++. returns a set of QML
@@ -60,8 +57,7 @@ QSet<QString> setupQmlCppTypes(const Qml2CppContext &context, QList<Qml2CppObjec
// value type, etc.). returns aliases which point to ids. must be done after
// setupQmlCppTypes() since some (own) aliased properties are not fully set up
// untile then and thus do not have the necessary information
-QSet<QQmlJSMetaProperty> deferredResolveValidateAliases(const Qml2CppContext &context,
- QList<Qml2CppObject> &objects);
+void deferredResolveValidateAliases(const Qml2CppContext &context, QList<Qml2CppObject> &objects);
// finds all required C++ include files that are needed for the generated C++
QSet<QString> findCppIncludes(const Qml2CppContext &context, QList<Qml2CppObject> &objects);
@@ -81,10 +77,6 @@ QHash<int, int> findAndResolveImplicitComponents(const Qml2CppContext &context,
void setObjectIds(const Qml2CppContext &context, QList<Qml2CppObject> &objects);
-// finds an immediate parent of each to-be-compiled type
-QHash<QQmlJSScope::ConstPtr, QQmlJSScope::ConstPtr>
-findImmediateParents(const Qml2CppContext &context, QList<Qml2CppObject> &objects);
-
QSet<QQmlJSScope::ConstPtr> collectIgnoredTypes(const Qml2CppContext &context,
QList<Qml2CppObject> &objects);
diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp
index 99e43bc161..8eadf9739d 100644
--- a/tools/qmltc/qmltccompiler.cpp
+++ b/tools/qmltc/qmltccompiler.cpp
@@ -60,6 +60,22 @@ QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, Qm
// incomplete type in the header (~std::unique_ptr<> fails with a static_assert)
QmltcCompiler::~QmltcCompiler() = default;
+QString QmltcCompiler::newSymbol(const QString &base)
+{
+ QString symbol = base;
+ symbol.replace(QLatin1String("."), QLatin1String("_"));
+ while (symbol.startsWith(QLatin1Char('_')) && symbol.size() >= 2
+ && (symbol[1].isUpper() || symbol[1] == QLatin1Char('_'))) {
+ symbol.remove(0, 1);
+ }
+ if (!m_symbols.contains(symbol)) {
+ m_symbols.insert(symbol, 1);
+ } else {
+ symbol += u"_" + QString::number(m_symbols[symbol]++);
+ }
+ return symbol;
+}
+
void QmltcCompiler::compile(const QmltcCompilerInfo &info, QmlIR::Document *doc)
{
m_info = info;
@@ -89,7 +105,7 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info, QmlIR::Document *doc)
QmltcMethod urlMethod;
compileUrlMethod(urlMethod, generator.urlMethodName());
- m_prototypeCodegen->setUrlMethodName(urlMethod.name);
+ m_urlMethodName = urlMethod.name;
const auto objects = m_prototypeCodegen->objects();
QList<CodeGenerator::CodeGenObject> filteredObjects;
@@ -305,10 +321,21 @@ void QmltcCompiler::compileType(
compileElements(current, type);
}
-void QmltcCompiler::compileTypeElements(QmltcType &current, const QQmlJSScope::ConstPtr &type)
+template<typename Iterator>
+static Iterator partitionBindings(Iterator first, Iterator last)
{
- const CodeGenerator::CodeGenObject &object = m_prototypeCodegen->objectFromType(type);
+ // NB: the code generator cares about script bindings being processed at a
+ // later point, so we should sort or partition the range. we do a stable
+ // partition since the relative order of binding evaluation affects the UI
+ return std::stable_partition(first, last, [](const QQmlJSMetaPropertyBinding &b) {
+ // we want script bindings to be at the end, so do the negation of "is
+ // script binding"
+ return b.bindingType() != QQmlJSMetaPropertyBinding::Script;
+ });
+}
+void QmltcCompiler::compileTypeElements(QmltcType &current, const QQmlJSScope::ConstPtr &type)
+{
// compile components of a type:
// - enums
// - properties
@@ -346,25 +373,11 @@ void QmltcCompiler::compileTypeElements(QmltcType &current, const QQmlJSScope::C
for (const QQmlJSMetaMethod &m : methods)
compileMethod(current, m, type);
- {
- const auto sortedBindings = CodeGenerator::toOrderedSequence(
- object.irObject->bindingsBegin(), object.irObject->bindingsEnd(),
- object.irObject->bindingCount());
-
- auto scriptBindingsBegin =
- std::find_if(sortedBindings.cbegin(), sortedBindings.cend(),
- [](auto it) { return it->type() == QmlIR::Binding::Type_Script; });
- auto it = sortedBindings.cbegin();
- for (; it != scriptBindingsBegin; ++it) {
- m_prototypeCodegen->compileBinding(current, **it, object,
- { object.type, u"this"_s, u""_s, false });
- }
+ auto bindings = type->ownPropertyBindingsInQmlIROrder();
+ partitionBindings(bindings.begin(), bindings.end());
- for (; it != sortedBindings.cend(); ++it) {
- m_prototypeCodegen->compileBinding(current, **it, object,
- { type, u"this"_s, u""_s, false });
- }
- }
+ for (auto it = bindings.begin(); it != bindings.end(); ++it)
+ compileBinding(current, *it, type, { type });
}
void QmltcCompiler::compileEnum(QmltcType &current, const QQmlJSMetaEnum &e)
@@ -385,6 +398,8 @@ compileMethodParameters(const QStringList &names,
const QList<QSharedPointer<const QQmlJSScope>> &types,
bool allowUnnamed = false)
{
+ Q_ASSERT(names.size() == types.size());
+
QList<QmltcVariable> parameters;
const auto size = names.size();
parameters.reserve(size);
@@ -399,22 +414,23 @@ compileMethodParameters(const QStringList &names,
return parameters;
}
+static QString figureReturnType(const QQmlJSMetaMethod &m)
+{
+ const bool isVoidMethod =
+ m.returnTypeName() == u"void" || m.methodType() == QQmlJSMetaMethod::Signal;
+ Q_ASSERT(isVoidMethod || m.returnType());
+ QString type;
+ if (isVoidMethod) {
+ type = u"void"_s;
+ } else {
+ type = m.returnType()->augmentedInternalName();
+ }
+ return type;
+}
+
void QmltcCompiler::compileMethod(QmltcType &current, const QQmlJSMetaMethod &m,
const QQmlJSScope::ConstPtr &owner)
{
- const auto figureReturnType = [](const QQmlJSMetaMethod &m) {
- const bool isVoidMethod =
- m.returnTypeName() == u"void" || m.methodType() == QQmlJSMetaMethod::Signal;
- Q_ASSERT(isVoidMethod || m.returnType());
- QString type;
- if (isVoidMethod) {
- type = u"void"_s;
- } else {
- type = m.returnType()->augmentedInternalName();
- }
- return type;
- };
-
const auto returnType = figureReturnType(m);
const auto paramNames = m.parameterNames();
const auto paramTypes = m.parameterTypes();
@@ -424,12 +440,10 @@ void QmltcCompiler::compileMethod(QmltcType &current, const QQmlJSMetaMethod &m,
QStringList code;
if (methodType != QQmlJSMetaMethod::Signal) {
- const qsizetype index =
- static_cast<qsizetype>(owner->ownRuntimeFunctionIndex(m.jsFunctionIndex()));
- Q_ASSERT(index >= 0);
QmltcCodeGenerator urlGenerator { m_url, m_visitor };
QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
- &code, urlGenerator.urlMethodName() + u"()", index, u"this"_s, returnType,
+ &code, urlGenerator.urlMethodName() + u"()",
+ owner->ownRuntimeFunctionIndex(m.jsFunctionIndex()), u"this"_s, returnType,
compiledParams);
}
@@ -784,85 +798,610 @@ void QmltcCompiler::compileAlias(QmltcType &current, const QQmlJSMetaProperty &a
}
}
+static QString generate_callCompilationUnit(const QString &urlMethodName)
+{
+ return u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(%1())"_s.arg(urlMethodName);
+}
+
void QmltcCompiler::compileBinding(QmltcType &current, const QQmlJSMetaPropertyBinding &binding,
const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
- Q_UNUSED(current);
- Q_UNUSED(accessor);
QString propertyName = binding.propertyName();
- if (propertyName.isEmpty()) {
- // if empty, try default property
- for (QQmlJSScope::ConstPtr t = type->baseType(); t && propertyName.isEmpty();
- t = t->baseType()) {
- propertyName = t->defaultPropertyName();
+ Q_ASSERT(!propertyName.isEmpty());
+
+ auto bindingType = binding.bindingType();
+
+ // 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 (type->isNameDeferred(propertyName)) {
+ const auto location = binding.sourceLocation();
+ if (bindingType == QQmlJSMetaPropertyBinding::GroupProperty) {
+ qCWarning(lcQmltcCompiler)
+ << QStringLiteral("Binding at line %1 column %2 is not deferred as it is a "
+ "binding on a group property.")
+ .arg(QString::number(location.startLine),
+ QString::number(location.startColumn));
+ // we do not support PropertyChanges and other types with similar
+ // behavior yet, so this binding is compiled
+ } else {
+ qCDebug(lcQmltcCompiler)
+ << QStringLiteral(
+ "Binding at line %1 column %2 is deferred and thus not compiled")
+ .arg(QString::number(location.startLine),
+ QString::number(location.startColumn));
+ return;
}
}
- Q_ASSERT(!propertyName.isEmpty());
+
+ const auto assignToProperty = [&](const QQmlJSMetaProperty &p, const QString &value,
+ bool constructFromQObject = false) {
+ if (p.isAlias() && m_prototypeCodegen->hasQmlCompiledBaseType(type)) {
+ qCDebug(lcQmltcCompiler) << u"Property '" + p.propertyName()
+ + u"' is an alias on type '" + type->internalName()
+ + u"' which is a QML type compiled to C++. The assignment is special"
+ + u"in this case";
+ // TODO: attest whether we could simplify this (see why prototype
+ // did special code generation)
+ QmltcCodeGenerator::generate_assignToProperty(&current.endInit.body, type, p, value,
+ accessor.name, constructFromQObject);
+ } else {
+ QmltcCodeGenerator::generate_assignToProperty(&current.endInit.body, type, p, value,
+ accessor.name, constructFromQObject);
+ }
+ };
+
QQmlJSMetaProperty p = type->property(propertyName);
- Q_ASSERT(p.isValid());
QQmlJSScope::ConstPtr propertyType = p.type();
- Q_ASSERT(propertyType);
- // NB: we assume here that QmltcVisitor took care of type mismatches and
- // other errors, so the compiler just needs to add correct instructions,
- // without if-checking every type
+ const auto addObjectBinding = [&](const QString &value) {
+ if (p.isList()) {
+ const auto &listName =
+ m_uniques[UniqueStringId(current, propertyName)].qmlListVariableName;
+ Q_ASSERT(!listName.isEmpty());
+ current.endInit.body << u"%1.append(%2);"_qs.arg(listName, value);
+ } else {
+ assignToProperty(p, value, /* constructFromQObject = */ true);
+ }
+ };
- QmltcCodeGenerator generator {};
+ // when property is list, create a local variable (unique per-scope &&
+ // per-property) that would be used to append new elements
+ if (p.isList()) {
+ auto &listName = m_uniques[UniqueStringId(current, propertyName)].qmlListVariableName;
+ if (listName.isEmpty()) { // not created yet, add extra instructions
+ listName = u"listref_" + propertyName;
+ current.endInit.body << QStringLiteral("QQmlListReference %1(%2, %3);")
+ .arg(listName, accessor.name,
+ QQmlJSUtils::toLiteral(propertyName,
+ u"QByteArrayLiteral"));
+ current.endInit.body << QStringLiteral("Q_ASSERT(%1.canAppend());").arg(listName);
+ }
+ }
switch (binding.bindingType()) {
case QQmlJSMetaPropertyBinding::BoolLiteral: {
const bool value = binding.boolValue();
- generator.generate_assignToProperty(&current.init.body, type, p,
- value ? u"true"_s : u"false"_s, accessor.name);
+ assignToProperty(p, value ? u"true"_s : u"false"_s);
break;
}
case QQmlJSMetaPropertyBinding::NumberLiteral: {
- const QString value = QString::number(binding.numberValue());
- generator.generate_assignToProperty(&current.init.body, type, p, value, accessor.name);
+ assignToProperty(p, QString::number(binding.numberValue()));
break;
}
case QQmlJSMetaPropertyBinding::StringLiteral: {
- const QString value = binding.stringValue();
- generator.generate_assignToProperty(&current.init.body, type, p,
- QQmlJSUtils::toLiteral(value), accessor.name);
+ assignToProperty(p, QQmlJSUtils::toLiteral(binding.stringValue()));
break;
}
case QQmlJSMetaPropertyBinding::Null: {
// poor check: null bindings are only supported for var and objects
- if (propertyType != m_typeResolver->varType()
- && propertyType->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
- // TODO: this should really be done before the compiler here
+ Q_ASSERT(propertyType->isSameType(m_typeResolver->varType())
+ || propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference);
+ if (propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
+ assignToProperty(p, u"nullptr"_s);
+ else
+ assignToProperty(p, u"QVariant::fromValue(nullptr)"_s);
+ break;
+ }
+ case QQmlJSMetaPropertyBinding::Script: {
+ QString bindingSymbolName = type->internalName() + u'_' + propertyName + u"_binding";
+ bindingSymbolName.replace(u'.', u'_'); // can happen with group properties
+ compileScriptBinding(current, binding, bindingSymbolName, type, propertyName, propertyType,
+ accessor);
+ break;
+ }
+ case QQmlJSMetaPropertyBinding::Object: {
+ // NB: object is compiled with compileType(), here just need to use it
+ auto object = binding.objectType();
+
+ // Note: despite a binding being set for `accessor`, we use "this" as a
+ // parent of a created object. Both attached and grouped properties are
+ // parented by "this", so lifetime-wise we should be fine
+ const QString qobjectParent = u"this"_s;
+
+ if (!propertyType) {
recordError(binding.sourceLocation(),
- u"Cannot assign null to incompatible property"_s);
- } else if (propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
- generator.generate_assignToProperty(&current.init.body, type, p, u"nullptr"_s,
- accessor.name);
+ u"Binding on property '" + propertyName + u"' of unknown type");
+ return;
+ }
+
+ // special case of implicit or explicit component:
+ if (qsizetype index = m_visitor->qmlComponentIndex(object); index >= 0) {
+ // TODO: or do this in current.init? - yes, because otherwise
+ // components are not going to be id-accessible, which is bad
+
+ const QString objectName = newSymbol(u"sc"_s);
+ current.endInit.body << u"{"_s;
+ current.endInit.body << QStringLiteral(
+ "auto thisContext = QQmlData::get(%1)->outerContext;")
+ .arg(qobjectParent);
+ current.endInit.body << QStringLiteral(
+ "auto %1 = QQmlObjectCreator::createComponent(engine, "
+ "%2, %3, %4, thisContext);")
+ .arg(objectName,
+ generate_callCompilationUnit(m_urlMethodName),
+ QString::number(index), qobjectParent);
+ current.endInit.body << QStringLiteral("thisContext->installContext(QQmlData::get(%1), "
+ "QQmlContextData::OrdinaryObject);")
+ .arg(objectName);
+
+ // objects wrapped in implicit components do not have visible ids,
+ // however, explicit components can have an id and that one is going
+ // to be visible in the common document context
+ if (!object->isComponentRootElement()) {
+ if (int id = m_visitor->runtimeId(object); id >= 0) {
+ QString idString = m_visitor->addressableScopes().id(object);
+ if (idString.isEmpty())
+ idString = u"<unknown>"_s;
+ QmltcCodeGenerator::generate_setIdValue(&current.endInit.body, u"thisContext"_s,
+ id, objectName, idString);
+ }
+ }
+ addObjectBinding(objectName);
+ current.endInit.body << u"}"_s;
+ break;
+ }
+
+ const QString objectName = newSymbol(u"o"_s);
+ current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg(
+ objectName, object->internalName(), qobjectParent);
+ current.init.body << u"creator->set(%1, %2);"_s.arg(
+ QString::number(m_visitor->creationIndex(object)), objectName);
+
+ // refetch the same object during endInit to set the bindings
+ current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg(
+ objectName, object->internalName(),
+ QString::number(m_visitor->creationIndex(object)));
+ addObjectBinding(objectName);
+ break;
+ }
+ case QQmlJSMetaPropertyBinding::Interceptor:
+ Q_FALLTHROUGH();
+ case QQmlJSMetaPropertyBinding::ValueSource: {
+ // NB: object is compiled with compileType(), here just need to use it
+ QSharedPointer<const QQmlJSScope> object;
+ if (bindingType == QQmlJSMetaPropertyBinding::Interceptor)
+ object = binding.interceptorType();
+ else
+ object = binding.valueSourceType();
+
+ // Note: despite a binding being set for `accessor`, we use "this" as a
+ // parent of a created object. Both attached and grouped properties are
+ // parented by "this", so lifetime-wise we should be fine
+ const QString qobjectParent = u"this"_s;
+
+ if (!propertyType) {
+ recordError(binding.sourceLocation(),
+ u"Binding on property '" + propertyName + u"' of unknown type");
+ return;
+ }
+
+ auto &objectName = m_uniques[UniqueStringId(current, propertyName)].onAssignmentObjectName;
+ if (objectName.isEmpty()) {
+ objectName = u"onAssign_" + propertyName;
+
+ current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg(
+ objectName, object->internalName(), qobjectParent);
+ current.init.body << u"creator->set(%1, %2);"_s.arg(
+ QString::number(m_visitor->creationIndex(object)), objectName);
+
+ current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg(
+ objectName, object->internalName(),
+ QString::number(m_visitor->creationIndex(object)));
+ }
+
+ // NB: we expect one "on" assignment per property, so creating
+ // QQmlProperty each time should be fine (unlike QQmlListReference)
+ current.endInit.body << u"{"_s;
+ current.endInit.body << u"QQmlProperty qmlprop(%1, %2);"_s.arg(
+ accessor.name, QQmlJSUtils::toLiteral(propertyName));
+ current.endInit.body
+ << u"QT_PREPEND_NAMESPACE(QQmlCppOnAssignmentHelper)::set(%1, qmlprop);"_s.arg(
+ objectName);
+ current.endInit.body << u"}"_s;
+ break;
+ }
+ case QQmlJSMetaPropertyBinding::AttachedProperty: {
+ Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact
+ const auto attachedType = binding.attachingType();
+ Q_ASSERT(attachedType);
+ auto subbindings = attachedType->ownPropertyBindingsInQmlIROrder();
+ if (propertyName == u"Component"_s) {
+ // TODO: there's a special QQmlComponentAttached, which has to be
+ // called? c.f. qqmlobjectcreator.cpp's finalize()
+ for (const auto &b : qAsConst(subbindings)) {
+ Q_ASSERT(b.bindingType() == QQmlJSMetaPropertyBinding::Script);
+ compileScriptBindingOfComponent(current, attachedType, b, b.propertyName());
+ }
} else {
- generator.generate_assignToProperty(&current.init.body, type, p,
- u"QVariant::fromValue(nullptr)"_s, accessor.name);
+ const QString attachingTypeName = propertyName; // acts as an identifier
+ auto attachingType = m_typeResolver->typeForName(attachingTypeName);
+
+ QString attachedTypeName = attachedType->baseTypeName();
+ Q_ASSERT(!attachedTypeName.isEmpty());
+
+ auto &attachedMemberName =
+ m_uniques[UniqueStringId(current, propertyName)].attachedVariableName;
+ if (attachedMemberName.isEmpty()) {
+ attachedMemberName = u"m_" + attachingTypeName;
+ // add attached type as a member variable to allow noop lookup
+ current.variables.emplaceBack(attachedTypeName + u" *", attachedMemberName,
+ u"nullptr"_s);
+ // Note: getting attached property is fairly expensive
+ const QString getAttachedPropertyLine = u"qobject_cast<" + attachedTypeName
+ + u" *>(qmlAttachedPropertiesObject<" + attachingType->internalName()
+ + u">(this, /* create = */ true))";
+ current.endInit.body
+ << attachedMemberName + u" = " + getAttachedPropertyLine + u";";
+ }
+
+ // compile bindings of the attached property
+ partitionBindings(subbindings.begin(), subbindings.end());
+ for (const auto &b : qAsConst(subbindings)) {
+ compileBinding(current, b, attachedType,
+ { type, attachedMemberName, propertyName, false });
+ }
+ }
+ break;
+ }
+ case QQmlJSMetaPropertyBinding::GroupProperty: {
+ Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact
+ if (p.read().isEmpty()) {
+ recordError(binding.sourceLocation(),
+ u"READ function of group property '" + propertyName + u"' is unknown");
+ return;
+ }
+
+ auto groupType = binding.groupType();
+ Q_ASSERT(groupType);
+
+ const bool isValueType =
+ propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Value;
+ if (!isValueType
+ && propertyType->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
+ recordError(binding.sourceLocation(),
+ u"Group property '" + propertyName + u"' has unsupported access semantics");
+ return;
+ }
+
+ auto subbindings = groupType->ownPropertyBindingsInQmlIROrder();
+ auto firstScript = partitionBindings(subbindings.begin(), subbindings.end());
+
+ // if we have no non-script bindings, we have no bindings that affect
+ // the value type group, so no reason to generate the wrapping code
+ const bool generateValueTypeCode = isValueType && (subbindings.begin() != firstScript);
+
+ QString groupAccessor =
+ QmltcCodeGenerator::wrap_privateClass(accessor.name, p) + u"->" + p.read() + u"()";
+ // NB: used when isValueType == true
+ const QString groupPropertyVarName = accessor.name + u"_group_" + propertyName;
+ // value types are special
+ if (generateValueTypeCode) {
+ if (p.write().isEmpty()) { // just reject this
+ recordError(binding.sourceLocation(),
+ u"Group property '" + propertyName
+ + u"' is a value type without a setter");
+ return;
+ }
+
+ current.endInit.body << u"auto " + groupPropertyVarName + u" = " + groupAccessor + u";";
+ // addressof operator is to make the binding logic work, which
+ // expects that `accessor.name` is a pointer type
+ groupAccessor = QmltcCodeGenerator::wrap_addressof(groupPropertyVarName);
+ }
+
+ // compile bindings of the grouped property
+ const auto compile = [&](const QQmlJSMetaPropertyBinding &b) {
+ compileBinding(current, b, groupType,
+ { type, groupAccessor, propertyName, isValueType });
+ };
+
+ auto it = subbindings.begin();
+ for (; it != firstScript; ++it) {
+ Q_ASSERT(it->bindingType() != QQmlJSMetaPropertyBinding::Script);
+ compile(*it);
+ }
+
+ // NB: script bindings are special on group properties. if our group is
+ // a value type, the binding would be installed on the *object* that
+ // holds the value type and not on the value type itself. this may cause
+ // subtle side issues (esp. when script binding is actually a simple
+ // enum value assignment - which is not recognized specially):
+ //
+ // auto valueTypeGroupProperty = getCopy();
+ // installBinding(valueTypeGroupProperty, "subproperty1"); // changes subproperty1 value
+ // setCopy(valueTypeGroupProperty); // oops, subproperty1 value changed to old again
+ if (generateValueTypeCode) { // write the value type back
+ current.endInit.body << QmltcCodeGenerator::wrap_privateClass(accessor.name, p) + u"->"
+ + p.write() + u"(" + groupPropertyVarName + u");";
+ }
+
+ // once the value is written back, process the script bindings
+ for (; it != subbindings.end(); ++it) {
+ Q_ASSERT(it->bindingType() == QQmlJSMetaPropertyBinding::Script);
+ compile(*it);
}
break;
}
- // case QQmlJSMetaPropertyBinding::RegExpLiteral:
- // case QQmlJSMetaPropertyBinding::Translation:
- // case QQmlJSMetaPropertyBinding::TranslationById:
- // case QQmlJSMetaPropertyBinding::Script:
- // case QQmlJSMetaPropertyBinding::Object:
- // case QQmlJSMetaPropertyBinding::Interceptor:
- // case QQmlJSMetaPropertyBinding::ValueSource:
- // case QQmlJSMetaPropertyBinding::AttachedProperty:
- // case QQmlJSMetaPropertyBinding::GroupProperty:
case QQmlJSMetaPropertyBinding::Invalid: {
- m_logger->log(u"This binding is invalid"_s, Log_Compiler, binding.sourceLocation());
+ recordError(binding.sourceLocation(), u"This binding is invalid"_s);
break;
}
default: {
- m_logger->log(u"Binding type is not supported (yet)"_s, Log_Compiler,
- binding.sourceLocation());
+ recordError(binding.sourceLocation(), u"Binding is not supported"_s);
break;
}
}
}
+// returns compiled script binding for "property changed" handler in a form of object type
+static QmltcType compileScriptBindingPropertyChangeHandler(const QQmlJSMetaPropertyBinding &binding,
+ const QQmlJSScope::ConstPtr &objectType,
+ const QString &urlMethodName,
+ const QString &functorCppType,
+ const QString &objectCppType)
+{
+ QmltcType bindingFunctor {};
+ bindingFunctor.cppType = functorCppType;
+ bindingFunctor.ignoreInit = true;
+
+ // default member variable and ctor:
+ const QString pointerToObject = objectCppType + u" *";
+ bindingFunctor.variables.emplaceBack(
+ QmltcVariable { pointerToObject, u"m_self"_s, u"nullptr"_s });
+ bindingFunctor.baselineCtor.name = functorCppType;
+ bindingFunctor.baselineCtor.parameterList.emplaceBack(
+ QmltcVariable { pointerToObject, u"self"_s, QString() });
+ bindingFunctor.baselineCtor.initializerList.emplaceBack(u"m_self(self)"_s);
+
+ // call operator:
+ QmltcMethod callOperator {};
+ callOperator.returnType = u"void"_s;
+ callOperator.name = u"operator()"_s;
+ callOperator.modifiers << u"const"_s;
+ QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
+ &callOperator.body, urlMethodName + u"()",
+ objectType->ownRuntimeFunctionIndex(binding.scriptIndex()), u"m_self"_s, u"void"_s, {});
+
+ bindingFunctor.functions.emplaceBack(std::move(callOperator));
+
+ return bindingFunctor;
+}
+
+// finds property for given scope and returns it together with the absolute
+// property index in the property array of the corresponding QMetaObject
+static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope,
+ const QString &propertyName)
+{
+ auto owner = QQmlJSScope::ownerOfProperty(scope, propertyName).scope;
+ Q_ASSERT(owner);
+ auto p = owner->ownProperty(propertyName);
+ if (!p.isValid())
+ return { p, -1 };
+ int index = p.index();
+ if (index < 0) // this property doesn't have index - comes from QML
+ return { p, -1 };
+ // owner is not included in absolute index
+
+ // TODO: we also have to go over extensions here!
+ for (QQmlJSScope::ConstPtr base = owner->baseType(); base; base = base->baseType())
+ index += int(base->ownProperties().size());
+ return { p, index };
+}
+
+void QmltcCompiler::compileScriptBinding(QmltcType &current,
+ const QQmlJSMetaPropertyBinding &binding,
+ const QString &bindingSymbolName,
+ const QQmlJSScope::ConstPtr &objectType,
+ const QString &propertyName,
+ const QQmlJSScope::ConstPtr &propertyType,
+ const QmltcCompiler::BindingAccessorData &accessor)
+{
+ const auto compileScriptSignal = [&](const QString &name) {
+ QString This_signal = u"this"_s;
+ QString This_slot = u"this"_s;
+ QString objectClassName_signal = objectType->internalName();
+ QString objectClassName_slot = objectType->internalName();
+
+ // TODO: ugly crutch to make stuff work
+ if (accessor.name != u"this"_s) { // e.g. if attached property
+ This_signal = accessor.name;
+ This_slot = u"this"_s; // still
+ objectClassName_signal = objectType->baseTypeName();
+ objectClassName_slot = current.cppType; // real base class where slot would go
+ }
+ Q_ASSERT(!objectClassName_signal.isEmpty());
+ Q_ASSERT(!objectClassName_slot.isEmpty());
+
+ const auto signalMethods = objectType->methods(name, QQmlJSMetaMethod::Signal);
+ Q_ASSERT(!signalMethods.isEmpty()); // an error somewhere else
+ QQmlJSMetaMethod signal = signalMethods.at(0);
+ Q_ASSERT(signal.methodType() == QQmlJSMetaMethod::Signal);
+
+ const QString signalName = signal.methodName();
+ const QString slotName = newSymbol(signalName + u"_slot");
+
+ const QString signalReturnType = figureReturnType(signal);
+ const QList<QmltcVariable> slotParameters = compileMethodParameters(
+ signal.parameterNames(), signal.parameterTypes(), /* allow unnamed = */ true);
+
+ // SignalHander specific:
+ QmltcMethod slotMethod {};
+ slotMethod.returnType = signalReturnType;
+ slotMethod.name = slotName;
+ slotMethod.parameterList = slotParameters;
+
+ QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
+ &slotMethod.body, m_urlMethodName + u"()",
+ objectType->ownRuntimeFunctionIndex(binding.scriptIndex()),
+ u"this"_s, // Note: because script bindings always use current QML object scope
+ signalReturnType, slotParameters);
+ slotMethod.type = QQmlJSMetaMethod::Slot;
+
+ current.functions << std::move(slotMethod);
+ current.endInit.body << u"QObject::connect(" + This_signal + u", "
+ + QmltcCodeGenerator::wrap_qOverload(slotParameters,
+ u"&" + objectClassName_signal + u"::"
+ + signalName)
+ + u", " + This_slot + u", &" + objectClassName_slot + u"::" + slotName
+ + u");";
+ };
+
+ switch (binding.scriptKind()) {
+ case QQmlJSMetaPropertyBinding::Script_PropertyBinding: {
+ if (!propertyType) {
+ recordError(binding.sourceLocation(),
+ u"Binding on property '" + propertyName + u"' of unknown type");
+ return;
+ }
+
+ auto [property, absoluteIndex] = getMetaPropertyIndex(objectType, propertyName);
+ if (absoluteIndex < 0) {
+ recordError(binding.sourceLocation(),
+ u"Binding on unknown property '" + propertyName + u"'");
+ return;
+ }
+
+ QString bindingTarget = accessor.name;
+
+ int valueTypeIndex = -1;
+ if (accessor.isValueType) {
+ Q_ASSERT(accessor.scope != objectType);
+ bindingTarget = u"this"_s; // TODO: not necessarily "this"?
+ auto [groupProperty, groupPropertyIndex] =
+ getMetaPropertyIndex(accessor.scope, accessor.propertyName);
+ if (groupPropertyIndex < 0) {
+ recordError(binding.sourceLocation(),
+ u"Binding on group property '" + accessor.propertyName
+ + u"' of unknown type");
+ return;
+ }
+ valueTypeIndex = absoluteIndex;
+ absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name
+ }
+
+ QmltcCodeGenerator::generate_createBindingOnProperty(
+ &current.endInit.body, generate_callCompilationUnit(m_urlMethodName),
+ u"this"_s, // NB: always using enclosing object as a scope for the binding
+ static_cast<qsizetype>(objectType->ownRuntimeFunctionIndex(binding.scriptIndex())),
+ bindingTarget, // binding target
+ absoluteIndex, property, valueTypeIndex, accessor.name);
+ break;
+ }
+ case QQmlJSMetaPropertyBinding::Script_SignalHandler: {
+ const auto name = QQmlJSUtils::signalName(propertyName);
+ Q_ASSERT(name.has_value());
+ compileScriptSignal(*name);
+ break;
+ }
+ case QQmlJSMetaPropertyBinding::Script_ChangeHandler: {
+ const QString objectClassName = objectType->internalName();
+ const QString bindingFunctorName = newSymbol(bindingSymbolName + u"Functor");
+
+ const auto signalName = QQmlJSUtils::signalName(propertyName);
+ Q_ASSERT(signalName.has_value()); // an error somewhere else
+ const auto actualProperty = QQmlJSUtils::changeHandlerProperty(objectType, *signalName);
+ Q_ASSERT(actualProperty.has_value()); // an error somewhere else
+ const auto actualPropertyType = actualProperty->type();
+ if (!actualPropertyType) {
+ recordError(binding.sourceLocation(),
+ u"Binding on property '" + actualProperty->propertyName()
+ + u"' of unknown type");
+ return;
+ }
+
+ // due to historical reasons (QQmlObjectCreator), prefer NOTIFY over
+ // BINDABLE when both are available. thus, test for notify first
+ const QString notifyString = actualProperty->notify();
+ if (!notifyString.isEmpty()) {
+ compileScriptSignal(notifyString);
+ break;
+ }
+ const QString bindableString = actualProperty->bindable();
+ QString typeOfQmlBinding =
+ u"std::unique_ptr<QPropertyChangeHandler<" + bindingFunctorName + u">>";
+
+ current.children << compileScriptBindingPropertyChangeHandler(
+ binding, objectType, m_urlMethodName, bindingFunctorName, objectClassName);
+
+ // TODO: this could be dropped if QQmlEngine::setContextForObject() is
+ // done before currently generated C++ object is constructed
+ current.endInit.body << bindingSymbolName + u".reset(new QPropertyChangeHandler<"
+ + bindingFunctorName + u">("
+ + QmltcCodeGenerator::wrap_privateClass(accessor.name, *actualProperty)
+ + u"->" + bindableString + u"().onValueChanged(" + bindingFunctorName + u"("
+ + accessor.name + u"))));";
+
+ current.variables.emplaceBack(
+ QmltcVariable { typeOfQmlBinding, bindingSymbolName, QString() });
+ break;
+ }
+ default:
+ recordError(binding.sourceLocation(), u"Invalid script binding found"_s);
+ break;
+ }
+}
+
+// TODO: should use "compileScriptBinding" instead of custom code
+void QmltcCompiler::compileScriptBindingOfComponent(QmltcType &current,
+ const QQmlJSScope::ConstPtr &type,
+ const QQmlJSMetaPropertyBinding &binding,
+ const QString &propertyName)
+{
+ const auto signalName = QQmlJSUtils::signalName(propertyName);
+ Q_ASSERT(signalName.has_value());
+ const QList<QQmlJSMetaMethod> signalMethods = type->methods(*signalName);
+ Q_ASSERT(!signalMethods.isEmpty());
+ // Component signals do not have parameters
+ Q_ASSERT(signalMethods.at(0).parameterNames().isEmpty());
+ const QString signalReturnType = figureReturnType(signalMethods.at(0));
+ const QString slotName = newSymbol(*signalName + u"_slot");
+
+ // SignalHander specific:
+ QmltcMethod slotMethod {};
+ slotMethod.returnType = signalReturnType;
+ slotMethod.name = slotName;
+
+ // Component is special:
+ QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
+ &slotMethod.body, m_urlMethodName + u"()",
+ type->ownRuntimeFunctionIndex(binding.scriptIndex()), u"this"_s, signalReturnType);
+ slotMethod.type = QQmlJSMetaMethod::Slot;
+
+ // TODO: there's actually an attached type, which has completed/destruction
+ // signals that are typically emitted -- do we care enough about supporting
+ // that? see QQmlComponentAttached
+ if (*signalName == u"completed"_s) {
+ current.handleOnCompleted.body << slotName + u"();";
+ } else if (*signalName == u"destruction"_s) {
+ if (!current.dtor) {
+ current.dtor = QmltcDtor {};
+ current.dtor->name = u"~" + current.cppType;
+ }
+ current.dtor->body << slotName + u"();";
+ }
+ current.functions << std::move(slotMethod);
+}
+
QT_END_NAMESPACE
diff --git a/tools/qmltc/qmltccompiler.h b/tools/qmltc/qmltccompiler.h
index 88d8f1bb67..e5389b69b9 100644
--- a/tools/qmltc/qmltccompiler.h
+++ b/tools/qmltc/qmltccompiler.h
@@ -36,6 +36,7 @@
#include <QtCore/qcommandlineparser.h>
#include <QtCore/qcoreapplication.h>
#include <QtCore/qstring.h>
+#include <QtCore/qhash.h>
#include <private/qqmljslogger_p.h>
@@ -68,6 +69,12 @@ private:
QQmlJSLogger *m_logger = nullptr;
std::unique_ptr<CodeGenerator> m_prototypeCodegen;
QmltcCompilerInfo m_info {}; // miscellaneous input/output information
+ QString m_urlMethodName;
+
+ struct UniqueStringId;
+ struct QmltcTypeLocalData;
+ // per-type, per-property code generation cache of created symbols
+ QHash<UniqueStringId, QmltcTypeLocalData> m_uniques;
void compileUrlMethod(QmltcMethod &urlMethod, const QString &urlMethodName);
void
@@ -103,6 +110,61 @@ private:
void compileBinding(QmltcType &current, const QQmlJSMetaPropertyBinding &binding,
const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor);
+ // special case (for simplicity)
+ void compileScriptBinding(QmltcType &current, const QQmlJSMetaPropertyBinding &binding,
+ const QString &bindingSymbolName, const QQmlJSScope::ConstPtr &type,
+ const QString &propertyName,
+ const QQmlJSScope::ConstPtr &propertyType,
+ const BindingAccessorData &accessor);
+
+ // TODO: remove this special case
+ void compileScriptBindingOfComponent(QmltcType &current, const QQmlJSScope::ConstPtr &type,
+ const QQmlJSMetaPropertyBinding &binding,
+ const QString &propertyName);
+
+ /*!
+ \internal
+ Helper structure that acts as a key in a hash-table of
+ QmltcType-specific data (such as local variable names). Using a
+ hash-table allows to avoid creating the same variables multiple times
+ during binding compilation, which leads to better code generation and
+ faster object creation. This is really something that the QML optimizer
+ should do, but we have only this home-grown alternative at the moment
+ */
+ struct UniqueStringId
+ {
+ QString unique;
+ UniqueStringId(const QmltcType &context, const QString &property)
+ : unique(context.cppType + u"_" + property) // this is unique enough
+ {
+ Q_ASSERT(!context.cppType.isEmpty());
+ Q_ASSERT(!property.isEmpty());
+ }
+ friend bool operator==(const UniqueStringId &x, const UniqueStringId &y)
+ {
+ return x.unique == y.unique;
+ }
+ friend bool operator!=(const UniqueStringId &x, const UniqueStringId &y)
+ {
+ return !(x == y);
+ }
+ friend size_t qHash(const UniqueStringId &x, size_t seed = 0)
+ {
+ return qHash(x.unique, seed);
+ }
+ };
+
+ struct QmltcTypeLocalData
+ {
+ // empty QString() means that the local data is not present (yet)
+ QString qmlListVariableName;
+ QString onAssignmentObjectName;
+ QString attachedVariableName;
+ };
+
+ QHash<QString, qsizetype> m_symbols;
+ QString newSymbol(const QString &base);
+
bool hasErrors() const { return m_logger->hasErrors(); }
void recordError(const QQmlJS::SourceLocation &location, const QString &message,
QQmlJSLoggerCategory category = Log_Compiler)
diff --git a/tools/qmltc/qmltccompilerpieces.cpp b/tools/qmltc/qmltccompilerpieces.cpp
index 76faf6aa56..f3bc578495 100644
--- a/tools/qmltc/qmltccompilerpieces.cpp
+++ b/tools/qmltc/qmltccompilerpieces.cpp
@@ -168,11 +168,9 @@ void QmltcCodeGenerator::generate_setIdValue(QStringList *block, const QString &
idString, accessor);
}
-void QmltcCodeGenerator::generate_callExecuteRuntimeFunction(QStringList *block, const QString &url,
- qsizetype index,
- const QString &accessor,
- const QString &returnType,
- const QList<QmltcVariable> &parameters)
+void QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
+ QStringList *block, const QString &url, QQmlJSMetaMethod::AbsoluteFunctionIndex index,
+ const QString &accessor, const QString &returnType, const QList<QmltcVariable> &parameters)
{
*block << u"QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(" + accessor + u"));";
@@ -199,7 +197,9 @@ void QmltcCodeGenerator::generate_callExecuteRuntimeFunction(QStringList *block,
*block << u"void *_a[] = { " + args.join(u", "_s) + u" };";
*block << u"QMetaType _t[] = { " + types.join(u", "_s) + u" };";
- *block << u"e->executeRuntimeFunction(" + url + u", " + QString::number(index) + u", "
+ const qsizetype runtimeIndex = static_cast<qsizetype>(index);
+ Q_ASSERT(runtimeIndex >= 0);
+ *block << u"e->executeRuntimeFunction(" + url + u", " + QString::number(runtimeIndex) + u", "
+ accessor + u", " + QString::number(parameters.size()) + u", _a, _t);";
if (returnType != u"void"_s)
*block << u"return " + returnValueName + u";";
diff --git a/tools/qmltc/qmltccompilerpieces.h b/tools/qmltc/qmltccompilerpieces.h
index 2544e5fd9f..545e347caf 100644
--- a/tools/qmltc/qmltccompilerpieces.h
+++ b/tools/qmltc/qmltccompilerpieces.h
@@ -89,7 +89,8 @@ struct QmltcCodeGenerator
inline QString generate_typeCount(Predicate p) const;
static void generate_callExecuteRuntimeFunction(QStringList *block, const QString &url,
- qsizetype index, const QString &accessor,
+ QQmlJSMetaMethod::AbsoluteFunctionIndex index,
+ const QString &accessor,
const QString &returnType,
const QList<QmltcVariable> &parameters = {});
@@ -233,8 +234,11 @@ inline decltype(auto) QmltcCodeGenerator::generate_initCode(QmltcType &current,
if (int id = visitor->runtimeId(type); id >= 0) {
current.init.body << u"// 3. set id since it is provided"_s;
+ QString idString = visitor->addressableScopes().id(type);
+ if (idString.isEmpty())
+ idString = u"<unknown>"_s;
QmltcCodeGenerator::generate_setIdValue(&current.init.body, u"context"_s, id, u"this"_s,
- u"<unknown>"_s);
+ idString);
}
// if type has an extension, create a dynamic meta object for it
diff --git a/tools/qmltc/qmltcvisitor.cpp b/tools/qmltc/qmltcvisitor.cpp
index 6fe5973e2a..e37e21a570 100644
--- a/tools/qmltc/qmltcvisitor.cpp
+++ b/tools/qmltc/qmltcvisitor.cpp
@@ -31,6 +31,8 @@
#include <QtCore/qfileinfo.h>
#include <QtCore/qstack.h>
+#include <private/qqmljsutils_p.h>
+
#include <algorithm>
QT_BEGIN_NAMESPACE
@@ -148,30 +150,6 @@ void QmltcVisitor::findCppIncludes()
}
}
-void QmltcVisitor::findTypeIndicesInQmlDocument()
-{
- qsizetype count = 0;
-
- // Perform DFS to align with the logic of discovering new QmlIR::Objects
- // during IR building: we should align with it here to get correct object
- // indices within the QmlIR::Document.
- QList<QQmlJSScope::Ptr> stack;
- stack.append(m_exportedRootScope);
-
- while (!stack.isEmpty()) {
- QQmlJSScope::Ptr current = stack.takeLast();
-
- if (current->scopeType() == QQmlJSScope::QMLScope) {
- Q_ASSERT(!m_qmlIrObjectIndices.contains(current));
- m_qmlIrObjectIndices[current] = count;
- ++count;
- }
-
- const auto &children = current->childScopes();
- std::copy(children.rbegin(), children.rend(), std::back_inserter(stack));
- }
-}
-
bool QmltcVisitor::visit(QQmlJS::AST::UiObjectDefinition *object)
{
if (!QQmlJSImportVisitor::visit(object))
@@ -307,7 +285,6 @@ void QmltcVisitor::endVisit(QQmlJS::AST::UiProgram *program)
postVisitResolve(bindings);
findCppIncludes();
- findTypeIndicesInQmlDocument();
}
QQmlJSScope::ConstPtr fetchType(const QQmlJSMetaPropertyBinding &binding)
@@ -399,8 +376,19 @@ void QmltcVisitor::postVisitResolve(
// resolve things that couldn't be resolved during the AST traversal, such
// as anything that is dependent on implicit or explicit components
- // 1. find types that are part of the deferred bindings (we care about
- // *types* exclusively here)
+ // match scopes to indices of QmlIR::Object from QmlIR::Document
+ qsizetype count = 0;
+ const auto setIndex = [&](const QQmlJSScope::Ptr &current) {
+ if (current->scopeType() != QQmlJSScope::QMLScope || current->isArrayScope())
+ return;
+ Q_ASSERT(!m_qmlIrObjectIndices.contains(current));
+ m_qmlIrObjectIndices[current] = count;
+ ++count;
+ };
+ QQmlJSUtils::traverseFollowingQmlIrObjectStructure(m_exportedRootScope, setIndex);
+
+ // find types that are part of the deferred bindings (we care about *types*
+ // exclusively here)
QSet<QQmlJSScope::ConstPtr> deferredTypes;
const auto findDeferred = [&](const QQmlJSScope::ConstPtr &type,
const QQmlJSMetaPropertyBinding &binding) {
@@ -426,7 +414,7 @@ void QmltcVisitor::postVisitResolve(
return false;
};
- // 2. find all "pure" QML types
+ // find all "pure" QML types
m_pureQmlTypes.reserve(m_qmlTypes.size());
for (qsizetype i = 0; i < m_qmlTypes.size(); ++i) {
const QQmlJSScope::ConstPtr &type = m_qmlTypes.at(i);
@@ -453,24 +441,32 @@ void QmltcVisitor::postVisitResolve(
qSwap(m_qmlTypesWithQmlBases, filteredQmlTypesWithQmlBases);
}
- // 3. figure synthetic indices of QQmlComponent-wrapped types
- int syntheticCreationIndex = -1;
- const auto addSyntheticIndex = [&](const QQmlJSScope::ConstPtr &type) mutable {
- if (type->isComponentRootElement()) {
- m_syntheticTypeIndices[type] = ++syntheticCreationIndex;
+ // figure synthetic indices of QQmlComponent-wrapped types
+ int syntheticCreationIndex = 0;
+ const auto addSyntheticIndex = [&](const QQmlJSScope::ConstPtr &type) {
+ // explicit component
+ if (auto base = type->baseType(); base && base->internalName() == u"QQmlComponent"_s) {
+ m_syntheticTypeIndices[type] = m_qmlIrObjectIndices.value(type, -1);
+ return true;
+ }
+ // implicit component
+ const auto cppBase = QQmlJSScope::nonCompositeBaseType(type);
+ const bool isComponentBased = (cppBase && cppBase->internalName() == u"QQmlComponent"_s);
+ if (type->isComponentRootElement() && !isComponentBased) {
+ const int index = int(m_qmlTypes.size()) + syntheticCreationIndex++;
+ m_syntheticTypeIndices[type] = index;
return true;
}
return false;
};
iterateTypes(m_exportedRootScope, qmlIrOrderedBindings, addSyntheticIndex);
- // 4. figure runtime object ids for non-component wrapped types
+ // figure runtime object ids for non-component wrapped types
int currentId = 0;
- const auto setRuntimeId = [&](const QQmlJSScope::ConstPtr &type) mutable {
- // fancy way to call type->isComponentRootElement(). any type that is
- // considered synthetic shouldn't be processed here. even if it has id,
- // it doesn't need to be set by qmltc
- if (m_syntheticTypeIndices.contains(type))
+ const auto setRuntimeId = [&](const QQmlJSScope::ConstPtr &type) {
+ // any type wrapped in an implicit component shouldn't be processed
+ // here. even if it has id, it doesn't need to be set by qmltc
+ if (type->isComponentRootElement())
return true;
if (m_typesWithId.contains(type))
diff --git a/tools/qmltc/qmltcvisitor.h b/tools/qmltc/qmltcvisitor.h
index af239e9f7b..4f9a98bfba 100644
--- a/tools/qmltc/qmltcvisitor.h
+++ b/tools/qmltc/qmltcvisitor.h
@@ -42,7 +42,6 @@ QT_BEGIN_NAMESPACE
class QmltcVisitor : public QQmlJSImportVisitor
{
void findCppIncludes();
- void findTypeIndicesInQmlDocument();
void postVisitResolve(const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>>
&qmlIrOrderedBindings);
@@ -77,16 +76,14 @@ public:
qsizetype qmlComponentIndex(const QQmlJSScope::ConstPtr &type) const
{
- Q_ASSERT(m_syntheticTypeIndices.contains(type));
Q_ASSERT(type->scopeType() == QQmlJSScope::QMLScope);
- Q_ASSERT(type->isComponentRootElement());
- return m_syntheticTypeIndices[type] + qmlTypes().size();
+ return m_syntheticTypeIndices.value(type, -1);
}
qsizetype qmlIrObjectIndex(const QQmlJSScope::ConstPtr &type) const
{
- Q_ASSERT(m_qmlIrObjectIndices.contains(type));
Q_ASSERT(type->scopeType() == QQmlJSScope::QMLScope);
+ Q_ASSERT(m_qmlIrObjectIndices.contains(type));
return m_qmlIrObjectIndices[type];
}