Revision control
++ // Copyright (C) 2021 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++ // Qt-Security score:critical reason:code-generation
++
++ #include "qqmltccompiler_p.h"
++
++ #include <private/qqmltcoutputir_p.h>
++ #include <private/qqmltccodewriter_p.h>
++ #include <private/qqmltcpropertyutils_p.h>
++ #include <private/qqmltccompilerpieces_p.h>
++
++ #include <QtCore/qloggingcategory.h>
++ #include <QtQml/private/qqmlsignalnames_p.h>
++ #include <private/qqmljsutils_p.h>
++
++ #include <algorithm>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ namespace QQmltc {
++
++ bool qIsReferenceTypeList(const QQmlJSMetaProperty &p)
++ {
++ if (QQmlJSScope::ConstPtr type = p.type())
++ return type->isListProperty();
++ return false;
++ }
++
++ static QList<QQmlJSMetaProperty> unboundRequiredProperties(
++ const QQmlJSScope::ConstPtr &type, TypeResolver *resolver)
++ {
++ QList<QQmlJSMetaProperty> requiredProperties{};
++
++ auto isPropertyRequired = [&type, &resolver](const auto &property) {
++ if (type->hasPropertyBindings(property.propertyName()))
++ return false;
++
++ if (type->isPropertyRequired(property.propertyName()))
++ return true;
++
++ // We consider top-level aliases to required properties as required.
++ // This is somewhat blunt, but it captures the most common use case.
++ // If you have a type with multiple aliases to the same required property,
++ // the type will be impossible to instantiate, but that's a very strange
++ // arrangement in the first place.
++ if (property.isAlias()) {
++ QQmlJSUtils::AliasResolutionVisitor aliasVisitor;
++
++ QQmlJSUtils::ResolvedAlias result =
++ QQmlJSUtils::resolveAlias(resolver, property, type, aliasVisitor);
++
++ if (result.kind != QQmlJSUtils::AliasTarget_Property)
++ return false;
++
++ if (!result.owner->isPropertyRequired(result.property.propertyName()))
++ return false;
++
++ if (result.owner == type)
++ return false;
++
++ if (result.owner->hasPropertyBindings(result.property.propertyName()))
++ return false;
++
++ return true;
++ }
++
++ return false;
++ };
++
++ 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(
++ Type ¤t, const QQmlJSScope::ConstPtr &type, TypeResolver *resolver)
++ {
++
++ QList<QQmlJSMetaProperty> 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()->elementType()->internalName())
++ : u"passByConstRefOrValue<%1>"_s.arg(
++ property.type()->augmentedInternalName());
++ return Variable{ type, property.propertyName() };
++ });
++ }
++
++ static void compileRootExternalConstructorBody(Type ¤t, 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(
++ PropertyData(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);
++ };
++
++ const QString CodeGenerator::privateEngineName = u"ePriv"_s;
++ const QString CodeGenerator::typeCountName = u"q_qmltc_typeCount"_s;
++
++ Compiler::Compiler(const QString &url, TypeResolver *resolver, Visitor *visitor,
++ QQmlJSLogger *logger)
++ : m_url(url), m_typeResolver(resolver), m_visitor(visitor), m_logger(logger)
++ {
++ Q_UNUSED(m_typeResolver);
++ Q_ASSERT(!hasErrors());
++ }
++
++ // needed due to std::unique_ptr<CodeGenerator> with CodeGenerator being
++ // incomplete type in the header (~std::unique_ptr<> fails with a static_assert)
++ Compiler::~Compiler() = default;
++
++ QString Compiler::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 Compiler::compile(const CompilerInfo &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;
++ };
++
++ CodeGenerator generator { m_url, m_visitor };
++
++ Method 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<InlineComponentNameType>(&a);
++ const auto *inlineComponentBName = std::get_if<InlineComponentNameType>(&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<Type> compiledTypes;
++ for (const auto &inlineComponent : sortedInlineComponentNames) {
++ const QList<QQmlJSScope::ConstPtr> &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 = [&](Type ¤t, const QQmlJSScope::ConstPtr &type) {
++ generator.generate_initCodeForTopLevelComponent(current, type);
++ };
++ compileType(compiledTypes.back(), root, compile);
++ } else {
++ const auto compile = [this](Type ¤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;
++
++ Program 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;
++
++ Output out;
++ OutputWrapper code(out);
++ CodeWriter::write(code, program);
++ }
++
++ void Compiler::compileUrlMethod(Method &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 Compiler::compileType(
++ Type ¤t, const QQmlJSScope::ConstPtr &type,
++ std::function<void(Type &, const QQmlJSScope::ConstPtr &)> 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();
++
++ CodeGenerator 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<DocumentRoot> 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)
++ Method typeCountMethod;
++ typeCountMethod.name = CodeGenerator::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 = {
++ Variable(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<QString>"_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<QQmlContextData>"_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;
++ const Variable creator(u"QQmltcObjectCreationHelper*"_s, u"creator"_s);
++ const Variable engine(u"QQmlEngine*"_s, u"engine"_s);
++ const Variable parent(u"QObject*"_s, u"parent"_s, u"nullptr"_s);
++ const Variable initializedCache(
++ u"[[maybe_unused]] const QSet<QString>&"_s,
++ u"initializedCache"_s,
++ u"{}"_s
++ );
++ const Variable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_s, u"parentContext"_s);
++ const Variable 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 Variable initializer(
++ u"[[maybe_unused]] qxp::function_ref<void(%1&)>"_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) {
++ const Variable 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);
++ Variable 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<typename Iterator>
++ 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 !Compiler::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.
++ void Compiler::compilePropertyInitializer(Type ¤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;
++
++ const auto properties = type->properties().values();
++ for (const auto &property: properties) {
++ if (property.index() == -1) continue;
++ if (property.isPrivate()) continue;
++ if (!property.isWritable() && !qIsReferenceTypeList(property)) continue;
++
++ const QString name = property.propertyName();
++ const auto propertyType = property.type();
++ if (propertyType.isNull() || propertyType->isOpaqueType()) {
++ recordError(type->sourceLocation(), u"Type of property '%1' is unknown"_s.arg(name));
++ continue;
++ }
++
++ current.propertyInitializer.propertySetters.emplace_back();
++ auto& compiledSetter = current.propertyInitializer.propertySetters.back();
++
++ compiledSetter.userVisible = true;
++ compiledSetter.returnType = u"void"_s;
++ compiledSetter.name = PropertyData(property).write;
++
++ if (qIsReferenceTypeList(property)) {
++ compiledSetter.parameterList.emplaceBack(
++ QQmlJSUtils::constRefify(
++ u"QList<%1*>"_s.arg(propertyType->elementType()->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, PropertyData(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 Compiler::compileTypeElements(Type ¤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 Compiler::compileEnum(Type ¤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<Variable>
++ compileMethodParameters(const QList<QQmlJSMetaParameter> ¶meterInfos, bool allowUnnamed = false)
++ {
++ QList<Variable> 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 Compiler::compileMethod(Type ¤t, const QQmlJSMetaMethod &m,
++ const QQmlJSScope::ConstPtr &owner)
++ {
++ const QString returnType = m.returnType()->augmentedInternalName();
++
++ const QList<Variable> compiledParams = compileMethodParameters(m.parameters());
++ const auto methodType = m.methodType();
++
++ QStringList code;
++ if (methodType != QQmlJSMetaMethodType::Signal) {
++ CodeGenerator urlGenerator { m_url, m_visitor };
++ CodeGenerator::generate_callExecuteRuntimeFunction(
++ &code, urlGenerator.urlMethodName() + u"()",
++ owner->ownRuntimeFunctionIndex(m.compiledFunctionIndex()), u"this"_s, returnType,
++ compiledParams);
++ }
++
++ Method 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 Compiler::compileExtraListMethods(Type ¤t, const QQmlJSMetaProperty &p)
++ {
++ PropertyData data(p);
++ const QString elementType = p.type()->elementType()->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
++ {
++ Method 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(elementType, 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
++ {
++ Method 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
++ {
++ Method at{};
++ at.comments.emplaceBack(u"\\brief Access an element in %1."_s.arg(data.read));
++ at.returnType = elementType;
++ 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
++ {
++ Method 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
++ {
++ Method 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(elementType, 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
++ {
++ Method 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 Compiler::compileProperty(Type ¤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()->elementType()->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;
++
++ PropertyData 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.
++ Method 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)) {
++ Method 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)) {
++ Method 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<AliasResolutionFrame>.
++ *
++ * \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<AliasResolutionFrame> &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<typename Projection>
++ static QStringList joinFrames(const QStack<AliasResolutionFrame> &frames, Projection project)
++ {
++ QStringList joined;
++ for (const AliasResolutionFrame &frame : frames)
++ joined += project(frame);
++ return joined;
++ }
++
++ void Compiler::compileAlias(Type ¤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<AliasResolutionFrame> 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] =
++ CodeGenerator::wrap_extensionType(
++ owner, p, CodeGenerator::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 = CodeGenerator::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] =
++ CodeGenerator::wrap_extensionType(
++ result.owner, result.property,
++ CodeGenerator::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;
++
++ PropertyData compilationData(aliasName);
++ // 1. add setter and getter
++ Method 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
++ Method setter {};
++ setter.returnType = u"void"_s;
++ setter.name = compilationData.write;
++
++ const QString setName = result.property.write();
++ QList<QQmlJSMetaMethod> 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 Variable &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
++ Method 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
++ Method 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<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope,
++ const QString &propertyName);
++
++ /*!
++ * \internal
++ * Helper method used to keep compileBindingByType() readable.
++ */
++ void Compiler::compileObjectBinding(Type ¤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 {
++ CodeGenerator::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"<unknown>"_s;
++ CodeGenerator::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 Compiler::compileValueSourceOrInterceptorBinding(Type ¤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<const QQmlJSScope> 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 Compiler::compileAttachedPropertyBinding(Type ¤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 = Dtor{};
++ 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 Compiler::compileGroupedPropertyBinding(Type ¤t,
++ const QQmlJSMetaPropertyBinding &binding,
++ const QQmlJSScope::ConstPtr &type,
++ const BindingAccessorData &accessor)
++ {
++ Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::GroupedProperty);
++
++ 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 grouped 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"Grouped 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 = CodeGenerator::wrap_privateClass(accessor.name, property) + u"->"
++ + property.read() + u"()";
++ // NB: used when isValueType == true
++ const QString groupedPropertyVarName = accessor.name + u"_group_" + propertyName;
++ // value types are special
++ if (generateValueTypeCode) {
++ if (property.write().isEmpty()) { // just reject this
++ recordError(binding.sourceLocation(),
++ u"Grouped property '" + propertyName + u"' is a value type without a setter");
++ return;
++ }
++
++ current.endInit.body << u"auto " + groupedPropertyVarName + u" = " + groupAccessor + u";";
++ // addressof operator is to make the binding logic work, which
++ // expects that `accessor.name` is a pointer type
++ groupAccessor = CodeGenerator::wrap_addressof(groupedPropertyVarName);
++ }
++
++ // 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 grouped 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 valueTypeGroupedProperty = getCopy();
++ // installBinding(valueTypeGroupedProperty, "subproperty1"); // changes subproperty1 value
++ // setCopy(valueTypeGroupedProperty); // oops, subproperty1 value changed to old again
++ if (generateValueTypeCode) { // write the value type back
++ current.endInit.body << CodeGenerator::wrap_privateClass(accessor.name, property)
++ + u"->" + property.write() + u"(" + groupedPropertyVarName + 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 Compiler::compileTranslationBinding(Type ¤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 [groupedProperty, groupedPropertyIndex] =
++ getMetaPropertyIndex(accessor.scope, accessor.propertyName);
++ if (groupedPropertyIndex < 0) {
++ recordError(binding.sourceLocation(),
++ u"Binding on grouped property '" + accessor.propertyName
++ + u"' of unknown type");
++ return;
++ }
++ valueTypeIndex = absoluteIndex;
++ absoluteIndex = groupedPropertyIndex; // e.g. index of accessor.name
++ }
++
++ CodeGenerator::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;
++
++ CodeGenerator::generate_createTranslationBindingOnProperty(¤t.endInit.body, info);
++ }
++
++ void Compiler::processLastListBindings(Type ¤t, const QQmlJSScope::ConstPtr &type,
++ const BindingAccessorData &accessor)
++ {
++ if (unprocessedListBindings.empty())
++ return;
++
++ CodeGenerator::generate_assignToListProperty(
++ ¤t.endInit.body, type, unprocessedListProperty, unprocessedListBindings,
++ accessor.name,
++ m_uniques[UniqueStringId(current, unprocessedListProperty.propertyName())]
++ .qmlListVariableName);
++
++ unprocessedListBindings.clear();
++ }
++
++ void Compiler::compileBinding(Type ¤t,
++ QList<QQmlJSMetaPropertyBinding>::iterator bindingStart,
++ QList<QQmlJSMetaPropertyBinding>::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 grouped 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::GroupedProperty
++ && type->hasProperty(propertyName)) {
++ qCWarning(lcQmltcCompiler)
++ << QStringLiteral("Binding at line %1 column %2 is not deferred as it is a "
++ "binding on a grouped 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 Compiler::compileBindingByType(Type ¤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) {
++ CodeGenerator::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::GroupedProperty: {
++ compileGroupedPropertyBinding(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 Type compileScriptBindingPropertyChangeHandler(const QQmlJSMetaPropertyBinding &binding,
++ const QQmlJSScope::ConstPtr &objectType,
++ const QString &urlMethodName,
++ const QString &functorCppType,
++ const QString &objectCppType)
++ {
++ Type bindingFunctor {};
++ bindingFunctor.cppType = functorCppType;
++ bindingFunctor.ignoreInit = true;
++
++ // default member variable and ctor:
++ const QString pointerToObject = objectCppType + u" *";
++ bindingFunctor.variables.emplaceBack(Variable { pointerToObject, u"m_self"_s, u"nullptr"_s });
++ bindingFunctor.baselineCtor.name = functorCppType;
++ bindingFunctor.baselineCtor.parameterList.emplaceBack(
++ Variable { pointerToObject, u"self"_s, QString() });
++ bindingFunctor.baselineCtor.initializerList.emplaceBack(u"m_self(self)"_s);
++
++ // call operator:
++ Method callOperator {};
++ callOperator.returnType = u"void"_s;
++ callOperator.name = u"operator()"_s;
++ callOperator.modifiers << u"const"_s;
++ CodeGenerator::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);
++ 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 Compiler::compileScriptBinding(Type ¤t, const QQmlJSMetaPropertyBinding &binding,
++ const QString &bindingSymbolName,
++ const QQmlJSScope::ConstPtr &objectType,
++ const QString &propertyName,
++ const QQmlJSScope::ConstPtr &propertyType,
++ const Compiler::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<Variable> slotParameters =
++ compileMethodParameters(signal.parameters(), /* allow unnamed = */ true);
++
++ // SignalHander specific:
++ Method slotMethod {};
++ slotMethod.returnType = signalReturnType;
++ slotMethod.name = slotName;
++ slotMethod.parameterList = slotParameters;
++
++ CodeGenerator::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 [groupedProperty, groupedPropertyIndex] =
++ getMetaPropertyIndex(accessor.scope, accessor.propertyName);
++ if (groupedPropertyIndex < 0) {
++ recordError(binding.sourceLocation(),
++ u"Binding on grouped property '" + accessor.propertyName
++ + u"' of unknown type");
++ return;
++ }
++ valueTypeIndex = absoluteIndex;
++ absoluteIndex = groupedPropertyIndex; // e.g. index of accessor.name
++ }
++
++ CodeGenerator::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<qsizetype>(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<QPropertyChangeHandler<" + bindingFunctorName + u">>";
++
++ 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">("
++ + CodeGenerator::wrap_privateClass(accessor.name, *actualProperty)
++ + u"->" + bindableString + u"().onValueChanged(" + bindingFunctorName + u"("
++ + accessor.name + u"))));";
++
++ current.variables.emplaceBack(Variable { typeOfQmlBinding, bindingSymbolName, QString() });
++ break;
++ }
++ default:
++ recordError(binding.sourceLocation(), u"Invalid script binding found"_s);
++ break;
++ }
++ }
++
++ } // namespace QQmltc
++
++ QT_END_NAMESPACE