diff options
Diffstat (limited to 'tools/qmltc/qmltccompiler.cpp')
-rw-r--r-- | tools/qmltc/qmltccompiler.cpp | 397 |
1 files changed, 335 insertions, 62 deletions
diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp index 4050136ef2..75bd580e07 100644 --- a/tools/qmltc/qmltccompiler.cpp +++ b/tools/qmltc/qmltccompiler.cpp @@ -8,6 +8,7 @@ #include "qmltccompilerpieces.h" #include <QtCore/qloggingcategory.h> +#include <QtQml/private/qqmlsignalnames_p.h> #include <private/qqmljsutils_p.h> #include <algorithm> @@ -22,6 +23,137 @@ bool qIsReferenceTypeList(const QQmlJSMetaProperty &p) return false; } +static QList<QQmlJSMetaProperty> unboundRequiredProperties( + const QQmlJSScope::ConstPtr &type, + QmltcTypeResolver *resolver +) { + QList<QQmlJSMetaProperty> 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<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()->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; @@ -87,6 +219,9 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info) 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; @@ -127,7 +262,7 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info) }; for (const auto &type : pureTypes) { - Q_ASSERT(type->scopeType() == QQmlJSScope::QMLScope); + Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope); compiledTypes.emplaceBack(); // create empty type compileType(compiledTypes.back(), type, compile); } @@ -142,8 +277,11 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info) 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; @@ -186,7 +324,8 @@ void QmltcCompiler::compileType( // make document root a friend to allow it to access init and endInit const QString rootInternalName = m_visitor->inlineComponent(type->enclosingInlineComponentName())->internalName(); - current.otherCode << u"friend class %1;"_s.arg(rootInternalName); + 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() @@ -210,8 +349,11 @@ void QmltcCompiler::compileType( return scope->parentScope(); return scope; }; - current.otherCode << u"friend class %1;"_s.arg( - realQmlScope(type->parentScope())->internalName()); + + 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 @@ -240,6 +382,17 @@ void QmltcCompiler::compileType( 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<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; @@ -259,19 +412,40 @@ void QmltcCompiler::compileType( 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<QString>&"_s, + u"initializedCache"_s, + u"{}"_s + ); QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_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 }; + current.setComplexBindings.parameterList = { creator, engine, initializedCache }; current.handleOnCompleted.parameterList = { creator }; if (documentRoot || inlineComponent) { - current.externalCtor.parameterList = { engine, parent }; - current.init.parameterList = { creator, engine, ctxtdata, finalizeFlag }; + const QmltcVariable 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) { + 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 }; @@ -295,18 +469,7 @@ void QmltcCompiler::compileType( // compilation stub: current.externalCtor.body << u"Q_UNUSED(engine)"_s; if (documentRoot || inlineComponent) { - 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 - // now call init - current.externalCtor.body << current.init.name - + u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* " - u"endInit */ true);"; + compileRootExternalConstructorBody(current, type); } else { current.externalCtor.body << u"// not document root:"_s; // just call init, we don't do any setup here otherwise @@ -321,7 +484,7 @@ void QmltcCompiler::compileType( 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 = QQmlJSMetaMethod::StaticMethod; + staticCreate.type = QQmlJSMetaMethodType::StaticMethod; staticCreate.access = QQmlJSMetaMethod::Public; staticCreate.name = u"create"_s; staticCreate.returnType = u"%1 *"_s.arg(current.cppType); @@ -354,6 +517,102 @@ static Iterator partitionBindings(Iterator first, Iterator last) }); } +// 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(\"%2\");"_s.arg( + current.propertyInitializer.initializedCache.name, name); + } +} + void QmltcCompiler::compileTypeElements(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { // compile components of a type: @@ -396,6 +655,7 @@ void QmltcCompiler::compileTypeElements(QmltcType ¤t, const QQmlJSScope::C auto bindings = type->ownPropertyBindingsInQmlIROrder(); partitionBindings(bindings.begin(), bindings.end()); + compilePropertyInitializer(current, type); compileBinding(current, bindings.begin(), bindings.end(), type, { type }); } @@ -450,7 +710,7 @@ compileMethodParameters(const QList<QQmlJSMetaParameter> ¶meterInfos, bool a static QString figureReturnType(const QQmlJSMetaMethod &m) { const bool isVoidMethod = - m.returnTypeName() == u"void" || m.methodType() == QQmlJSMetaMethod::Signal; + m.returnTypeName() == u"void" || m.methodType() == QQmlJSMetaMethodType::Signal; Q_ASSERT(isVoidMethod || m.returnType()); QString type; if (isVoidMethod) { @@ -467,10 +727,10 @@ void QmltcCompiler::compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m, const auto returnType = figureReturnType(m); const QList<QmltcVariable> compiledParams = compileMethodParameters(m.parameters()); - const auto methodType = QQmlJSMetaMethod::Type(m.methodType()); + const auto methodType = m.methodType(); QStringList code; - if (methodType != QQmlJSMetaMethod::Signal) { + if (methodType != QQmlJSMetaMethodType::Signal) { QmltcCodeGenerator urlGenerator { m_url, m_visitor }; QmltcCodeGenerator::generate_callExecuteRuntimeFunction( &code, urlGenerator.urlMethodName() + u"()", @@ -485,7 +745,7 @@ void QmltcCompiler::compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m, compiled.body = std::move(code); compiled.type = methodType; compiled.access = m.access(); - if (methodType != QQmlJSMetaMethod::Signal) { + if (methodType != QQmlJSMetaMethodType::Signal) { compiled.declarationPrefixes << u"Q_INVOKABLE"_s; compiled.userVisible = m.access() == QQmlJSMetaMethod::Public; } else { @@ -1057,7 +1317,7 @@ void QmltcCompiler::compileObjectBinding(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { - Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::Object); + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Object); const QString &propertyName = binding.propertyName(); const QQmlJSMetaProperty property = type->property(propertyName); @@ -1115,7 +1375,7 @@ void QmltcCompiler::compileObjectBinding(QmltcType ¤t, Q_ASSERT(object->baseType()->internalName() == u"QQmlComponent"_s); if (int id = m_visitor->runtimeId(object); id >= 0) { - QString idString = m_visitor->addressableScopes().id(object); + QString idString = m_visitor->addressableScopes().id(object, object); if (idString.isEmpty()) idString = u"<unknown>"_s; QmltcCodeGenerator::generate_setIdValue(block, u"thisContext"_s, id, objectName, @@ -1154,8 +1414,8 @@ void QmltcCompiler::compileValueSourceOrInterceptorBinding(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { - Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::ValueSource - || binding.bindingType() == QQmlJSMetaPropertyBinding::Interceptor); + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::ValueSource + || binding.bindingType() == QQmlSA::BindingType::Interceptor); const QString &propertyName = binding.propertyName(); const QQmlJSMetaProperty property = type->property(propertyName); @@ -1163,7 +1423,7 @@ void QmltcCompiler::compileValueSourceOrInterceptorBinding(QmltcType ¤t, // NB: object is compiled with compileType(), here just need to use it QSharedPointer<const QQmlJSScope> object; - if (binding.bindingType() == QQmlJSMetaPropertyBinding::Interceptor) + if (binding.bindingType() == QQmlSA::BindingType::Interceptor) object = binding.interceptorType(); else object = binding.valueSourceType(); @@ -1212,7 +1472,7 @@ void QmltcCompiler::compileAttachedPropertyBinding(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { - Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::AttachedProperty); + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::AttachedProperty); const QString &propertyName = binding.propertyName(); const QQmlJSMetaProperty property = type->property(propertyName); @@ -1276,7 +1536,7 @@ void QmltcCompiler::compileGroupPropertyBinding(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { - Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::GroupProperty); + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::GroupProperty); const QString &propertyName = binding.propertyName(); const QQmlJSMetaProperty property = type->property(propertyName); @@ -1333,7 +1593,7 @@ void QmltcCompiler::compileGroupPropertyBinding(QmltcType ¤t, auto it = subbindings.begin(); Q_ASSERT(std::all_of(it, firstScript, [](const auto &x) { - return x.bindingType() != QQmlJSMetaPropertyBinding::Script; + return x.bindingType() != QQmlSA::BindingType::Script; })); compile(it, firstScript); it = firstScript; @@ -1354,7 +1614,7 @@ void QmltcCompiler::compileGroupPropertyBinding(QmltcType ¤t, // once the value is written back, process the script bindings Q_ASSERT(std::all_of(it, subbindings.end(), [](const auto &x) { - return x.bindingType() == QQmlJSMetaPropertyBinding::Script; + return x.bindingType() == QQmlSA::BindingType::Script; })); compile(it, subbindings.end()); } @@ -1368,8 +1628,8 @@ void QmltcCompiler::compileTranslationBinding(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { - Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::Translation - || binding.bindingType() == QQmlJSMetaPropertyBinding::TranslationById); + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Translation + || binding.bindingType() == QQmlSA::BindingType::TranslationById); const QString &propertyName = binding.propertyName(); @@ -1446,7 +1706,7 @@ void QmltcCompiler::compileBinding(QmltcType ¤t, 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() == QQmlJSMetaPropertyBinding::GroupProperty + 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 " @@ -1493,26 +1753,32 @@ void QmltcCompiler::compileBindingByType(QmltcType ¤t, accessor.name, constructFromQObject); }; switch (binding.bindingType()) { - case QQmlJSMetaPropertyBinding::BoolLiteral: { + case QQmlSA::BindingType::BoolLiteral: { const bool value = binding.boolValue(); assignToProperty(metaProperty, value ? u"true"_s : u"false"_s); break; } - case QQmlJSMetaPropertyBinding::NumberLiteral: { + case QQmlSA::BindingType::NumberLiteral: { assignToProperty(metaProperty, QString::number(binding.numberValue())); break; } - case QQmlJSMetaPropertyBinding::StringLiteral: { - assignToProperty(metaProperty, QQmlJSUtils::toLiteral(binding.stringValue())); + 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 QQmlJSMetaPropertyBinding::RegExpLiteral: { + case QQmlSA::BindingType::RegExpLiteral: { const QString value = u"QRegularExpression(%1)"_s.arg(QQmlJSUtils::toLiteral(binding.regExpValue())); assignToProperty(metaProperty, value); break; } - case QQmlJSMetaPropertyBinding::Null: { + 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); @@ -1522,38 +1788,38 @@ void QmltcCompiler::compileBindingByType(QmltcType ¤t, assignToProperty(metaProperty, u"QVariant::fromValue(nullptr)"_s); break; } - case QQmlJSMetaPropertyBinding::Script: { + case QQmlSA::BindingType::Script: { QString bindingSymbolName = type->internalName() + u'_' + propertyName + u"_binding"; bindingSymbolName.replace(u'.', u'_'); // can happen with group properties compileScriptBinding(current, binding, bindingSymbolName, type, propertyName, propertyType, accessor); break; } - case QQmlJSMetaPropertyBinding::Object: { + case QQmlSA::BindingType::Object: { compileObjectBinding(current, binding, type, accessor); break; } - case QQmlJSMetaPropertyBinding::Interceptor: + case QQmlSA::BindingType::Interceptor: Q_FALLTHROUGH(); - case QQmlJSMetaPropertyBinding::ValueSource: { + case QQmlSA::BindingType::ValueSource: { compileValueSourceOrInterceptorBinding(current, binding, type, accessor); break; } - case QQmlJSMetaPropertyBinding::AttachedProperty: { + case QQmlSA::BindingType::AttachedProperty: { compileAttachedPropertyBinding(current, binding, type, accessor); break; } - case QQmlJSMetaPropertyBinding::GroupProperty: { + case QQmlSA::BindingType::GroupProperty: { compileGroupPropertyBinding(current, binding, type, accessor); break; } - case QQmlJSMetaPropertyBinding::TranslationById: - case QQmlJSMetaPropertyBinding::Translation: { + case QQmlSA::BindingType::TranslationById: + case QQmlSA::BindingType::Translation: { compileTranslationBinding(current, binding, type, accessor); break; } - case QQmlJSMetaPropertyBinding::Invalid: { + case QQmlSA::BindingType::Invalid: { recordError(binding.sourceLocation(), u"This binding is invalid"_s); break; } @@ -1617,8 +1883,11 @@ static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope // index is already added as p.index()) if (type->isSameType(owner)) return; - if (m == QQmlJSScope::ExtensionNamespace) // extension namespace properties are ignored + + // 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); @@ -1649,10 +1918,10 @@ void QmltcCompiler::compileScriptBinding(QmltcType ¤t, Q_ASSERT(!objectClassName_signal.isEmpty()); Q_ASSERT(!objectClassName_slot.isEmpty()); - const auto signalMethods = objectType->methods(name, QQmlJSMetaMethod::Signal); + 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() == QQmlJSMetaMethod::Signal); + Q_ASSERT(signal.methodType() == QQmlJSMetaMethodType::Signal); const QString signalName = signal.methodName(); const QString slotName = newSymbol(signalName + u"_slot"); @@ -1672,7 +1941,7 @@ void QmltcCompiler::compileScriptBinding(QmltcType ¤t, objectType->ownRuntimeFunctionIndex(binding.scriptIndex()), u"this"_s, // Note: because script bindings always use current QML object scope signalReturnType, slotParameters); - slotMethod.type = QQmlJSMetaMethod::Slot; + slotMethod.type = QQmlJSMetaMethodType::Slot; current.functions << std::move(slotMethod); current.setComplexBindings.body << u"QObject::connect(" + This_signal + u", " + u"&" @@ -1681,7 +1950,7 @@ void QmltcCompiler::compileScriptBinding(QmltcType ¤t, }; switch (binding.scriptKind()) { - case QQmlJSMetaPropertyBinding::Script_PropertyBinding: { + case QQmlSA::ScriptBindingKind::PropertyBinding: { if (!propertyType) { recordError(binding.sourceLocation(), u"Binding on property '" + propertyName + u"' of unknown type"); @@ -1723,19 +1992,20 @@ void QmltcCompiler::compileScriptBinding(QmltcType ¤t, property, valueTypeIndex, accessor.name); break; } - case QQmlJSMetaPropertyBinding::Script_SignalHandler: { - const auto name = QQmlJSUtils::signalName(propertyName); + case QQmlSA::ScriptBindingKind::SignalHandler: { + const auto name = QQmlSignalNames::handlerNameToSignalName(propertyName); Q_ASSERT(name.has_value()); compileScriptSignal(*name); break; } - case QQmlJSMetaPropertyBinding::Script_ChangeHandler: { + case QQmlSA ::ScriptBindingKind::ChangeHandler: { const QString objectClassName = objectType->internalName(); const QString bindingFunctorName = newSymbol(bindingSymbolName + u"Functor"); - const auto signalName = QQmlJSUtils::signalName(propertyName); + const auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName); Q_ASSERT(signalName.has_value()); // an error somewhere else - const auto actualProperty = QQmlJSUtils::changeHandlerProperty(objectType, *signalName); + const auto actualProperty = + QQmlJSUtils::propertyFromChangedHandler(objectType, propertyName); Q_ASSERT(actualProperty.has_value()); // an error somewhere else const auto actualPropertyType = actualProperty->type(); if (!actualPropertyType) { @@ -1759,9 +2029,12 @@ void QmltcCompiler::compileScriptBinding(QmltcType ¤t, current.children << compileScriptBindingPropertyChangeHandler( binding, objectType, m_urlMethodName, bindingFunctorName, objectClassName); + current.setComplexBindings.body << u"if (!%1.contains(\"%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 << bindingSymbolName + u".reset(new QPropertyChangeHandler<" + current.setComplexBindings.body << u" "_s + bindingSymbolName + u".reset(new QPropertyChangeHandler<" + bindingFunctorName + u">(" + QmltcCodeGenerator::wrap_privateClass(accessor.name, *actualProperty) + u"->" + bindableString + u"().onValueChanged(" + bindingFunctorName + u"(" |