// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmltccompiler.h" #include "qmltcoutputir.h" #include "qmltccodewriter.h" #include "qmltcpropertyutils.h" #include "qmltccompilerpieces.h" #include #include #include #include QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; bool qIsReferenceTypeList(const QQmlJSMetaProperty &p) { if (QQmlJSScope::ConstPtr type = p.type()) return type->isListProperty(); return false; } static QList unboundRequiredProperties( const QQmlJSScope::ConstPtr &type, QmltcTypeResolver *resolver ) { QList requiredProperties{}; auto isPropertyRequired = [&type, &resolver](const auto &property) { if (!type->isPropertyRequired(property.propertyName())) return false; if (type->hasPropertyBindings(property.propertyName())) return false; if (property.isAlias()) { QQmlJSUtils::AliasResolutionVisitor aliasVisitor; QQmlJSUtils::ResolvedAlias result = QQmlJSUtils::resolveAlias(resolver, property, type, aliasVisitor); if (result.kind != QQmlJSUtils::AliasTarget_Property) return false; // If the top level alias targets a property that is in // the top level scope and that property is required, then // we will already pick up the property during one of the // iterations. // Setting the property or the alias is the same so we // discard one of the two, as otherwise we would require // the user to pass two values for the same property ,in // this case the alias. // // For example in: // // ``` // Item { // id: self // required property int foo // property alias bar: self.foo // } // ``` // // Both foo and bar are required but setting one or the // other is the same operation so that we should choose // only one. if (result.owner == type && type->isPropertyRequired(result.property.propertyName())) return false; if (result.owner->hasPropertyBindings(result.property.propertyName())) return false; } return true; }; const auto properties = type->properties(); std::copy_if(properties.cbegin(), properties.cend(), std::back_inserter(requiredProperties), isPropertyRequired); std::sort(requiredProperties.begin(), requiredProperties.end(), [](const auto &left, const auto &right) { return left.propertyName() < right.propertyName(); }); return requiredProperties; } // Populates the internal representation for a // RequiredPropertiesBundle, a class that acts as a bundle of initial // values that should be set for the required properties of a type. static void compileRequiredPropertiesBundle( QmltcType ¤t, const QQmlJSScope::ConstPtr &type, QmltcTypeResolver *resolver ) { QList requiredProperties = unboundRequiredProperties(type, resolver); if (requiredProperties.isEmpty()) return; current.requiredPropertiesBundle.emplace(); current.requiredPropertiesBundle->name = u"RequiredPropertiesBundle"_s; current.requiredPropertiesBundle->members.reserve(requiredProperties.size()); std::transform(requiredProperties.cbegin(), requiredProperties.cend(), std::back_inserter(current.requiredPropertiesBundle->members), [](const QQmlJSMetaProperty &property) { QString type = qIsReferenceTypeList(property) ? u"const QList<%1*>&"_s.arg( property.type()->valueType()->internalName()) : u"passByConstRefOrValue<%1>"_s.arg( property.type()->augmentedInternalName()); return QmltcVariable{ type, property.propertyName() }; }); } static void compileRootExternalConstructorBody( QmltcType& current, const QQmlJSScope::ConstPtr &type ) { current.externalCtor.body << u"// document root:"_s; // if it's document root, we want to create our QQmltcObjectCreationBase // that would store all the created objects current.externalCtor.body << u"QQmltcObjectCreationBase<%1> objectHolder;"_s.arg( type->internalName()); current.externalCtor.body << u"QQmltcObjectCreationHelper creator = objectHolder.view();"_s; current.externalCtor.body << u"creator.set(0, this);"_s; // special case QString initializerName = u"initializer"_s; if (current.requiredPropertiesBundle) { // Compose new initializer based on the initial values for required properties. current.externalCtor.body << u"auto newInitializer = [&](auto& propertyInitializer) {"_s; for (const auto& member : current.requiredPropertiesBundle->members) { current.externalCtor.body << u" propertyInitializer.%1(requiredPropertiesBundle.%2);"_s.arg( QmltcPropertyData(member.name).write, member.name ); } current.externalCtor.body << u" initializer(propertyInitializer);"_s; current.externalCtor.body << u"};"_s; initializerName = u"newInitializer"_s; } // now call init current.externalCtor.body << current.init.name + u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* " u"endInit */ true, %1);"_s.arg(initializerName); }; Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg); const QString QmltcCodeGenerator::privateEngineName = u"ePriv"_s; const QString QmltcCodeGenerator::typeCountName = u"q_qmltc_typeCount"_s; QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor, QQmlJSLogger *logger) : m_http://code.qt.io/url(url), m_typeResolver(resolver), m_visitor(visitor), m_logger(logger) { Q_UNUSED(m_typeResolver); Q_ASSERT(!hasErrors()); } // needed due to std::unique_ptr with CodeGenerator being // 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) { m_info = info; Q_ASSERT(!m_info.outputCppFile.isEmpty()); Q_ASSERT(!m_info.outputHFile.isEmpty()); Q_ASSERT(!m_info.resourcePath.isEmpty()); // Note: we only compile "pure" QML types. any component-wrapped type is // expected to appear through a binding const auto isComponent = [](const QQmlJSScope::ConstPtr &type) { auto base = type->baseType(); return base && base->internalName() == u"QQmlComponent"_s; }; QmltcCodeGenerator generator { m_url, m_visitor }; QmltcMethod urlMethod; compileUrlMethod(urlMethod, generator.urlMethodName()); m_urlMethodName = urlMethod.name; // sort inline components to compile them in the right order // a inherits b => b needs to be defined in the cpp file before a! // r is the root => r needs to be compiled at the end! // otherwise => sort them by inline component names to have consistent output auto sortedInlineComponentNames = m_visitor->inlineComponentNames(); std::sort(sortedInlineComponentNames.begin(), sortedInlineComponentNames.end(), [&](const InlineComponentOrDocumentRootName &a, const InlineComponentOrDocumentRootName &b) { const auto *inlineComponentAName = std::get_if(&a); const auto *inlineComponentBName = std::get_if(&b); if (inlineComponentAName == inlineComponentBName) return false; // the root comes at last, so (a < b) == true when b is the root and a is not if (inlineComponentAName && !inlineComponentBName) return true; // b requires a to be declared before b when b inherits from a, therefore (a < b) // == true if (inlineComponentAName && inlineComponentBName) { QQmlJSScope::ConstPtr inlineComponentA = m_visitor->inlineComponent(a); QQmlJSScope::ConstPtr inlineComponentB = m_visitor->inlineComponent(b); if (inlineComponentB->inherits(inlineComponentA)) { return true; } else if (inlineComponentA->inherits(inlineComponentB)) { return false; } else { // fallback to default sorting based on names return *inlineComponentAName < *inlineComponentBName; } } Q_ASSERT(!inlineComponentAName || !inlineComponentBName); // a is the root or both a and b are the root return false; }); QList compiledTypes; for (const auto &inlineComponent : sortedInlineComponentNames) { const QList &pureTypes = m_visitor->pureQmlTypes(inlineComponent); Q_ASSERT(!pureTypes.empty()); const QQmlJSScope::ConstPtr &root = pureTypes.front(); if (isComponent(root)) { compiledTypes.emplaceBack(); // create empty type const auto compile = [&](QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { generator.generate_initCodeForTopLevelComponent(current, type); }; compileType(compiledTypes.back(), root, compile); } else { const auto compile = [this](QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { compileTypeElements(current, type); }; for (const auto &type : pureTypes) { Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope); compiledTypes.emplaceBack(); // create empty type compileType(compiledTypes.back(), type, compile); } } } if (hasErrors()) return; QmltcProgram program; program.url = m_url; program.cppPath = m_info.outputCppFile; program.hPath = m_info.outputHFile; program.outNamespace = m_info.outputNamespace; program.exportMacro = m_info.exportMacro; program.compiledTypes = compiledTypes; program.includes = m_visitor->cppIncludeFiles(); if (!m_info.exportMacro.isEmpty() && !m_info.exportInclude.isEmpty()) program.includes += (m_info.exportInclude); program.urlMethod = urlMethod; QmltcOutput out; QmltcOutputWrapper code(out); QmltcCodeWriter::write(code, program); } void QmltcCompiler::compileUrlMethod(QmltcMethod &urlMethod, const QString &urlMethodName) { urlMethod.name = urlMethodName; urlMethod.returnType = u"const QUrl&"_s; urlMethod.body << u"static QUrl url {QStringLiteral(\"qrc:%1\")};"_s.arg(m_info.resourcePath); urlMethod.body << u"return url;"_s; urlMethod.declarationPrefixes << u"static"_s; urlMethod.modifiers << u"noexcept"_s; } void QmltcCompiler::compileType( QmltcType ¤t, const QQmlJSScope::ConstPtr &type, std::function compileElements) { Q_ASSERT(!type->internalName().isEmpty()); current.cppType = type->internalName(); Q_ASSERT(!type->baseType()->internalName().isEmpty()); const QString baseClass = type->baseType()->internalName(); const auto rootType = m_visitor->result(); const InlineComponentOrDocumentRootName name = type->enclosingInlineComponentName(); QQmlJSScope::ConstPtr inlineComponentType = m_visitor->inlineComponent(name); Q_ASSERT(inlineComponentType); const bool documentRoot = (type == rootType); const bool inlineComponent = type->isInlineComponent(); const bool isAnonymous = !documentRoot || type->internalName().at(0).isLower(); const bool isSingleton = type->isSingleton(); QmltcCodeGenerator generator { m_url, m_visitor }; current.baseClasses = { baseClass }; if (!documentRoot) { // make document root a friend to allow it to access init and endInit const QString rootInternalName = m_visitor->inlineComponent(type->enclosingInlineComponentName())->internalName(); if (rootInternalName != current.cppType) // avoid GCC13 warning on self-befriending current.otherCode << "friend class %1;"_L1.arg(rootInternalName); } if (documentRoot || inlineComponent) { auto name = type->inlineComponentName() ? InlineComponentOrDocumentRootName(*type->inlineComponentName()) : InlineComponentOrDocumentRootName(RootDocumentNameType()); // make QQmltcObjectCreationBase a friend to allow it to // be created for the root object current.otherCode << u"friend class QQmltcObjectCreationBase<%1>;"_s.arg( inlineComponentType->internalName()); // generate typeCount for all components (root + inlineComponents) QmltcMethod typeCountMethod; typeCountMethod.name = QmltcCodeGenerator::typeCountName; typeCountMethod.returnType = u"uint"_s; typeCountMethod.body << u"return " + generator.generate_typeCount(name) + u";"; current.typeCount = typeCountMethod; } else { // make an immediate parent a friend since that parent // would create the object through a non-public constructor const auto realQmlScope = [](const QQmlJSScope::ConstPtr &scope) { if (scope->isArrayScope()) return scope->parentScope(); return scope; }; const auto& realScope = realQmlScope(type->parentScope()); if (realScope != rootType) { current.otherCode << u"friend class %1;"_s.arg(realScope->internalName()); } } // make QQmltcObjectCreationHelper a friend of every type since it provides // useful helper methods for all types current.otherCode << u"friend class QT_PREPEND_NAMESPACE(QQmltcObjectCreationHelper);"_s; current.mocCode = { u"Q_OBJECT"_s, // Note: isAnonymous holds for non-root types in the document as well type->isInlineComponent() ? (u"QML_NAMED_ELEMENT(%1)"_s.arg(*type->inlineComponentName())) : (isAnonymous ? u"QML_ANONYMOUS"_s : u"QML_ELEMENT"_s), }; // add special member functions current.baselineCtor.access = QQmlJSMetaMethod::Protected; if (documentRoot || inlineComponent || isSingleton) { current.externalCtor.access = QQmlJSMetaMethod::Public; } else { current.externalCtor.access = QQmlJSMetaMethod::Protected; } current.init.access = QQmlJSMetaMethod::Protected; current.beginClass.access = QQmlJSMetaMethod::Protected; current.endInit.access = QQmlJSMetaMethod::Protected; current.setComplexBindings.access = QQmlJSMetaMethod::Protected; current.completeComponent.access = QQmlJSMetaMethod::Protected; current.finalizeComponent.access = QQmlJSMetaMethod::Protected; current.handleOnCompleted.access = QQmlJSMetaMethod::Protected; current.propertyInitializer.name = u"PropertyInitializer"_s; current.propertyInitializer.constructor.access = QQmlJSMetaMethod::Public; current.propertyInitializer.constructor.name = current.propertyInitializer.name; current.propertyInitializer.constructor.parameterList = { QmltcVariable(u"%1&"_s.arg(current.cppType), u"component"_s) }; current.propertyInitializer.component.cppType = current.cppType + u"&"; current.propertyInitializer.component.name = u"component"_s; current.propertyInitializer.initializedCache.cppType = u"QSet"_s; current.propertyInitializer.initializedCache.name = u"initializedCache"_s; current.baselineCtor.name = current.cppType; current.externalCtor.name = current.cppType; current.init.name = u"QML_init"_s; current.init.returnType = u"QQmlRefPointer"_s; current.beginClass.name = u"QML_beginClass"_s; current.beginClass.returnType = u"void"_s; current.endInit.name = u"QML_endInit"_s; current.endInit.returnType = u"void"_s; current.setComplexBindings.name = u"QML_setComplexBindings"_s; current.setComplexBindings.returnType = u"void"_s; current.completeComponent.name = u"QML_completeComponent"_s; current.completeComponent.returnType = u"void"_s; current.finalizeComponent.name = u"QML_finalizeComponent"_s; current.finalizeComponent.returnType = u"void"_s; current.handleOnCompleted.name = u"QML_handleOnCompleted"_s; current.handleOnCompleted.returnType = u"void"_s; QmltcVariable creator(u"QQmltcObjectCreationHelper*"_s, u"creator"_s); QmltcVariable engine(u"QQmlEngine*"_s, u"engine"_s); QmltcVariable parent(u"QObject*"_s, u"parent"_s, u"nullptr"_s); QmltcVariable initializedCache( u"[[maybe_unused]] const QSet&"_s, u"initializedCache"_s, u"{}"_s ); QmltcVariable ctxtdata(u"const QQmlRefPointer&"_s, u"parentContext"_s); QmltcVariable finalizeFlag(u"bool"_s, u"canFinalize"_s); current.baselineCtor.parameterList = { parent }; current.endInit.parameterList = { creator, engine }; current.setComplexBindings.parameterList = { creator, engine, initializedCache }; current.handleOnCompleted.parameterList = { creator }; if (documentRoot || inlineComponent) { const QmltcVariable initializer( u"[[maybe_unused]] qxp::function_ref"_s.arg(current.propertyInitializer.name), u"initializer"_s, u"[](%1&){}"_s.arg(current.propertyInitializer.name)); current.init.parameterList = { creator, engine, ctxtdata, finalizeFlag, initializer }; current.beginClass.parameterList = { creator, finalizeFlag }; current.completeComponent.parameterList = { creator, finalizeFlag }; current.finalizeComponent.parameterList = { creator, finalizeFlag }; compileRequiredPropertiesBundle(current, type, m_typeResolver); if (current.requiredPropertiesBundle) { QmltcVariable bundle{ u"const %1&"_s.arg(current.requiredPropertiesBundle->name), u"requiredPropertiesBundle"_s, }; current.externalCtor.parameterList = { engine, bundle, parent, initializer }; } else { current.externalCtor.parameterList = { engine, parent, initializer }; } } else { current.externalCtor.parameterList = { creator, engine, parent }; current.init.parameterList = { creator, engine, ctxtdata }; current.beginClass.parameterList = { creator }; current.completeComponent.parameterList = { creator }; current.finalizeComponent.parameterList = { creator }; } current.externalCtor.initializerList = { current.baselineCtor.name + u"(" + parent.name + u")" }; if (QQmlJSUtils::hasCompositeBase(type)) { // call parent's (QML type's) basic ctor from this. that one will take // care about QObject::setParent() current.baselineCtor.initializerList = { baseClass + u"(" + parent.name + u")" }; } else { // default call to ctor is enough, but QQml_setParent_noEvent() is // needed (note: faster? version of QObject::setParent()) current.baselineCtor.body << u"QQml_setParent_noEvent(this, " + parent.name + u");"; } // compilation stub: current.externalCtor.body << u"Q_UNUSED(engine)"_s; if (documentRoot || inlineComponent) { compileRootExternalConstructorBody(current, type); } else { current.externalCtor.body << u"// not document root:"_s; // just call init, we don't do any setup here otherwise current.externalCtor.body << current.init.name + u"(creator, engine, QQmlData::get(parent)->outerContext);"; } if (isSingleton) { // see https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON for context current.mocCode.append(u"QML_SINGLETON"_s); auto &staticCreate = current.staticCreate.emplace(); staticCreate.comments << u"Used by the engine for singleton creation."_s << u"See also \\l {https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON}."_s; staticCreate.type = QQmlJSMetaMethodType::StaticMethod; staticCreate.access = QQmlJSMetaMethod::Public; staticCreate.name = u"create"_s; staticCreate.returnType = u"%1 *"_s.arg(current.cppType); QmltcVariable jsEngine(u"QJSEngine*"_s, u"jsEngine"_s); staticCreate.parameterList = { engine, jsEngine }; staticCreate.body << u"Q_UNUSED(jsEngine);"_s << u"%1 *result = new %1(engine, nullptr);"_s.arg(current.cppType) << u"return result;"_s; } auto postponedQmlContextSetup = generator.generate_initCode(current, type); generator.generate_endInitCode(current, type); generator.generate_setComplexBindingsCode(current, type); generator.generate_beginClassCode(current, type); generator.generate_completeComponentCode(current, type); generator.generate_finalizeComponentCode(current, type); generator.generate_handleOnCompletedCode(current, type); compileElements(current, type); } template static Iterator partitionBindings(Iterator first, Iterator last) { // 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 complex bindings to be at the end, so do the negation return !QmltcCompiler::isComplexBinding(b); }); } // Populates the propertyInitializer of the current type based on the // available properties. // // A propertyInitializer is a generated class that provides a // restricted interface that only allows setting property values and // internally keep tracks of which properties where actually set, // intended to be used to allow the user to set up the initial values // when creating an instance of a component. // // For each property of the current type that is known, is not private // and is writable, a setter method is generated. // Each setter method knows how to set a specific property, so as to // provide a strongly typed interface to property setting, as if the // relevant C++ type was used directly. // // Each setter uses the write method for the proprerty when available // and otherwise falls back to a the more generic // `QObject::setProperty` for properties where a WRITE method is not // available or in scope. static void compilePropertyInitializer(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { static auto isFromExtension = [](const QQmlJSMetaProperty &property, const QQmlJSScope::ConstPtr &scope) { return scope->ownerOfProperty(scope, property.propertyName()).extensionSpecifier != QQmlJSScope::NotExtension; }; current.propertyInitializer.constructor.initializerList << u"component{component}"_s; auto properties = type->properties().values(); for (auto& property: properties) { if (property.index() == -1) continue; if (property.isPrivate()) continue; if (!property.isWritable() && !qIsReferenceTypeList(property)) continue; const QString name = property.propertyName(); current.propertyInitializer.propertySetters.emplace_back(); auto& compiledSetter = current.propertyInitializer.propertySetters.back(); compiledSetter.userVisible = true; compiledSetter.returnType = u"void"_s; compiledSetter.name = QmltcPropertyData(property).write; if (qIsReferenceTypeList(property)) { compiledSetter.parameterList.emplaceBack( QQmlJSUtils::constRefify(u"QList<%1*>"_s.arg(property.type()->valueType()->internalName())), name + u"_", QString() ); } else { compiledSetter.parameterList.emplaceBack( QQmlJSUtils::constRefify(getUnderlyingType(property)), name + u"_", QString() ); } if (qIsReferenceTypeList(property)) { compiledSetter.body << u"QQmlListReference list_ref_(&%1, \"%2\");"_s.arg( current.propertyInitializer.component.name, name ); compiledSetter.body << u"list_ref_.clear();"_s; compiledSetter.body << u"for (const auto& list_item_ : %1_)"_s.arg(name); compiledSetter.body << u" list_ref_.append(list_item_);"_s; } else if ( QQmlJSUtils::bindablePropertyHasDefaultAccessor(property, QQmlJSUtils::PropertyAccessor_Write) ) { compiledSetter.body << u"%1.%2().setValue(%3_);"_s.arg( current.propertyInitializer.component.name, property.bindable(), name); } else if (type->hasOwnProperty(name)) { compiledSetter.body << u"%1.%2(%3_);"_s.arg( current.propertyInitializer.component.name, QmltcPropertyData(property).write, name); } else if (property.write().isEmpty() || isFromExtension(property, type)) { // We can end here if a WRITE method is not available or // if the method is available but not in this scope, so // that we fallback to the string-based setters.. // // For example, types that makes use of QML_EXTENDED // types, will have the extension types properties // available and with a WRITE method, but the WRITE method // will not be available to the extended type, from C++, // as the type does not directly inherit from the // extension type. // // We specifically scope `setProperty` to `QObject` as // certain types might have shadowed the method. // For example, in QtQuick, some types have a property // called `property` with a `setProperty` WRITE method // that will produce the shadowing. compiledSetter.body << u"%1.QObject::setProperty(\"%2\", QVariant::fromValue(%2_));"_s.arg( current.propertyInitializer.component.name, name); } else { compiledSetter.body << u"%1.%2(%3_);"_s.arg( current.propertyInitializer.component.name, property.write(), name); } compiledSetter.body << u"%1.insert(QStringLiteral(\"%2\"));"_s.arg( current.propertyInitializer.initializedCache.name, name); } } void QmltcCompiler::compileTypeElements(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { // compile components of a type: // - enums // - properties // - methods // - bindings const auto enums = type->ownEnumerations(); current.enums.reserve(enums.size()); for (auto it = enums.begin(); it != enums.end(); ++it) compileEnum(current, it.value()); auto properties = type->ownProperties().values(); current.properties.reserve(properties.size()); // Note: index() is the (future) meta property index, so make sure given // properties are ordered by that index before compiling std::sort(properties.begin(), properties.end(), [](const QQmlJSMetaProperty &x, const QQmlJSMetaProperty &y) { return x.index() < y.index(); }); for (const QQmlJSMetaProperty &p : std::as_const(properties)) { if (p.index() == -1) { recordError(type->sourceLocation(), u"Internal error: property '%1' has incomplete information"_s.arg( p.propertyName())); continue; } if (p.isAlias()) { compileAlias(current, p, type); } else { compileProperty(current, p, type); } } const auto methods = type->ownMethods(); for (const QQmlJSMetaMethod &m : methods) compileMethod(current, m, type); auto bindings = type->ownPropertyBindingsInQmlIROrder(); partitionBindings(bindings.begin(), bindings.end()); compilePropertyInitializer(current, type); compileBinding(current, bindings.begin(), bindings.end(), type, { type }); } void QmltcCompiler::compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e) { const auto intValues = e.values(); QStringList values; values.reserve(intValues.size()); std::transform(intValues.cbegin(), intValues.cend(), std::back_inserter(values), [](int x) { return QString::number(x); }); // structure: (C++ type name, enum keys, enum values, MOC line) current.enums.emplaceBack(e.name(), e.keys(), std::move(values), u"Q_ENUM(%1)"_s.arg(e.name())); } static QList compileMethodParameters(const QList ¶meterInfos, bool allowUnnamed = false) { QList parameters; const auto size = parameterInfos.size(); parameters.reserve(size); for (qsizetype i = 0; i < size; ++i) { const auto &p = parameterInfos[i]; Q_ASSERT(p.type()); // assume verified QString name = p.name(); Q_ASSERT(allowUnnamed || !name.isEmpty()); // assume verified if (name.isEmpty() && allowUnnamed) name = u"unnamed_" + QString::number(i); QString internalName; const QQmlJSScope::AccessSemantics semantics = p.type()->accessSemantics(); switch (semantics) { case QQmlJSScope::AccessSemantics::Reference: if (p.typeQualifier() == QQmlJSMetaParameter::Const) internalName = u"const "_s; internalName += u"%1*"_s.arg(p.type()->internalName()); break; case QQmlJSScope::AccessSemantics::Value: case QQmlJSScope::AccessSemantics::Sequence: internalName = u"passByConstRefOrValue<%1>"_s.arg(p.type()->internalName()); break; case QQmlJSScope::AccessSemantics::None: Q_ASSERT(false); // or maybe print an error message } parameters.emplaceBack(internalName, name, QString()); } return parameters; } void QmltcCompiler::compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m, const QQmlJSScope::ConstPtr &owner) { const QString returnType = m.returnType()->augmentedInternalName(); const QList compiledParams = compileMethodParameters(m.parameters()); const auto methodType = m.methodType(); QStringList code; if (methodType != QQmlJSMetaMethodType::Signal) { QmltcCodeGenerator urlGenerator { m_url, m_visitor }; QmltcCodeGenerator::generate_callExecuteRuntimeFunction( &code, urlGenerator.urlMethodName() + u"()", owner->ownRuntimeFunctionIndex(m.jsFunctionIndex()), u"this"_s, returnType, compiledParams); } QmltcMethod compiled {}; compiled.returnType = returnType; compiled.name = m.methodName(); compiled.parameterList = std::move(compiledParams); compiled.body = std::move(code); compiled.type = methodType; compiled.access = m.access(); if (methodType != QQmlJSMetaMethodType::Signal) { compiled.declarationPrefixes << u"Q_INVOKABLE"_s; compiled.userVisible = m.access() == QQmlJSMetaMethod::Public; } else { compiled.userVisible = !m.isImplicitQmlPropertyChangeSignal(); } current.functions.emplaceBack(compiled); } /*! \internal Compiles an extra set of methods for Lists, that makes manipulating lists easier from C++ for the user. */ void QmltcCompiler::compileExtraListMethods(QmltcType ¤t, const QQmlJSMetaProperty &p) { QmltcPropertyData data(p); const QString valueType = p.type()->valueType()->internalName() + u'*'; const QString variableName = data.read + u"()"_s; const QStringList ownershipWarning = { u"\\note {This method does not change the ownership of its argument."_s, u"The caller is responsible for setting the argument's \\c {QObject::parent} or"_s, u"for ensuring that the argument lives long enough."_s, u"For example, an argument created with \\c {createObject()} that has no parent"_s, u"will eventually be garbage-collected, leaving a dangling pointer.}"_s }; // generate append() sugar for users { QmltcMethod append{}; append.comments.emplaceBack(u"\\brief Append an element to %1."_s.arg(data.read)); append.comments << ownershipWarning; append.returnType = u"void"_s; append.name = u"%1Append"_s.arg(data.read); append.parameterList.emplaceBack(valueType, u"toBeAppended"_s); append.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName); append.body << u"q_qmltc_localList.append(std::addressof(q_qmltc_localList), toBeAppended);"_s; // append.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 is // resolved append.userVisible = true; current.functions.emplaceBack(append); } // generate count() sugar for users { QmltcMethod count{}; count.comments.emplaceBack(u"\\brief Number of elements in %1."_s.arg(data.read)); count.returnType = u"int"_s; count.name = u"%1Count"_s.arg(data.read); count.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName); count.body << u"int result = q_qmltc_localList.count(std::addressof(q_qmltc_localList));"_s; count.body << u"return result;"_s; count.userVisible = true; current.functions.emplaceBack(count); } // generate at() sugar for users { QmltcMethod at{}; at.comments.emplaceBack(u"\\brief Access an element in %1."_s.arg(data.read)); at.returnType = valueType; at.name = u"%1At"_s.arg(data.read); at.parameterList.emplaceBack(u"qsizetype"_s, u"position"_s, QString()); at.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName); at.body << u"auto result = q_qmltc_localList.at(std::addressof(q_qmltc_localList), position);"_s; at.body << u"return result;"_s; at.userVisible = true; current.functions.emplaceBack(at); } // generate clear() sugar for users { QmltcMethod clear{}; clear.comments.emplaceBack(u"\\brief Clear %1."_s.arg(data.read)); clear.returnType = u"void"_s; clear.name = u"%1Clear"_s.arg(data.read); clear.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName); clear.body << u"q_qmltc_localList.clear(std::addressof(q_qmltc_localList));"_s; // clear.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 is // resolved clear.userVisible = true; current.functions.emplaceBack(clear); } // generate replace() sugar for users { QmltcMethod replace{}; replace.comments.emplaceBack(u"\\brief Replace an element in %1."_s.arg(data.read)); replace.comments << ownershipWarning; replace.returnType = u"void"_s; replace.name = u"%1Replace"_s.arg(data.read); replace.parameterList.emplaceBack(u"qsizetype"_s, u"position"_s, QString()); replace.parameterList.emplaceBack(valueType, u"element"_s, QString()); replace.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName); replace.body << u"q_qmltc_localList.replace(std::addressof(q_qmltc_localList), position, element);"_s; // replace.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 // is resolved replace.userVisible = true; current.functions.emplaceBack(replace); } // generate removeLast() sugar for users { QmltcMethod removeLast{}; removeLast.comments.emplaceBack(u"\\brief Remove the last element in %1."_s.arg(data.read)); removeLast.returnType = u"void"_s; removeLast.name = u"%1RemoveLast"_s.arg(data.read); removeLast.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName); removeLast.body << u"q_qmltc_localList.removeLast(std::addressof(q_qmltc_localList));"_s; // removeLast.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when // QTBUG-106587 is resolved removeLast.userVisible = true; current.functions.emplaceBack(removeLast); } } void QmltcCompiler::compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p, const QQmlJSScope::ConstPtr &owner) { Q_ASSERT(!p.isAlias()); // will be handled separately Q_ASSERT(p.type()); const QString name = p.propertyName(); const QString variableName = u"m_" + name; const QString underlyingType = getUnderlyingType(p); if (qIsReferenceTypeList(p)) { const QString storageName = variableName + u"_storage"; current.variables.emplaceBack( u"QList<" + p.type()->valueType()->internalName() + u"*>", storageName, QString()); current.baselineCtor.initializerList.emplaceBack(variableName + u"(" + underlyingType + u"(this, std::addressof(" + storageName + u")))"); compileExtraListMethods(current, p); } // along with property, also add relevant moc code, so that we can use the // property in Qt/QML contexts QStringList mocPieces; mocPieces.reserve(10); mocPieces << underlyingType << name; QmltcPropertyData compilationData(p); // 1. add setter and getter // If p.isList(), it's a QQmlListProperty. Then you can write the underlying list through // the QQmlListProperty object retrieved with the getter. Setting it would make no sense. QmltcMethod getter{}; getter.returnType = underlyingType; getter.name = compilationData.read; getter.body << u"return " + variableName + u".value();"; getter.userVisible = true; current.functions.emplaceBack(getter); mocPieces << u"READ"_s << getter.name; if (p.isWritable() && !qIsReferenceTypeList(p)) { QmltcMethod setter {}; setter.returnType = u"void"_s; setter.name = compilationData.write; // QmltcVariable setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(underlyingType), name + u"_", u""_s); setter.body << variableName + u".setValue(" + name + u"_);"; setter.body << u"Q_EMIT " + compilationData.notify + u"();"; setter.userVisible = true; current.functions.emplaceBack(setter); mocPieces << u"WRITE"_s << setter.name; } // 2. add bindable if (!qIsReferenceTypeList(p)) { QmltcMethod bindable {}; bindable.returnType = u"QBindable<" + underlyingType + u">"; bindable.name = compilationData.bindable; bindable.body << u"return QBindable<" + underlyingType + u">(std::addressof(" + variableName + u"));"; bindable.userVisible = true; current.functions.emplaceBack(bindable); mocPieces << u"BINDABLE"_s << bindable.name; } // 3. add/check notify (actually, this is already done inside QmltcVisitor) if (owner->isPropertyRequired(name)) mocPieces << u"REQUIRED"_s; // 4. add moc entry // e.g. Q_PROPERTY(QString p READ getP WRITE setP BINDABLE bindableP) current.mocCode << u"Q_PROPERTY(" + mocPieces.join(u" "_s) + u")"; // 5. add extra moc entry if this property is marked default if (name == owner->defaultPropertyName()) current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(name); // structure: (C++ type name, name, C++ class name, C++ signal name) current.properties.emplaceBack(underlyingType, variableName, current.cppType, compilationData.notify); } /*! * \internal * * Models one step of the alias resolution. If the current alias to be resolved * points to \c {x.y.z} and that \c {x.y} is already resolved, then this struct * contains the information on how to obtain the \c {z} part from \c {x.y}. */ struct AliasResolutionFrame { /*! * \internal * * Placeholder for the current resolved state. It is replaced later with * the result from previous resolutions from the \c QStack. * * \sa unpackFrames() */ static QString inVar; /*! * \internal * * Steps to access this value as a list of C++ statements, to be used in * conjunction with \c {epilogue}. */ QStringList prologue; /*! * \internal * * Steps to finish the statements of the \c prologue (e.g. closing brackets). */ QStringList epilogue; /*! * \internal * * Instructions on how to write the property, after it was loaded with the * instructions from \c prologue. Has to happen before \c epilogue. */ QStringList epilogueForWrite; /*! * \internal * * Name of the variable holding the result of this resolution step, to be * used in the following resolution steps. */ QString outVar; }; // special string replaced by outVar of the previous frame QString AliasResolutionFrame::inVar = QStringLiteral("__QMLTC_ALIAS_FRAME_INPUT_VAR__"); /*! * \internal * * Process the frames by replacing the placeholder \c invar * used in \c epilogueForWrite and \c prologue with the result * obtained from the previous frame. */ static void unpackFrames(QStack &frames) { if (frames.size() < 2) return; // assume first frame is fine auto prev = frames.begin(); for (auto it = std::next(prev); it != frames.end(); ++it, ++prev) { for (QString &line : it->prologue) line.replace(AliasResolutionFrame::inVar, prev->outVar); for (QString &line : it->epilogueForWrite) line.replace(AliasResolutionFrame::inVar, prev->outVar); it->outVar.replace(AliasResolutionFrame::inVar, prev->outVar); } } template static QStringList joinFrames(const QStack &frames, Projection project) { QStringList joined; for (const AliasResolutionFrame &frame : frames) joined += project(frame); return joined; } void QmltcCompiler::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &alias, const QQmlJSScope::ConstPtr &owner) { const QString aliasName = alias.propertyName(); Q_ASSERT(!aliasName.isEmpty()); QStringList aliasExprBits = alias.aliasExpression().split(u'.'); Q_ASSERT(!aliasExprBits.isEmpty()); QStack frames; QQmlJSUtils::AliasResolutionVisitor aliasVisitor; qsizetype i = 0; aliasVisitor.reset = [&]() { frames.clear(); i = 0; // we use it in property processing // first frame is a dummy one: frames.push( AliasResolutionFrame { QStringList(), QStringList(), QStringList(), u"this"_s }); }; aliasVisitor.processResolvedId = [&](const QQmlJSScope::ConstPtr &type) { Q_ASSERT(type); if (owner != type) { // cannot start at `this`, need to fetch object through context const int id = m_visitor->runtimeId(type); Q_ASSERT(id >= 0); // since the type is found by id, it must have an id AliasResolutionFrame queryIdFrame {}; Q_ASSERT(frames.top().outVar == u"this"_s); // so inVar would be "this" as well queryIdFrame.prologue << u"auto context = %1::q_qmltc_thisContext;"_s.arg( owner->internalName()); // doing the above allows us to lookup id object by index (fast) queryIdFrame.outVar = u"alias_objectById_" + aliasExprBits.front(); // unique enough const QString cppType = (m_visitor->qmlComponentIndex(type) == -1) ? type->internalName() : u"QQmlComponent"_s; queryIdFrame.prologue << u"auto " + queryIdFrame.outVar + u" = static_cast<" + cppType + u"*>(context->idValue(" + QString::number(id) + u"));"; queryIdFrame.prologue << u"Q_ASSERT(" + queryIdFrame.outVar + u");"; frames.push(queryIdFrame); } }; aliasVisitor.processResolvedProperty = [&](const QQmlJSMetaProperty &p, const QQmlJSScope::ConstPtr &owner) { AliasResolutionFrame queryPropertyFrame {}; auto [extensionPrologue, extensionAccessor, extensionEpilogue] = QmltcCodeGenerator::wrap_extensionType( owner, p, QmltcCodeGenerator::wrap_privateClass(AliasResolutionFrame::inVar, p)); QString inVar = extensionAccessor; queryPropertyFrame.prologue += extensionPrologue; if (p.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Value) { // we need to read the property to a local variable and then // write the updated value once the actual operation is done const QString aliasVar = u"alias_" + QString::number(i); // should be fairly unique ++i; queryPropertyFrame.prologue << u"auto " + aliasVar + u" = " + inVar + u"->" + p.read() + u"();"; queryPropertyFrame.epilogueForWrite << inVar + u"->" + p.write() + u"(" + aliasVar + u");"; // NB: since accessor becomes a value type, wrap it into an // addressof operator so that we could access it as a pointer inVar = QmltcCodeGenerator::wrap_addressof(aliasVar); // reset } else { inVar += u"->" + p.read() + u"()"; // update } queryPropertyFrame.outVar = inVar; queryPropertyFrame.epilogue += extensionEpilogue; frames.push(queryPropertyFrame); }; QQmlJSUtils::ResolvedAlias result = QQmlJSUtils::resolveAlias(m_typeResolver, alias, owner, aliasVisitor); Q_ASSERT(result.kind != QQmlJSUtils::AliasTarget_Invalid); unpackFrames(frames); if (result.kind == QQmlJSUtils::AliasTarget_Property) { // we don't need the last frame here frames.pop(); // instead, add a custom frame AliasResolutionFrame customFinalFrame {}; auto [extensionPrologue, extensionAccessor, extensionEpilogue] = QmltcCodeGenerator::wrap_extensionType( result.owner, result.property, QmltcCodeGenerator::wrap_privateClass(frames.top().outVar, result.property)); customFinalFrame.prologue = extensionPrologue; customFinalFrame.outVar = extensionAccessor; customFinalFrame.epilogue = extensionEpilogue; frames.push(customFinalFrame); } const QString latestAccessor = frames.top().outVar; const QStringList prologue = joinFrames(frames, [](const AliasResolutionFrame &frame) { return frame.prologue; }); const QStringList epilogue = joinFrames(frames, [](const AliasResolutionFrame &frame) { return frame.epilogue; }); const QString underlyingType = (result.kind == QQmlJSUtils::AliasTarget_Property) ? getUnderlyingType(result.property) : result.owner->internalName() + u" *"; QStringList mocLines; mocLines.reserve(10); mocLines << underlyingType << aliasName; QmltcPropertyData compilationData(aliasName); // 1. add setter and getter QmltcMethod getter {}; getter.returnType = underlyingType; getter.name = compilationData.read; getter.body += prologue; if (result.kind == QQmlJSUtils::AliasTarget_Property) { if (QString read = result.property.read(); !read.isEmpty() && !QQmlJSUtils::bindablePropertyHasDefaultAccessor( result.property, QQmlJSUtils::PropertyAccessor_Read)) { getter.body << u"return %1->%2();"_s.arg(latestAccessor, read); } else { // use QObject::property() as a fallback when read method is unknown getter.body << u"return qvariant_cast<%1>(%2->property(\"%3\"));"_s.arg( underlyingType, latestAccessor, result.property.propertyName()); } } else { // AliasTarget_Object getter.body << u"return " + latestAccessor + u";"; } getter.body += epilogue; getter.userVisible = true; current.functions.emplaceBack(getter); mocLines << u"READ"_s << getter.name; if (result.property.isWritable()) { Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise QmltcMethod setter {}; setter.returnType = u"void"_s; setter.name = compilationData.write; const QString setName = result.property.write(); QList methods = result.owner->methods(setName); if (methods.isEmpty()) { // when we are compiling the property as well // QmltcVariable setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(underlyingType), aliasName + u"_", u""_s); } else { setter.parameterList = compileMethodParameters(methods.at(0).parameters(), /* allow unnamed = */ true); } setter.body += prologue; QStringList parameterNames; parameterNames.reserve(setter.parameterList.size()); std::transform(setter.parameterList.cbegin(), setter.parameterList.cend(), std::back_inserter(parameterNames), [](const QmltcVariable &x) { return x.name; }); QString commaSeparatedParameterNames = parameterNames.join(u", "_s); if (!setName.isEmpty() && !QQmlJSUtils::bindablePropertyHasDefaultAccessor( result.property, QQmlJSUtils::PropertyAccessor_Write)) { setter.body << u"%1->%2(%3);"_s.arg(latestAccessor, setName, commaSeparatedParameterNames); } else { // use QObject::setProperty() as fallback when write method is unknown Q_ASSERT(parameterNames.size() == 1); const QString variantName = u"var_" + aliasName; // fairly unique setter.body << u"QVariant %1;"_s.arg(variantName); setter.body << u"%1.setValue(%2);"_s.arg(variantName, commaSeparatedParameterNames); setter.body << u"%1->setProperty(\"%2\", std::move(%3));"_s.arg( latestAccessor, result.property.propertyName(), variantName); } setter.body += joinFrames( frames, [](const AliasResolutionFrame &frame) { return frame.epilogueForWrite; }); setter.body += epilogue; // NB: *after* epilogueForWrite - see prologue construction setter.userVisible = true; current.functions.emplaceBack(setter); mocLines << u"WRITE"_s << setter.name; } // 2. add bindable if (QString bindableName = result.property.bindable(); !bindableName.isEmpty()) { Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise QmltcMethod bindable {}; bindable.returnType = u"QBindable<" + underlyingType + u">"; bindable.name = compilationData.bindable; bindable.body += prologue; bindable.body << u"return " + latestAccessor + u"->" + bindableName + u"()" + u";"; bindable.body += epilogue; bindable.userVisible = true; current.functions.emplaceBack(bindable); mocLines << u"BINDABLE"_s << bindable.name; } // 3. add notify - which is pretty special // step 1: generate the moc instructions // mimic the engines behavior: do it even if the notify will never be emitted if (const QString aliasNotifyName = alias.notify(); !aliasNotifyName.isEmpty()) { Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise mocLines << u"NOTIFY"_s << aliasNotifyName; } // step 2: connect the notifier to the aliased property notifier, if this latter exists // otherwise, mimic the engines behavior and generate a useless notify if (const QString notifyName = result.property.notify(); !notifyName.isEmpty()) { auto notifyFrames = frames; notifyFrames.pop(); // we don't need the last frame at all in this case const QStringList notifyPrologue = joinFrames( frames, [](const AliasResolutionFrame &frame) { return frame.prologue; }); const QStringList notifyEpilogue = joinFrames( frames, [](const AliasResolutionFrame &frame) { return frame.epilogue; }); // notify is very special current.endInit.body << u"{ // alias notify connection:"_s; current.endInit.body += notifyPrologue; // TODO: use non-private accessor since signals must exist on the public // type, not on the private one -- otherwise, you can't connect to a // private property signal in C++ and so it is useless (hence, use // public type) const QString cppType = (m_visitor->qmlComponentIndex(result.owner) == -1) ? result.owner->internalName() : u"QQmlComponent"_s; const QString latestAccessorNonPrivate = notifyFrames.top().outVar; current.endInit.body << u"QObject::connect(" + latestAccessorNonPrivate + u", &" + cppType + u"::" + notifyName + u", this, &" + current.cppType + u"::" + compilationData.notify + u");"; current.endInit.body += notifyEpilogue; current.endInit.body << u"}"_s; } if (QString resetName = result.property.reset(); !resetName.isEmpty()) { Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise QmltcMethod reset {}; reset.returnType = u"void"_s; reset.name = compilationData.reset; reset.body += prologue; reset.body << latestAccessor + u"->" + resetName + u"()" + u";"; reset.body += epilogue; reset.userVisible = true; current.functions.emplaceBack(reset); mocLines << u"RESET"_s << reset.name; } // mimic the engines behavior: aliases are never constants // mocLines << u"CONSTANT"_s; // mimic the engines behavior: aliases are never stored mocLines << u"STORED"_s << u"false"_s; // mimic the engines behavior: aliases are never designable mocLines << u"DESIGNABLE"_s << u"false"_s; // 4. add moc entry // Q_PROPERTY(QString text READ text WRITE setText BINDABLE bindableText NOTIFY textChanged) current.mocCode << u"Q_PROPERTY(" + mocLines.join(u" "_s) + u")"; // 5. add extra moc entry if this alias is default one if (aliasName == owner->defaultPropertyName()) { // Q_CLASSINFO("DefaultProperty", propertyName) current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(aliasName); } } static QString generate_callCompilationUnit(const QString &urlMethodName) { return u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(%1())"_s.arg(urlMethodName); } static std::pair getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope, const QString &propertyName); /*! * \internal * Helper method used to keep compileBindingByType() readable. */ void QmltcCompiler::compileObjectBinding(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Object); const QString &propertyName = binding.propertyName(); const QQmlJSMetaProperty property = type->property(propertyName); QQmlJSScope::ConstPtr propertyType = property.type(); // 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"Binding on property '" + propertyName + u"' of unknown type"); return; } const auto addObjectBinding = [&](const QString &value) { if (qIsReferenceTypeList(property)) { Q_ASSERT(unprocessedListProperty == property || unprocessedListBindings.empty()); unprocessedListBindings.append(value); unprocessedListProperty = property; } else { QmltcCodeGenerator::generate_assignToProperty(¤t.endInit.body, type, property, value, accessor.name, true); } }; // special case of implicit or explicit component: if (qsizetype index = m_visitor->qmlComponentIndex(object); index >= 0) { const QString objectName = newSymbol(u"sc"_s); const qsizetype creationIndex = m_visitor->creationIndex(object); QStringList *block = (creationIndex == -1) ? ¤t.endInit.body : ¤t.init.body; *block << u"{"_s; *block << QStringLiteral("auto thisContext = QQmlData::get(%1)->outerContext;") .arg(qobjectParent); *block << QStringLiteral("auto %1 = QQmlObjectCreator::createComponent(engine, " "%2, %3, %4, thisContext);") .arg(objectName, generate_callCompilationUnit(m_urlMethodName), QString::number(index), qobjectParent); *block << 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 (creationIndex != -1) { // explicit component Q_ASSERT(object->isComposite()); Q_ASSERT(object->baseType()->internalName() == u"QQmlComponent"_s); if (int id = m_visitor->runtimeId(object); id >= 0) { QString idString = m_visitor->addressableScopes().id(object, object); if (idString.isEmpty()) idString = u""_s; QmltcCodeGenerator::generate_setIdValue(block, u"thisContext"_s, id, objectName, idString); } const QString creationIndexStr = QString::number(creationIndex); *block << QStringLiteral("creator->set(%1, %2);").arg(creationIndexStr, objectName); Q_ASSERT(block == ¤t.init.body); current.endInit.body << QStringLiteral("auto %1 = creator->get<%2>(%3);") .arg(objectName, u"QQmlComponent"_s, creationIndexStr); } addObjectBinding(objectName); *block << u"}"_s; return; } 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); } /*! * \internal * Helper method used to keep compileBindingByType() readable. */ void QmltcCompiler::compileValueSourceOrInterceptorBinding(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::ValueSource || binding.bindingType() == QQmlSA::BindingType::Interceptor); const QString &propertyName = binding.propertyName(); const QQmlJSMetaProperty property = type->property(propertyName); QQmlJSScope::ConstPtr propertyType = property.type(); // NB: object is compiled with compileType(), here just need to use it QSharedPointer object; if (binding.bindingType() == QQmlSA::BindingType::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; } /*! * \internal * Helper method used to keep compileBindingByType() readable. */ void QmltcCompiler::compileAttachedPropertyBinding(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::AttachedProperty); const QString &propertyName = binding.propertyName(); const QQmlJSMetaProperty property = type->property(propertyName); QQmlJSScope::ConstPtr propertyType = property.type(); Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact const auto attachedType = binding.attachedType(); Q_ASSERT(attachedType); 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 = uniqueVariableName(attachingTypeName); // add attached type as a member variable to allow noop lookup current.variables.emplaceBack(attachedTypeName + u" *", attachedMemberName, u"nullptr"_s); if (propertyName == u"Component"_s) { // Component attached type is special current.endInit.body << u"Q_ASSERT(qmlEngine(this));"_s; current.endInit.body << u"// attached Component must be added to the object's QQmlData"_s; current.endInit.body << u"Q_ASSERT(!QQmlEnginePrivate::get(qmlEngine(this))->activeObjectCreator);"_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";"; if (propertyName == u"Component"_s) { // call completed/destruction signals appropriately current.handleOnCompleted.body << u"Q_EMIT " + attachedMemberName + u"->completed();"; if (!current.dtor) { current.dtor = QmltcDtor{}; current.dtor->name = u"~" + current.cppType; } current.dtor->body << u"Q_EMIT " + attachedMemberName + u"->destruction();"; } } auto subbindings = attachedType->ownPropertyBindingsInQmlIROrder(); // compile bindings of the attached property partitionBindings(subbindings.begin(), subbindings.end()); compileBinding(current, subbindings.begin(), subbindings.end(), attachedType, { type, attachedMemberName, propertyName, false }); } /*! * \internal * Helper method used to keep compileBindingByType() readable. */ void QmltcCompiler::compileGroupPropertyBinding(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::GroupProperty); const QString &propertyName = binding.propertyName(); const QQmlJSMetaProperty property = type->property(propertyName); QQmlJSScope::ConstPtr propertyType = property.type(); Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact if (property.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, property) + u"->" + property.read() + u"()"; // NB: used when isValueType == true const QString groupPropertyVarName = accessor.name + u"_group_" + propertyName; // value types are special if (generateValueTypeCode) { if (property.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 auto &bStart, const auto &bEnd) { compileBinding(current, bStart, bEnd, groupType, { type, groupAccessor, propertyName, isValueType }); }; auto it = subbindings.begin(); Q_ASSERT(std::all_of(it, firstScript, [](const auto &x) { return x.bindingType() != QQmlSA::BindingType::Script; })); compile(it, firstScript); it = firstScript; // 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, property) + u"->" + property.write() + u"(" + groupPropertyVarName + u");"; } // once the value is written back, process the script bindings Q_ASSERT(std::all_of(it, subbindings.end(), [](const auto &x) { return x.bindingType() == QQmlSA::BindingType::Script; })); compile(it, subbindings.end()); } /*! * \internal * Helper method used to keep compileBindingByType() readable. */ void QmltcCompiler::compileTranslationBinding(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Translation || binding.bindingType() == QQmlSA::BindingType::TranslationById); const QString &propertyName = binding.propertyName(); auto [property, absoluteIndex] = getMetaPropertyIndex(type, 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 != type); 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::TranslationBindingInfo info; info.unitVarName = generate_callCompilationUnit(m_urlMethodName); info.scope = u"this"_s; info.target = u"this"_s; info.propertyIndex = absoluteIndex; info.property = property; info.data = binding.translationDataValue(m_url); info.valueTypeIndex = valueTypeIndex; info.line = binding.sourceLocation().startLine; info.column = binding.sourceLocation().startColumn; QmltcCodeGenerator::generate_createTranslationBindingOnProperty(¤t.endInit.body, info); } void QmltcCompiler::processLastListBindings(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { if (unprocessedListBindings.empty()) return; QmltcCodeGenerator::generate_assignToListProperty( ¤t.endInit.body, type, unprocessedListProperty, unprocessedListBindings, accessor.name, m_uniques[UniqueStringId(current, unprocessedListProperty.propertyName())] .qmlListVariableName); unprocessedListBindings.clear(); } void QmltcCompiler::compileBinding(QmltcType ¤t, QList::iterator bindingStart, QList::iterator bindingEnd, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { for (auto it = bindingStart; it != bindingEnd; it++) { const QQmlJSMetaPropertyBinding &binding = *it; const QString &propertyName = binding.propertyName(); Q_ASSERT(!propertyName.isEmpty()); // 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(); // make sure group property is not generalized by checking if type really has a property // called propertyName. If not, it is probably an id. if (binding.bindingType() == QQmlSA::BindingType::GroupProperty && type->hasProperty(propertyName)) { 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)); continue; } } const QQmlJSMetaProperty metaProperty = type->property(propertyName); const QQmlJSScope::ConstPtr propertyType = metaProperty.type(); if (!(qIsReferenceTypeList(metaProperty) && unprocessedListProperty == metaProperty)) { processLastListBindings(current, type, accessor); } compileBindingByType(current, binding, type, accessor); } processLastListBindings(current, type, accessor); } void QmltcCompiler::compileBindingByType(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { const QString &propertyName = binding.propertyName(); const QQmlJSMetaProperty metaProperty = type->property(propertyName); const QQmlJSScope::ConstPtr propertyType = metaProperty.type(); const auto assignToProperty = [&](const QQmlJSMetaProperty &p, const QString &value, bool constructFromQObject = false) { QmltcCodeGenerator::generate_assignToProperty(¤t.endInit.body, type, p, value, accessor.name, constructFromQObject); }; switch (binding.bindingType()) { case QQmlSA::BindingType::BoolLiteral: { const bool value = binding.boolValue(); assignToProperty(metaProperty, value ? u"true"_s : u"false"_s); break; } case QQmlSA::BindingType::NumberLiteral: { assignToProperty(metaProperty, QString::number(binding.numberValue())); break; } case QQmlSA::BindingType::StringLiteral: { QString value = QQmlJSUtils::toLiteral(binding.stringValue()); if (auto type = metaProperty.type()) { if (type->internalName() == u"QUrl"_s) { value = u"QUrl(%1)"_s.arg(value); } } assignToProperty(metaProperty, value); break; } case QQmlSA::BindingType::RegExpLiteral: { const QString value = u"QRegularExpression(%1)"_s.arg(QQmlJSUtils::toLiteral(binding.regExpValue())); assignToProperty(metaProperty, value); break; } case QQmlSA::BindingType::Null: { // poor check: null bindings are only supported for var and objects Q_ASSERT(propertyType->isSameType(m_typeResolver->varType()) || propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference); if (propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) assignToProperty(metaProperty, u"nullptr"_s); else assignToProperty(metaProperty, u"QVariant::fromValue(nullptr)"_s); break; } case QQmlSA::BindingType::Script: { QString bindingSymbolName = uniqueVariableName(type->internalName() + u'_' + propertyName + u"_binding"); compileScriptBinding(current, binding, bindingSymbolName, type, propertyName, propertyType, accessor); break; } case QQmlSA::BindingType::Object: { compileObjectBinding(current, binding, type, accessor); break; } case QQmlSA::BindingType::Interceptor: Q_FALLTHROUGH(); case QQmlSA::BindingType::ValueSource: { compileValueSourceOrInterceptorBinding(current, binding, type, accessor); break; } case QQmlSA::BindingType::AttachedProperty: { compileAttachedPropertyBinding(current, binding, type, accessor); break; } case QQmlSA::BindingType::GroupProperty: { compileGroupPropertyBinding(current, binding, type, accessor); break; } case QQmlSA::BindingType::TranslationById: case QQmlSA::BindingType::Translation: { compileTranslationBinding(current, binding, type, accessor); break; } case QQmlSA::BindingType::Invalid: { recordError(binding.sourceLocation(), u"This binding is invalid"_s); break; } default: { 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 getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope, const QString &propertyName) { auto owner = QQmlJSScope::ownerOfProperty(scope, propertyName).scope; Q_ASSERT(owner); const QQmlJSMetaProperty 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 }; const auto increment = [&](const QQmlJSScope::ConstPtr &type, QQmlJSScope::ExtensionKind m) { // owner of property is not included in the offset calculation (relative // index is already added as p.index()) if (type->isSameType(owner)) return; // extension namespace and JavaScript properties are ignored if (m == QQmlJSScope::ExtensionNamespace || m == QQmlJSScope::ExtensionJavaScript) return; index += int(type->ownProperties().size()); }; QQmlJSUtils::traverseFollowingMetaObjectHierarchy(scope, owner, increment); return { p, index }; } void QmltcCompiler::compileScriptBinding(QmltcType ¤t, 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, QQmlJSMetaMethodType::Signal); Q_ASSERT(!signalMethods.isEmpty()); // an error somewhere else QQmlJSMetaMethod signal = signalMethods.at(0); Q_ASSERT(signal.methodType() == QQmlJSMetaMethodType::Signal); const QString signalName = signal.methodName(); const QString slotName = newSymbol(signalName + u"_slot"); const QString signalReturnType = signal.returnType()->augmentedInternalName(); const QList slotParameters = compileMethodParameters(signal.parameters(), /* 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 = QQmlJSMetaMethodType::Slot; current.functions << std::move(slotMethod); current.setComplexBindings.body << u"QObject::connect(" + This_signal + u", " + u"&" + objectClassName_signal + u"::" + signalName + u", " + This_slot + u", &" + objectClassName_slot + u"::" + slotName + u");"; }; switch (binding.scriptKind()) { case QQmlSA::ScriptBindingKind::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( ¤t.setComplexBindings.body, generate_callCompilationUnit(m_urlMethodName), u"this"_s, // NB: always using enclosing object as a scope for the binding static_cast(objectType->ownRuntimeFunctionIndex(binding.scriptIndex())), bindingTarget, // binding target // value types are special and are bound through valueTypeIndex accessor.isValueType ? QQmlJSScope::ConstPtr() : objectType, absoluteIndex, property, valueTypeIndex, accessor.name); break; } case QQmlSA::ScriptBindingKind::SignalHandler: { const auto name = QQmlSignalNames::handlerNameToSignalName(propertyName); Q_ASSERT(name.has_value()); compileScriptSignal(*name); break; } case QQmlSA ::ScriptBindingKind::ChangeHandler: { const QString objectClassName = objectType->internalName(); const QString bindingFunctorName = newSymbol(bindingSymbolName + u"Functor"); const auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName); Q_ASSERT(signalName.has_value()); // an error somewhere else const auto actualProperty = QQmlJSUtils::propertyFromChangedHandler(objectType, propertyName); 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>"; current.children << compileScriptBindingPropertyChangeHandler( binding, objectType, m_urlMethodName, bindingFunctorName, objectClassName); current.setComplexBindings.body << u"if (!%1.contains(QStringLiteral(\"%2\")))"_s.arg( current.propertyInitializer.initializedCache.name, propertyName); // TODO: this could be dropped if QQmlEngine::setContextForObject() is // done before currently generated C++ object is constructed current.setComplexBindings.body << u" "_s + 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; } } QT_END_NAMESPACE