| 1 | // Copyright (C) 2021 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
| 3 | |
| 4 | #include "qmltccompiler.h" |
| 5 | #include "qmltcoutputir.h" |
| 6 | #include "qmltccodewriter.h" |
| 7 | #include "qmltcpropertyutils.h" |
| 8 | #include "qmltccompilerpieces.h" |
| 9 | |
| 10 | #include <QtCore/qloggingcategory.h> |
| 11 | #include <QtQml/private/qqmlsignalnames_p.h> |
| 12 | #include <private/qqmljsutils_p.h> |
| 13 | |
| 14 | #include <algorithm> |
| 15 | |
| 16 | QT_BEGIN_NAMESPACE |
| 17 | using namespace Qt::StringLiterals; |
| 18 | |
| 19 | bool qIsReferenceTypeList(const QQmlJSMetaProperty &p) |
| 20 | { |
| 21 | if (QQmlJSScope::ConstPtr type = p.type()) |
| 22 | return type->isListProperty(); |
| 23 | return false; |
| 24 | } |
| 25 | |
| 26 | static QList<QQmlJSMetaProperty> unboundRequiredProperties( |
| 27 | const QQmlJSScope::ConstPtr &type, |
| 28 | QmltcTypeResolver *resolver |
| 29 | ) { |
| 30 | QList<QQmlJSMetaProperty> requiredProperties{}; |
| 31 | |
| 32 | auto isPropertyRequired = [&type, &resolver](const auto &property) { |
| 33 | if (!type->isPropertyRequired(name: property.propertyName())) |
| 34 | return false; |
| 35 | |
| 36 | if (type->hasPropertyBindings(name: property.propertyName())) |
| 37 | return false; |
| 38 | |
| 39 | if (property.isAlias()) { |
| 40 | QQmlJSUtils::AliasResolutionVisitor aliasVisitor; |
| 41 | |
| 42 | QQmlJSUtils::ResolvedAlias result = |
| 43 | QQmlJSUtils::resolveAlias(resolver, property, type, aliasVisitor); |
| 44 | |
| 45 | if (result.kind != QQmlJSUtils::AliasTarget_Property) |
| 46 | return false; |
| 47 | |
| 48 | // If the top level alias targets a property that is in |
| 49 | // the top level scope and that property is required, then |
| 50 | // we will already pick up the property during one of the |
| 51 | // iterations. |
| 52 | // Setting the property or the alias is the same so we |
| 53 | // discard one of the two, as otherwise we would require |
| 54 | // the user to pass two values for the same property ,in |
| 55 | // this case the alias. |
| 56 | // |
| 57 | // For example in: |
| 58 | // |
| 59 | // ``` |
| 60 | // Item { |
| 61 | // id: self |
| 62 | // required property int foo |
| 63 | // property alias bar: self.foo |
| 64 | // } |
| 65 | // ``` |
| 66 | // |
| 67 | // Both foo and bar are required but setting one or the |
| 68 | // other is the same operation so that we should choose |
| 69 | // only one. |
| 70 | if (result.owner == type && |
| 71 | type->isPropertyRequired(name: result.property.propertyName())) |
| 72 | return false; |
| 73 | |
| 74 | if (result.owner->hasPropertyBindings(name: result.property.propertyName())) |
| 75 | return false; |
| 76 | } |
| 77 | |
| 78 | return true; |
| 79 | }; |
| 80 | |
| 81 | const auto properties = type->properties(); |
| 82 | std::copy_if(first: properties.cbegin(), last: properties.cend(), |
| 83 | result: std::back_inserter(x&: requiredProperties), pred: isPropertyRequired); |
| 84 | std::sort(first: requiredProperties.begin(), last: requiredProperties.end(), |
| 85 | comp: [](const auto &left, const auto &right) { |
| 86 | return left.propertyName() < right.propertyName(); |
| 87 | }); |
| 88 | |
| 89 | return requiredProperties; |
| 90 | } |
| 91 | |
| 92 | |
| 93 | // Populates the internal representation for a |
| 94 | // RequiredPropertiesBundle, a class that acts as a bundle of initial |
| 95 | // values that should be set for the required properties of a type. |
| 96 | static void compileRequiredPropertiesBundle( |
| 97 | QmltcType ¤t, |
| 98 | const QQmlJSScope::ConstPtr &type, |
| 99 | QmltcTypeResolver *resolver |
| 100 | ) { |
| 101 | |
| 102 | QList<QQmlJSMetaProperty> requiredProperties = unboundRequiredProperties(type, resolver); |
| 103 | |
| 104 | if (requiredProperties.isEmpty()) |
| 105 | return; |
| 106 | |
| 107 | current.requiredPropertiesBundle.emplace(); |
| 108 | current.requiredPropertiesBundle->name = u"RequiredPropertiesBundle"_s ; |
| 109 | |
| 110 | current.requiredPropertiesBundle->members.reserve(asize: requiredProperties.size()); |
| 111 | std::transform(first: requiredProperties.cbegin(), last: requiredProperties.cend(), |
| 112 | result: std::back_inserter(x&: current.requiredPropertiesBundle->members), |
| 113 | unary_op: [](const QQmlJSMetaProperty &property) { |
| 114 | QString type = qIsReferenceTypeList(p: property) |
| 115 | ? u"const QList<%1*>&"_s .arg( |
| 116 | a: property.type()->valueType()->internalName()) |
| 117 | : u"passByConstRefOrValue<%1>"_s .arg( |
| 118 | a: property.type()->augmentedInternalName()); |
| 119 | return QmltcVariable{ type, property.propertyName() }; |
| 120 | }); |
| 121 | } |
| 122 | |
| 123 | static void compileRootExternalConstructorBody( |
| 124 | QmltcType& current, |
| 125 | const QQmlJSScope::ConstPtr &type |
| 126 | ) { |
| 127 | current.externalCtor.body << u"// document root:"_s ; |
| 128 | // if it's document root, we want to create our QQmltcObjectCreationBase |
| 129 | // that would store all the created objects |
| 130 | current.externalCtor.body << u"QQmltcObjectCreationBase<%1> objectHolder;"_s .arg( |
| 131 | a: type->internalName()); |
| 132 | current.externalCtor.body |
| 133 | << u"QQmltcObjectCreationHelper creator = objectHolder.view();"_s ; |
| 134 | current.externalCtor.body << u"creator.set(0, this);"_s ; // special case |
| 135 | |
| 136 | QString initializerName = u"initializer"_s ; |
| 137 | if (current.requiredPropertiesBundle) { |
| 138 | // Compose new initializer based on the initial values for required properties. |
| 139 | current.externalCtor.body << u"auto newInitializer = [&](auto& propertyInitializer) {"_s ; |
| 140 | for (const auto& member : current.requiredPropertiesBundle->members) { |
| 141 | current.externalCtor.body << u" propertyInitializer.%1(requiredPropertiesBundle.%2);"_s .arg( |
| 142 | args: QmltcPropertyData(member.name).write, args: member.name |
| 143 | ); |
| 144 | } |
| 145 | current.externalCtor.body << u" initializer(propertyInitializer);"_s ; |
| 146 | current.externalCtor.body << u"};"_s ; |
| 147 | |
| 148 | initializerName = u"newInitializer"_s ; |
| 149 | } |
| 150 | |
| 151 | // now call init |
| 152 | current.externalCtor.body << current.init.name |
| 153 | + u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* " |
| 154 | u"endInit */ true, %1);"_s .arg(a: initializerName); |
| 155 | }; |
| 156 | |
| 157 | Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler" , QtWarningMsg); |
| 158 | |
| 159 | const QString QmltcCodeGenerator::privateEngineName = u"ePriv"_s ; |
| 160 | const QString QmltcCodeGenerator::typeCountName = u"q_qmltc_typeCount"_s ; |
| 161 | |
| 162 | QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor, |
| 163 | QQmlJSLogger *logger) |
| 164 | : m_url(url), m_typeResolver(resolver), m_visitor(visitor), m_logger(logger) |
| 165 | { |
| 166 | Q_UNUSED(m_typeResolver); |
| 167 | Q_ASSERT(!hasErrors()); |
| 168 | } |
| 169 | |
| 170 | // needed due to std::unique_ptr<CodeGenerator> with CodeGenerator being |
| 171 | // incomplete type in the header (~std::unique_ptr<> fails with a static_assert) |
| 172 | QmltcCompiler::~QmltcCompiler() = default; |
| 173 | |
| 174 | QString QmltcCompiler::newSymbol(const QString &base) |
| 175 | { |
| 176 | QString symbol = base; |
| 177 | symbol.replace(before: QLatin1String("." ), after: QLatin1String("_" )); |
| 178 | while (symbol.startsWith(c: QLatin1Char('_')) && symbol.size() >= 2 |
| 179 | && (symbol[1].isUpper() || symbol[1] == QLatin1Char('_'))) { |
| 180 | symbol.remove(i: 0, len: 1); |
| 181 | } |
| 182 | if (!m_symbols.contains(key: symbol)) { |
| 183 | m_symbols.insert(key: symbol, value: 1); |
| 184 | } else { |
| 185 | symbol += u"_" + QString::number(m_symbols[symbol]++); |
| 186 | } |
| 187 | return symbol; |
| 188 | } |
| 189 | |
| 190 | void QmltcCompiler::compile(const QmltcCompilerInfo &info) |
| 191 | { |
| 192 | m_info = info; |
| 193 | Q_ASSERT(!m_info.outputCppFile.isEmpty()); |
| 194 | Q_ASSERT(!m_info.outputHFile.isEmpty()); |
| 195 | Q_ASSERT(!m_info.resourcePath.isEmpty()); |
| 196 | |
| 197 | // Note: we only compile "pure" QML types. any component-wrapped type is |
| 198 | // expected to appear through a binding |
| 199 | |
| 200 | const auto isComponent = [](const QQmlJSScope::ConstPtr &type) { |
| 201 | auto base = type->baseType(); |
| 202 | return base && base->internalName() == u"QQmlComponent"_s ; |
| 203 | }; |
| 204 | |
| 205 | QmltcCodeGenerator generator { .documentUrl: m_url, .visitor: m_visitor }; |
| 206 | |
| 207 | QmltcMethod urlMethod; |
| 208 | compileUrlMethod(urlMethod, urlMethodName: generator.urlMethodName()); |
| 209 | m_urlMethodName = urlMethod.name; |
| 210 | |
| 211 | // sort inline components to compile them in the right order |
| 212 | // a inherits b => b needs to be defined in the cpp file before a! |
| 213 | // r is the root => r needs to be compiled at the end! |
| 214 | // otherwise => sort them by inline component names to have consistent output |
| 215 | auto sortedInlineComponentNames = m_visitor->inlineComponentNames(); |
| 216 | std::sort(first: sortedInlineComponentNames.begin(), last: sortedInlineComponentNames.end(), |
| 217 | comp: [&](const InlineComponentOrDocumentRootName &a, |
| 218 | const InlineComponentOrDocumentRootName &b) { |
| 219 | const auto *inlineComponentAName = std::get_if<InlineComponentNameType>(ptr: &a); |
| 220 | const auto *inlineComponentBName = std::get_if<InlineComponentNameType>(ptr: &b); |
| 221 | |
| 222 | if (inlineComponentAName == inlineComponentBName) |
| 223 | return false; |
| 224 | |
| 225 | // the root comes at last, so (a < b) == true when b is the root and a is not |
| 226 | if (inlineComponentAName && !inlineComponentBName) |
| 227 | return true; |
| 228 | |
| 229 | // b requires a to be declared before b when b inherits from a, therefore (a < b) |
| 230 | // == true |
| 231 | if (inlineComponentAName && inlineComponentBName) { |
| 232 | QQmlJSScope::ConstPtr inlineComponentA = m_visitor->inlineComponent(inlineComponentName: a); |
| 233 | QQmlJSScope::ConstPtr inlineComponentB = m_visitor->inlineComponent(inlineComponentName: b); |
| 234 | if (inlineComponentB->inherits(base: inlineComponentA)) { |
| 235 | return true; |
| 236 | } else if (inlineComponentA->inherits(base: inlineComponentB)) { |
| 237 | return false; |
| 238 | } else { |
| 239 | // fallback to default sorting based on names |
| 240 | return *inlineComponentAName < *inlineComponentBName; |
| 241 | } |
| 242 | } |
| 243 | Q_ASSERT(!inlineComponentAName || !inlineComponentBName); |
| 244 | // a is the root or both a and b are the root |
| 245 | return false; |
| 246 | }); |
| 247 | |
| 248 | QList<QmltcType> compiledTypes; |
| 249 | for (const auto &inlineComponent : sortedInlineComponentNames) { |
| 250 | const QList<QQmlJSScope::ConstPtr> &pureTypes = m_visitor->pureQmlTypes(inlineComponent); |
| 251 | Q_ASSERT(!pureTypes.empty()); |
| 252 | const QQmlJSScope::ConstPtr &root = pureTypes.front(); |
| 253 | if (isComponent(root)) { |
| 254 | compiledTypes.emplaceBack(); // create empty type |
| 255 | const auto compile = [&](QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { |
| 256 | generator.generate_initCodeForTopLevelComponent(current, type); |
| 257 | }; |
| 258 | compileType(current&: compiledTypes.back(), type: root, compileElements: compile); |
| 259 | } else { |
| 260 | const auto compile = [this](QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { |
| 261 | compileTypeElements(current, type); |
| 262 | }; |
| 263 | |
| 264 | for (const auto &type : pureTypes) { |
| 265 | Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope); |
| 266 | compiledTypes.emplaceBack(); // create empty type |
| 267 | compileType(current&: compiledTypes.back(), type, compileElements: compile); |
| 268 | } |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | if (hasErrors()) |
| 273 | return; |
| 274 | |
| 275 | QmltcProgram program; |
| 276 | program.url = m_url; |
| 277 | program.cppPath = m_info.outputCppFile; |
| 278 | program.hPath = m_info.outputHFile; |
| 279 | program.outNamespace = m_info.outputNamespace; |
| 280 | program.exportMacro = m_info.exportMacro; |
| 281 | program.compiledTypes = compiledTypes; |
| 282 | program.includes = m_visitor->cppIncludeFiles(); |
| 283 | if (!m_info.exportMacro.isEmpty() && !m_info.exportInclude.isEmpty()) |
| 284 | program.includes += (m_info.exportInclude); |
| 285 | program.urlMethod = urlMethod; |
| 286 | |
| 287 | QmltcOutput out; |
| 288 | QmltcOutputWrapper code(out); |
| 289 | QmltcCodeWriter::write(code, program); |
| 290 | } |
| 291 | |
| 292 | void QmltcCompiler::compileUrlMethod(QmltcMethod &urlMethod, const QString &urlMethodName) |
| 293 | { |
| 294 | urlMethod.name = urlMethodName; |
| 295 | urlMethod.returnType = u"const QUrl&"_s ; |
| 296 | urlMethod.body << u"static QUrl url {QStringLiteral(\"qrc:%1\")};"_s .arg(a: m_info.resourcePath); |
| 297 | urlMethod.body << u"return url;"_s ; |
| 298 | urlMethod.declarationPrefixes << u"static"_s ; |
| 299 | urlMethod.modifiers << u"noexcept"_s ; |
| 300 | } |
| 301 | |
| 302 | void QmltcCompiler::compileType( |
| 303 | QmltcType ¤t, const QQmlJSScope::ConstPtr &type, |
| 304 | std::function<void(QmltcType &, const QQmlJSScope::ConstPtr &)> compileElements) |
| 305 | { |
| 306 | Q_ASSERT(!type->internalName().isEmpty()); |
| 307 | current.cppType = type->internalName(); |
| 308 | Q_ASSERT(!type->baseType()->internalName().isEmpty()); |
| 309 | const QString baseClass = type->baseType()->internalName(); |
| 310 | |
| 311 | const auto rootType = m_visitor->result(); |
| 312 | const InlineComponentOrDocumentRootName name = type->enclosingInlineComponentName(); |
| 313 | QQmlJSScope::ConstPtr inlineComponentType = m_visitor->inlineComponent(inlineComponentName: name); |
| 314 | Q_ASSERT(inlineComponentType); |
| 315 | const bool documentRoot = (type == rootType); |
| 316 | const bool inlineComponent = type->isInlineComponent(); |
| 317 | const bool isAnonymous = !documentRoot || type->internalName().at(i: 0).isLower(); |
| 318 | const bool isSingleton = type->isSingleton(); |
| 319 | |
| 320 | QmltcCodeGenerator generator { .documentUrl: m_url, .visitor: m_visitor }; |
| 321 | |
| 322 | current.baseClasses = { baseClass }; |
| 323 | if (!documentRoot) { |
| 324 | // make document root a friend to allow it to access init and endInit |
| 325 | const QString rootInternalName = |
| 326 | m_visitor->inlineComponent(inlineComponentName: type->enclosingInlineComponentName())->internalName(); |
| 327 | if (rootInternalName != current.cppType) // avoid GCC13 warning on self-befriending |
| 328 | current.otherCode << "friend class %1;"_L1 .arg(args: rootInternalName); |
| 329 | } |
| 330 | if (documentRoot || inlineComponent) { |
| 331 | auto name = type->inlineComponentName() |
| 332 | ? InlineComponentOrDocumentRootName(*type->inlineComponentName()) |
| 333 | : InlineComponentOrDocumentRootName(RootDocumentNameType()); |
| 334 | // make QQmltcObjectCreationBase<DocumentRoot> a friend to allow it to |
| 335 | // be created for the root object |
| 336 | current.otherCode << u"friend class QQmltcObjectCreationBase<%1>;"_s .arg( |
| 337 | a: inlineComponentType->internalName()); |
| 338 | // generate typeCount for all components (root + inlineComponents) |
| 339 | QmltcMethod typeCountMethod; |
| 340 | typeCountMethod.name = QmltcCodeGenerator::typeCountName; |
| 341 | typeCountMethod.returnType = u"uint"_s ; |
| 342 | typeCountMethod.body << u"return " + generator.generate_typeCount(inlinedComponent: name) + u";" ; |
| 343 | current.typeCount = typeCountMethod; |
| 344 | } else { |
| 345 | // make an immediate parent a friend since that parent |
| 346 | // would create the object through a non-public constructor |
| 347 | const auto realQmlScope = [](const QQmlJSScope::ConstPtr &scope) { |
| 348 | if (scope->isArrayScope()) |
| 349 | return scope->parentScope(); |
| 350 | return scope; |
| 351 | }; |
| 352 | |
| 353 | const auto& realScope = realQmlScope(type->parentScope()); |
| 354 | if (realScope != rootType) { |
| 355 | current.otherCode << u"friend class %1;"_s .arg(a: realScope->internalName()); |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | // make QQmltcObjectCreationHelper a friend of every type since it provides |
| 360 | // useful helper methods for all types |
| 361 | current.otherCode << u"friend class QT_PREPEND_NAMESPACE(QQmltcObjectCreationHelper);"_s ; |
| 362 | |
| 363 | current.mocCode = { |
| 364 | u"Q_OBJECT"_s , |
| 365 | // Note: isAnonymous holds for non-root types in the document as well |
| 366 | type->isInlineComponent() ? (u"QML_NAMED_ELEMENT(%1)"_s .arg(a: *type->inlineComponentName())) |
| 367 | : (isAnonymous ? u"QML_ANONYMOUS"_s : u"QML_ELEMENT"_s ), |
| 368 | }; |
| 369 | |
| 370 | // add special member functions |
| 371 | current.baselineCtor.access = QQmlJSMetaMethod::Protected; |
| 372 | if (documentRoot || inlineComponent || isSingleton) { |
| 373 | current.externalCtor.access = QQmlJSMetaMethod::Public; |
| 374 | } else { |
| 375 | current.externalCtor.access = QQmlJSMetaMethod::Protected; |
| 376 | } |
| 377 | current.init.access = QQmlJSMetaMethod::Protected; |
| 378 | current.beginClass.access = QQmlJSMetaMethod::Protected; |
| 379 | current.endInit.access = QQmlJSMetaMethod::Protected; |
| 380 | current.setComplexBindings.access = QQmlJSMetaMethod::Protected; |
| 381 | current.completeComponent.access = QQmlJSMetaMethod::Protected; |
| 382 | current.finalizeComponent.access = QQmlJSMetaMethod::Protected; |
| 383 | current.handleOnCompleted.access = QQmlJSMetaMethod::Protected; |
| 384 | |
| 385 | current.propertyInitializer.name = u"PropertyInitializer"_s ; |
| 386 | current.propertyInitializer.constructor.access = QQmlJSMetaMethod::Public; |
| 387 | current.propertyInitializer.constructor.name = current.propertyInitializer.name; |
| 388 | current.propertyInitializer.constructor.parameterList = { |
| 389 | QmltcVariable(u"%1&"_s .arg(a: current.cppType), u"component"_s ) |
| 390 | }; |
| 391 | current.propertyInitializer.component.cppType = current.cppType + u"&" ; |
| 392 | current.propertyInitializer.component.name = u"component"_s ; |
| 393 | current.propertyInitializer.initializedCache.cppType = u"QSet<QString>"_s ; |
| 394 | current.propertyInitializer.initializedCache.name = u"initializedCache"_s ; |
| 395 | |
| 396 | current.baselineCtor.name = current.cppType; |
| 397 | current.externalCtor.name = current.cppType; |
| 398 | current.init.name = u"QML_init"_s ; |
| 399 | current.init.returnType = u"QQmlRefPointer<QQmlContextData>"_s ; |
| 400 | current.beginClass.name = u"QML_beginClass"_s ; |
| 401 | current.beginClass.returnType = u"void"_s ; |
| 402 | current.endInit.name = u"QML_endInit"_s ; |
| 403 | current.endInit.returnType = u"void"_s ; |
| 404 | current.setComplexBindings.name = u"QML_setComplexBindings"_s ; |
| 405 | current.setComplexBindings.returnType = u"void"_s ; |
| 406 | current.completeComponent.name = u"QML_completeComponent"_s ; |
| 407 | current.completeComponent.returnType = u"void"_s ; |
| 408 | current.finalizeComponent.name = u"QML_finalizeComponent"_s ; |
| 409 | current.finalizeComponent.returnType = u"void"_s ; |
| 410 | current.handleOnCompleted.name = u"QML_handleOnCompleted"_s ; |
| 411 | current.handleOnCompleted.returnType = u"void"_s ; |
| 412 | QmltcVariable creator(u"QQmltcObjectCreationHelper*"_s , u"creator"_s ); |
| 413 | QmltcVariable engine(u"QQmlEngine*"_s , u"engine"_s ); |
| 414 | QmltcVariable parent(u"QObject*"_s , u"parent"_s , u"nullptr"_s ); |
| 415 | QmltcVariable initializedCache( |
| 416 | u"[[maybe_unused]] const QSet<QString>&"_s , |
| 417 | u"initializedCache"_s , |
| 418 | u"{}"_s |
| 419 | ); |
| 420 | QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_s , u"parentContext"_s ); |
| 421 | QmltcVariable finalizeFlag(u"bool"_s , u"canFinalize"_s ); |
| 422 | current.baselineCtor.parameterList = { parent }; |
| 423 | current.endInit.parameterList = { creator, engine }; |
| 424 | current.setComplexBindings.parameterList = { creator, engine, initializedCache }; |
| 425 | current.handleOnCompleted.parameterList = { creator }; |
| 426 | |
| 427 | if (documentRoot || inlineComponent) { |
| 428 | const QmltcVariable initializer( |
| 429 | u"[[maybe_unused]] qxp::function_ref<void(%1&)>"_s .arg(a: current.propertyInitializer.name), |
| 430 | u"initializer"_s , |
| 431 | u"[](%1&){}"_s .arg(a: current.propertyInitializer.name)); |
| 432 | |
| 433 | current.init.parameterList = { creator, engine, ctxtdata, finalizeFlag, initializer }; |
| 434 | current.beginClass.parameterList = { creator, finalizeFlag }; |
| 435 | current.completeComponent.parameterList = { creator, finalizeFlag }; |
| 436 | current.finalizeComponent.parameterList = { creator, finalizeFlag }; |
| 437 | |
| 438 | compileRequiredPropertiesBundle(current, type, resolver: m_typeResolver); |
| 439 | |
| 440 | if (current.requiredPropertiesBundle) { |
| 441 | QmltcVariable bundle{ |
| 442 | u"const %1&"_s .arg(a: current.requiredPropertiesBundle->name), |
| 443 | u"requiredPropertiesBundle"_s , |
| 444 | }; |
| 445 | current.externalCtor.parameterList = { engine, bundle, parent, initializer }; |
| 446 | } else { |
| 447 | current.externalCtor.parameterList = { engine, parent, initializer }; |
| 448 | } |
| 449 | } else { |
| 450 | current.externalCtor.parameterList = { creator, engine, parent }; |
| 451 | current.init.parameterList = { creator, engine, ctxtdata }; |
| 452 | current.beginClass.parameterList = { creator }; |
| 453 | current.completeComponent.parameterList = { creator }; |
| 454 | current.finalizeComponent.parameterList = { creator }; |
| 455 | } |
| 456 | |
| 457 | current.externalCtor.initializerList = { current.baselineCtor.name + u"(" + parent.name |
| 458 | + u")" }; |
| 459 | if (QQmlJSUtils::hasCompositeBase(scope: type)) { |
| 460 | // call parent's (QML type's) basic ctor from this. that one will take |
| 461 | // care about QObject::setParent() |
| 462 | current.baselineCtor.initializerList = { baseClass + u"(" + parent.name + u")" }; |
| 463 | } else { |
| 464 | // default call to ctor is enough, but QQml_setParent_noEvent() is |
| 465 | // needed (note: faster? version of QObject::setParent()) |
| 466 | current.baselineCtor.body << u"QQml_setParent_noEvent(this, " + parent.name + u");" ; |
| 467 | } |
| 468 | |
| 469 | // compilation stub: |
| 470 | current.externalCtor.body << u"Q_UNUSED(engine)"_s ; |
| 471 | if (documentRoot || inlineComponent) { |
| 472 | compileRootExternalConstructorBody(current, type); |
| 473 | } else { |
| 474 | current.externalCtor.body << u"// not document root:"_s ; |
| 475 | // just call init, we don't do any setup here otherwise |
| 476 | current.externalCtor.body << current.init.name |
| 477 | + u"(creator, engine, QQmlData::get(parent)->outerContext);" ; |
| 478 | } |
| 479 | |
| 480 | if (isSingleton) { |
| 481 | // see https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON for context |
| 482 | current.mocCode.append(t: u"QML_SINGLETON"_s ); |
| 483 | auto &staticCreate = current.staticCreate.emplace(); |
| 484 | staticCreate.comments |
| 485 | << u"Used by the engine for singleton creation."_s |
| 486 | << u"See also \\l {https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON}."_s ; |
| 487 | staticCreate.type = QQmlJSMetaMethodType::StaticMethod; |
| 488 | staticCreate.access = QQmlJSMetaMethod::Public; |
| 489 | staticCreate.name = u"create"_s ; |
| 490 | staticCreate.returnType = u"%1 *"_s .arg(a: current.cppType); |
| 491 | QmltcVariable jsEngine(u"QJSEngine*"_s , u"jsEngine"_s ); |
| 492 | staticCreate.parameterList = { engine, jsEngine }; |
| 493 | staticCreate.body << u"Q_UNUSED(jsEngine);"_s |
| 494 | << u"%1 *result = new %1(engine, nullptr);"_s .arg(a: current.cppType) |
| 495 | << u"return result;"_s ; |
| 496 | } |
| 497 | auto postponedQmlContextSetup = generator.generate_initCode(current, type); |
| 498 | generator.generate_endInitCode(current, type); |
| 499 | generator.generate_setComplexBindingsCode(current, type); |
| 500 | generator.generate_beginClassCode(current, type); |
| 501 | generator.generate_completeComponentCode(current, type); |
| 502 | generator.generate_finalizeComponentCode(current, type); |
| 503 | generator.generate_handleOnCompletedCode(current, type); |
| 504 | |
| 505 | compileElements(current, type); |
| 506 | } |
| 507 | |
| 508 | template<typename Iterator> |
| 509 | static Iterator partitionBindings(Iterator first, Iterator last) |
| 510 | { |
| 511 | // NB: the code generator cares about script bindings being processed at a |
| 512 | // later point, so we should sort or partition the range. we do a stable |
| 513 | // partition since the relative order of binding evaluation affects the UI |
| 514 | return std::stable_partition(first, last, [](const QQmlJSMetaPropertyBinding &b) { |
| 515 | // we want complex bindings to be at the end, so do the negation |
| 516 | return !QmltcCompiler::isComplexBinding(binding: b); |
| 517 | }); |
| 518 | } |
| 519 | |
| 520 | // Populates the propertyInitializer of the current type based on the |
| 521 | // available properties. |
| 522 | // |
| 523 | // A propertyInitializer is a generated class that provides a |
| 524 | // restricted interface that only allows setting property values and |
| 525 | // internally keep tracks of which properties where actually set, |
| 526 | // intended to be used to allow the user to set up the initial values |
| 527 | // when creating an instance of a component. |
| 528 | // |
| 529 | // For each property of the current type that is known, is not private |
| 530 | // and is writable, a setter method is generated. |
| 531 | // Each setter method knows how to set a specific property, so as to |
| 532 | // provide a strongly typed interface to property setting, as if the |
| 533 | // relevant C++ type was used directly. |
| 534 | // |
| 535 | // Each setter uses the write method for the proprerty when available |
| 536 | // and otherwise falls back to a the more generic |
| 537 | // `QObject::setProperty` for properties where a WRITE method is not |
| 538 | // available or in scope. |
| 539 | void QmltcCompiler::compilePropertyInitializer( |
| 540 | QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { |
| 541 | static auto isFromExtension |
| 542 | = [](const QQmlJSMetaProperty &property, const QQmlJSScope::ConstPtr &scope) { |
| 543 | return scope->ownerOfProperty(self: scope, name: property.propertyName()).extensionSpecifier |
| 544 | != QQmlJSScope::NotExtension; |
| 545 | }; |
| 546 | |
| 547 | current.propertyInitializer.constructor.initializerList << u"component{component}"_s ; |
| 548 | |
| 549 | const auto properties = type->properties().values(); |
| 550 | for (const auto &property: properties) { |
| 551 | if (property.index() == -1) continue; |
| 552 | if (property.isPrivate()) continue; |
| 553 | if (!property.isWritable() && !qIsReferenceTypeList(p: property)) continue; |
| 554 | |
| 555 | const QString name = property.propertyName(); |
| 556 | const auto propertyType = property.type(); |
| 557 | if (propertyType.isNull()) { |
| 558 | recordError(location: type->sourceLocation(), message: u"Type of property '%1' is unknown"_s .arg(a: name)); |
| 559 | continue; |
| 560 | } |
| 561 | |
| 562 | current.propertyInitializer.propertySetters.emplace_back(); |
| 563 | auto& compiledSetter = current.propertyInitializer.propertySetters.back(); |
| 564 | |
| 565 | compiledSetter.userVisible = true; |
| 566 | compiledSetter.returnType = u"void"_s ; |
| 567 | compiledSetter.name = QmltcPropertyData(property).write; |
| 568 | |
| 569 | if (qIsReferenceTypeList(p: property)) { |
| 570 | compiledSetter.parameterList.emplaceBack( |
| 571 | args: QQmlJSUtils::constRefify( |
| 572 | type: u"QList<%1*>"_s .arg(a: propertyType->valueType()->internalName())), |
| 573 | args: name + u"_" , args: QString() |
| 574 | ); |
| 575 | } else { |
| 576 | compiledSetter.parameterList.emplaceBack( |
| 577 | args: QQmlJSUtils::constRefify(type: getUnderlyingType(p: property)), args: name + u"_" , args: QString() |
| 578 | ); |
| 579 | } |
| 580 | |
| 581 | if (qIsReferenceTypeList(p: property)) { |
| 582 | compiledSetter.body << u"QQmlListReference list_ref_(&%1, \"%2\");"_s .arg( |
| 583 | args&: current.propertyInitializer.component.name, args: name |
| 584 | ); |
| 585 | compiledSetter.body << u"list_ref_.clear();"_s ; |
| 586 | compiledSetter.body << u"for (const auto& list_item_ : %1_)"_s .arg(a: name); |
| 587 | compiledSetter.body << u" list_ref_.append(list_item_);"_s ; |
| 588 | } else if ( |
| 589 | QQmlJSUtils::bindablePropertyHasDefaultAccessor(p: property, accessor: QQmlJSUtils::PropertyAccessor_Write) |
| 590 | ) { |
| 591 | compiledSetter.body << u"%1.%2().setValue(%3_);"_s .arg( |
| 592 | args&: current.propertyInitializer.component.name, args: property.bindable(), args: name); |
| 593 | } else if (type->hasOwnProperty(name)) { |
| 594 | compiledSetter.body << u"%1.%2(%3_);"_s .arg( |
| 595 | args&: current.propertyInitializer.component.name, args: QmltcPropertyData(property).write, args: name); |
| 596 | } else if (property.write().isEmpty() || isFromExtension(property, type)) { |
| 597 | // We can end here if a WRITE method is not available or |
| 598 | // if the method is available but not in this scope, so |
| 599 | // that we fallback to the string-based setters.. |
| 600 | // |
| 601 | // For example, types that makes use of QML_EXTENDED |
| 602 | // types, will have the extension types properties |
| 603 | // available and with a WRITE method, but the WRITE method |
| 604 | // will not be available to the extended type, from C++, |
| 605 | // as the type does not directly inherit from the |
| 606 | // extension type. |
| 607 | // |
| 608 | // We specifically scope `setProperty` to `QObject` as |
| 609 | // certain types might have shadowed the method. |
| 610 | // For example, in QtQuick, some types have a property |
| 611 | // called `property` with a `setProperty` WRITE method |
| 612 | // that will produce the shadowing. |
| 613 | compiledSetter.body << u"%1.QObject::setProperty(\"%2\", QVariant::fromValue(%2_));"_s .arg( |
| 614 | args&: current.propertyInitializer.component.name, args: name); |
| 615 | } else { |
| 616 | compiledSetter.body << u"%1.%2(%3_);"_s .arg( |
| 617 | args&: current.propertyInitializer.component.name, args: property.write(), args: name); |
| 618 | } |
| 619 | |
| 620 | compiledSetter.body << u"%1.insert(QStringLiteral(\"%2\"));"_s .arg( |
| 621 | args&: current.propertyInitializer.initializedCache.name, args: name); |
| 622 | } |
| 623 | } |
| 624 | |
| 625 | void QmltcCompiler::compileTypeElements(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) |
| 626 | { |
| 627 | // compile components of a type: |
| 628 | // - enums |
| 629 | // - properties |
| 630 | // - methods |
| 631 | // - bindings |
| 632 | |
| 633 | const auto enums = type->ownEnumerations(); |
| 634 | current.enums.reserve(asize: enums.size()); |
| 635 | for (auto it = enums.begin(); it != enums.end(); ++it) |
| 636 | compileEnum(current, e: it.value()); |
| 637 | |
| 638 | auto properties = type->ownProperties().values(); |
| 639 | current.properties.reserve(asize: properties.size()); |
| 640 | // Note: index() is the (future) meta property index, so make sure given |
| 641 | // properties are ordered by that index before compiling |
| 642 | std::sort(first: properties.begin(), last: properties.end(), |
| 643 | comp: [](const QQmlJSMetaProperty &x, const QQmlJSMetaProperty &y) { |
| 644 | return x.index() < y.index(); |
| 645 | }); |
| 646 | for (const QQmlJSMetaProperty &p : std::as_const(t&: properties)) { |
| 647 | if (p.index() == -1) { |
| 648 | recordError(location: type->sourceLocation(), |
| 649 | message: u"Internal error: property '%1' has incomplete information"_s .arg( |
| 650 | a: p.propertyName())); |
| 651 | continue; |
| 652 | } |
| 653 | if (p.isAlias()) { |
| 654 | compileAlias(current, alias: p, owner: type); |
| 655 | } else { |
| 656 | compileProperty(current, p, owner: type); |
| 657 | } |
| 658 | } |
| 659 | |
| 660 | const auto methods = type->ownMethods(); |
| 661 | for (const QQmlJSMetaMethod &m : methods) |
| 662 | compileMethod(current, m, owner: type); |
| 663 | |
| 664 | auto bindings = type->ownPropertyBindingsInQmlIROrder(); |
| 665 | partitionBindings(first: bindings.begin(), last: bindings.end()); |
| 666 | |
| 667 | compilePropertyInitializer(current, type); |
| 668 | compileBinding(current, bindingStart: bindings.begin(), bindingEnd: bindings.end(), type, accessor: { .scope: type }); |
| 669 | } |
| 670 | |
| 671 | void QmltcCompiler::compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e) |
| 672 | { |
| 673 | const auto intValues = e.values(); |
| 674 | QStringList values; |
| 675 | values.reserve(asize: intValues.size()); |
| 676 | std::transform(first: intValues.cbegin(), last: intValues.cend(), result: std::back_inserter(x&: values), |
| 677 | unary_op: [](int x) { return QString::number(x); }); |
| 678 | |
| 679 | // structure: (C++ type name, enum keys, enum values, MOC line) |
| 680 | current.enums.emplaceBack(args: e.name(), args: e.keys(), args: std::move(values), |
| 681 | args: u"Q_ENUM(%1)"_s .arg(a: e.name())); |
| 682 | } |
| 683 | |
| 684 | static QList<QmltcVariable> |
| 685 | compileMethodParameters(const QList<QQmlJSMetaParameter> ¶meterInfos, bool allowUnnamed = false) |
| 686 | { |
| 687 | QList<QmltcVariable> parameters; |
| 688 | const auto size = parameterInfos.size(); |
| 689 | parameters.reserve(asize: size); |
| 690 | for (qsizetype i = 0; i < size; ++i) { |
| 691 | const auto &p = parameterInfos[i]; |
| 692 | Q_ASSERT(p.type()); // assume verified |
| 693 | QString name = p.name(); |
| 694 | Q_ASSERT(allowUnnamed || !name.isEmpty()); // assume verified |
| 695 | if (name.isEmpty() && allowUnnamed) |
| 696 | name = u"unnamed_" + QString::number(i); |
| 697 | |
| 698 | QString internalName; |
| 699 | const QQmlJSScope::AccessSemantics semantics = p.type()->accessSemantics(); |
| 700 | |
| 701 | switch (semantics) { |
| 702 | case QQmlJSScope::AccessSemantics::Reference: |
| 703 | if (p.typeQualifier() == QQmlJSMetaParameter::Const) |
| 704 | internalName = u"const "_s ; |
| 705 | internalName += u"%1*"_s .arg(a: p.type()->internalName()); |
| 706 | break; |
| 707 | case QQmlJSScope::AccessSemantics::Value: |
| 708 | case QQmlJSScope::AccessSemantics::Sequence: |
| 709 | internalName = u"passByConstRefOrValue<%1>"_s .arg(a: p.type()->internalName()); |
| 710 | break; |
| 711 | case QQmlJSScope::AccessSemantics::None: |
| 712 | Q_ASSERT(false); // or maybe print an error message |
| 713 | } |
| 714 | parameters.emplaceBack(args&: internalName, args&: name, args: QString()); |
| 715 | } |
| 716 | return parameters; |
| 717 | } |
| 718 | |
| 719 | void QmltcCompiler::compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m, |
| 720 | const QQmlJSScope::ConstPtr &owner) |
| 721 | { |
| 722 | const QString returnType = m.returnType()->augmentedInternalName(); |
| 723 | |
| 724 | const QList<QmltcVariable> compiledParams = compileMethodParameters(parameterInfos: m.parameters()); |
| 725 | const auto methodType = m.methodType(); |
| 726 | |
| 727 | QStringList code; |
| 728 | if (methodType != QQmlJSMetaMethodType::Signal) { |
| 729 | QmltcCodeGenerator urlGenerator { .documentUrl: m_url, .visitor: m_visitor }; |
| 730 | QmltcCodeGenerator::generate_callExecuteRuntimeFunction( |
| 731 | block: &code, url: urlGenerator.urlMethodName() + u"()" , |
| 732 | index: owner->ownRuntimeFunctionIndex(index: m.jsFunctionIndex()), accessor: u"this"_s , returnType, |
| 733 | parameters: compiledParams); |
| 734 | } |
| 735 | |
| 736 | QmltcMethod compiled {}; |
| 737 | compiled.returnType = returnType; |
| 738 | compiled.name = m.methodName(); |
| 739 | compiled.parameterList = std::move(compiledParams); |
| 740 | compiled.body = std::move(code); |
| 741 | compiled.type = methodType; |
| 742 | compiled.access = m.access(); |
| 743 | if (methodType != QQmlJSMetaMethodType::Signal) { |
| 744 | compiled.declarationPrefixes << u"Q_INVOKABLE"_s ; |
| 745 | compiled.userVisible = m.access() == QQmlJSMetaMethod::Public; |
| 746 | } else { |
| 747 | compiled.userVisible = !m.isImplicitQmlPropertyChangeSignal(); |
| 748 | } |
| 749 | current.functions.emplaceBack(args&: compiled); |
| 750 | } |
| 751 | |
| 752 | /*! \internal |
| 753 | Compiles an extra set of methods for Lists, that makes manipulating lists easier from C++ |
| 754 | for the user. |
| 755 | */ |
| 756 | void QmltcCompiler::(QmltcType ¤t, const QQmlJSMetaProperty &p) |
| 757 | { |
| 758 | QmltcPropertyData data(p); |
| 759 | const QString valueType = p.type()->valueType()->internalName() + u'*'; |
| 760 | const QString variableName = data.read + u"()"_s ; |
| 761 | const QStringList ownershipWarning = { |
| 762 | u"\\note {This method does not change the ownership of its argument."_s , |
| 763 | u"The caller is responsible for setting the argument's \\c {QObject::parent} or"_s , |
| 764 | u"for ensuring that the argument lives long enough."_s , |
| 765 | u"For example, an argument created with \\c {createObject()} that has no parent"_s , |
| 766 | u"will eventually be garbage-collected, leaving a dangling pointer.}"_s |
| 767 | }; |
| 768 | |
| 769 | // generate append() sugar for users |
| 770 | { |
| 771 | QmltcMethod append{}; |
| 772 | append.comments.emplaceBack(args: u"\\brief Append an element to %1."_s .arg(a: data.read)); |
| 773 | append.comments << ownershipWarning; |
| 774 | append.returnType = u"void"_s ; |
| 775 | append.name = u"%1Append"_s .arg(a: data.read); |
| 776 | append.parameterList.emplaceBack(args: valueType, args: u"toBeAppended"_s ); |
| 777 | |
| 778 | append.body << u"auto q_qmltc_localList = %1;"_s .arg(a: variableName); |
| 779 | append.body |
| 780 | << u"q_qmltc_localList.append(std::addressof(q_qmltc_localList), toBeAppended);"_s ; |
| 781 | // append.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 is |
| 782 | // resolved |
| 783 | append.userVisible = true; |
| 784 | current.functions.emplaceBack(args&: append); |
| 785 | } |
| 786 | |
| 787 | // generate count() sugar for users |
| 788 | { |
| 789 | QmltcMethod count{}; |
| 790 | count.comments.emplaceBack(args: u"\\brief Number of elements in %1."_s .arg(a: data.read)); |
| 791 | count.returnType = u"int"_s ; |
| 792 | count.name = u"%1Count"_s .arg(a: data.read); |
| 793 | |
| 794 | count.body << u"auto q_qmltc_localList = %1;"_s .arg(a: variableName); |
| 795 | count.body << u"int result = q_qmltc_localList.count(std::addressof(q_qmltc_localList));"_s ; |
| 796 | count.body << u"return result;"_s ; |
| 797 | count.userVisible = true; |
| 798 | current.functions.emplaceBack(args&: count); |
| 799 | } |
| 800 | |
| 801 | // generate at() sugar for users |
| 802 | { |
| 803 | QmltcMethod at{}; |
| 804 | at.comments.emplaceBack(args: u"\\brief Access an element in %1."_s .arg(a: data.read)); |
| 805 | at.returnType = valueType; |
| 806 | at.name = u"%1At"_s .arg(a: data.read); |
| 807 | at.parameterList.emplaceBack(args: u"qsizetype"_s , args: u"position"_s , args: QString()); |
| 808 | |
| 809 | at.body << u"auto q_qmltc_localList = %1;"_s .arg(a: variableName); |
| 810 | at.body << u"auto result = q_qmltc_localList.at(std::addressof(q_qmltc_localList), position);"_s ; |
| 811 | at.body << u"return result;"_s ; |
| 812 | at.userVisible = true; |
| 813 | current.functions.emplaceBack(args&: at); |
| 814 | } |
| 815 | |
| 816 | // generate clear() sugar for users |
| 817 | { |
| 818 | QmltcMethod clear{}; |
| 819 | clear.comments.emplaceBack(args: u"\\brief Clear %1."_s .arg(a: data.read)); |
| 820 | clear.returnType = u"void"_s ; |
| 821 | clear.name = u"%1Clear"_s .arg(a: data.read); |
| 822 | |
| 823 | clear.body << u"auto q_qmltc_localList = %1;"_s .arg(a: variableName); |
| 824 | clear.body << u"q_qmltc_localList.clear(std::addressof(q_qmltc_localList));"_s ; |
| 825 | // clear.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 is |
| 826 | // resolved |
| 827 | clear.userVisible = true; |
| 828 | current.functions.emplaceBack(args&: clear); |
| 829 | } |
| 830 | |
| 831 | // generate replace() sugar for users |
| 832 | { |
| 833 | QmltcMethod replace{}; |
| 834 | replace.comments.emplaceBack(args: u"\\brief Replace an element in %1."_s .arg(a: data.read)); |
| 835 | replace.comments << ownershipWarning; |
| 836 | replace.returnType = u"void"_s ; |
| 837 | replace.name = u"%1Replace"_s .arg(a: data.read); |
| 838 | replace.parameterList.emplaceBack(args: u"qsizetype"_s , args: u"position"_s , args: QString()); |
| 839 | replace.parameterList.emplaceBack(args: valueType, args: u"element"_s , |
| 840 | args: QString()); |
| 841 | |
| 842 | replace.body << u"auto q_qmltc_localList = %1;"_s .arg(a: variableName); |
| 843 | replace.body |
| 844 | << u"q_qmltc_localList.replace(std::addressof(q_qmltc_localList), position, element);"_s ; |
| 845 | // replace.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 |
| 846 | // is resolved |
| 847 | replace.userVisible = true; |
| 848 | current.functions.emplaceBack(args&: replace); |
| 849 | } |
| 850 | |
| 851 | // generate removeLast() sugar for users |
| 852 | { |
| 853 | QmltcMethod removeLast{}; |
| 854 | removeLast.comments.emplaceBack(args: u"\\brief Remove the last element in %1."_s .arg(a: data.read)); |
| 855 | removeLast.returnType = u"void"_s ; |
| 856 | removeLast.name = u"%1RemoveLast"_s .arg(a: data.read); |
| 857 | |
| 858 | removeLast.body << u"auto q_qmltc_localList = %1;"_s .arg(a: variableName); |
| 859 | removeLast.body << u"q_qmltc_localList.removeLast(std::addressof(q_qmltc_localList));"_s ; |
| 860 | // removeLast.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when |
| 861 | // QTBUG-106587 is resolved |
| 862 | |
| 863 | removeLast.userVisible = true; |
| 864 | current.functions.emplaceBack(args&: removeLast); |
| 865 | } |
| 866 | } |
| 867 | |
| 868 | void QmltcCompiler::compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p, |
| 869 | const QQmlJSScope::ConstPtr &owner) |
| 870 | { |
| 871 | Q_ASSERT(!p.isAlias()); // will be handled separately |
| 872 | Q_ASSERT(p.type()); |
| 873 | |
| 874 | const QString name = p.propertyName(); |
| 875 | const QString variableName = u"m_" + name; |
| 876 | const QString underlyingType = getUnderlyingType(p); |
| 877 | if (qIsReferenceTypeList(p)) { |
| 878 | const QString storageName = variableName + u"_storage" ; |
| 879 | current.variables.emplaceBack( |
| 880 | args: u"QList<" + p.type()->valueType()->internalName() + u"*>" , args: storageName, |
| 881 | args: QString()); |
| 882 | current.baselineCtor.initializerList.emplaceBack(args: variableName + u"(" + underlyingType |
| 883 | + u"(this, std::addressof(" + storageName |
| 884 | + u")))" ); |
| 885 | compileExtraListMethods(current, p); |
| 886 | } |
| 887 | |
| 888 | // along with property, also add relevant moc code, so that we can use the |
| 889 | // property in Qt/QML contexts |
| 890 | QStringList mocPieces; |
| 891 | mocPieces.reserve(asize: 10); |
| 892 | mocPieces << underlyingType << name; |
| 893 | |
| 894 | QmltcPropertyData compilationData(p); |
| 895 | |
| 896 | // 1. add setter and getter |
| 897 | // If p.isList(), it's a QQmlListProperty. Then you can write the underlying list through |
| 898 | // the QQmlListProperty object retrieved with the getter. Setting it would make no sense. |
| 899 | QmltcMethod getter{}; |
| 900 | getter.returnType = underlyingType; |
| 901 | getter.name = compilationData.read; |
| 902 | getter.body << u"return " + variableName + u".value();" ; |
| 903 | getter.userVisible = true; |
| 904 | current.functions.emplaceBack(args&: getter); |
| 905 | mocPieces << u"READ"_s << getter.name; |
| 906 | |
| 907 | if (p.isWritable() && !qIsReferenceTypeList(p)) { |
| 908 | QmltcMethod setter {}; |
| 909 | setter.returnType = u"void"_s ; |
| 910 | setter.name = compilationData.write; |
| 911 | // QmltcVariable |
| 912 | setter.parameterList.emplaceBack(args: QQmlJSUtils::constRefify(type: underlyingType), args: name + u"_" , |
| 913 | args: u""_s ); |
| 914 | setter.body << variableName + u".setValue(" + name + u"_);" ; |
| 915 | setter.body << u"Q_EMIT " + compilationData.notify + u"();" ; |
| 916 | setter.userVisible = true; |
| 917 | current.functions.emplaceBack(args&: setter); |
| 918 | mocPieces << u"WRITE"_s << setter.name; |
| 919 | } |
| 920 | |
| 921 | // 2. add bindable |
| 922 | if (!qIsReferenceTypeList(p)) { |
| 923 | QmltcMethod bindable {}; |
| 924 | bindable.returnType = u"QBindable<" + underlyingType + u">" ; |
| 925 | bindable.name = compilationData.bindable; |
| 926 | bindable.body << u"return QBindable<" + underlyingType + u">(std::addressof(" + variableName |
| 927 | + u"));" ; |
| 928 | bindable.userVisible = true; |
| 929 | current.functions.emplaceBack(args&: bindable); |
| 930 | mocPieces << u"BINDABLE"_s << bindable.name; |
| 931 | } |
| 932 | |
| 933 | // 3. add/check notify (actually, this is already done inside QmltcVisitor) |
| 934 | |
| 935 | if (owner->isPropertyRequired(name)) |
| 936 | mocPieces << u"REQUIRED"_s ; |
| 937 | |
| 938 | // 4. add moc entry |
| 939 | // e.g. Q_PROPERTY(QString p READ getP WRITE setP BINDABLE bindableP) |
| 940 | current.mocCode << u"Q_PROPERTY(" + mocPieces.join(sep: u" "_s ) + u")" ; |
| 941 | |
| 942 | // 5. add extra moc entry if this property is marked default |
| 943 | if (name == owner->defaultPropertyName()) |
| 944 | current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s .arg(a: name); |
| 945 | |
| 946 | // structure: (C++ type name, name, C++ class name, C++ signal name) |
| 947 | current.properties.emplaceBack(args: underlyingType, args: variableName, args&: current.cppType, |
| 948 | args&: compilationData.notify); |
| 949 | } |
| 950 | |
| 951 | /*! |
| 952 | * \internal |
| 953 | * |
| 954 | * Models one step of the alias resolution. If the current alias to be resolved |
| 955 | * points to \c {x.y.z} and that \c {x.y} is already resolved, then this struct |
| 956 | * contains the information on how to obtain the \c {z} part from \c {x.y}. |
| 957 | */ |
| 958 | struct AliasResolutionFrame |
| 959 | { |
| 960 | /*! |
| 961 | * \internal |
| 962 | * |
| 963 | * Placeholder for the current resolved state. It is replaced later with |
| 964 | * the result from previous resolutions from the \c QStack<AliasResolutionFrame>. |
| 965 | * |
| 966 | * \sa unpackFrames() |
| 967 | */ |
| 968 | static QString inVar; |
| 969 | |
| 970 | /*! |
| 971 | * \internal |
| 972 | * |
| 973 | * Steps to access this value as a list of C++ statements, to be used in |
| 974 | * conjunction with \c {epilogue}. |
| 975 | */ |
| 976 | QStringList prologue; |
| 977 | |
| 978 | /*! |
| 979 | * \internal |
| 980 | * |
| 981 | * Steps to finish the statements of the \c prologue (e.g. closing brackets). |
| 982 | */ |
| 983 | QStringList epilogue; |
| 984 | |
| 985 | /*! |
| 986 | * \internal |
| 987 | * |
| 988 | * Instructions on how to write the property, after it was loaded with the |
| 989 | * instructions from \c prologue. Has to happen before \c epilogue. |
| 990 | */ |
| 991 | QStringList epilogueForWrite; |
| 992 | |
| 993 | /*! |
| 994 | * \internal |
| 995 | * |
| 996 | * Name of the variable holding the result of this resolution step, to be |
| 997 | * used in the following resolution steps. |
| 998 | */ |
| 999 | QString outVar; |
| 1000 | }; |
| 1001 | // special string replaced by outVar of the previous frame |
| 1002 | QString AliasResolutionFrame::inVar = QStringLiteral("__QMLTC_ALIAS_FRAME_INPUT_VAR__" ); |
| 1003 | |
| 1004 | /*! |
| 1005 | * \internal |
| 1006 | * |
| 1007 | * Process the frames by replacing the placeholder \c invar |
| 1008 | * used in \c epilogueForWrite and \c prologue with the result |
| 1009 | * obtained from the previous frame. |
| 1010 | */ |
| 1011 | static void unpackFrames(QStack<AliasResolutionFrame> &frames) |
| 1012 | { |
| 1013 | if (frames.size() < 2) |
| 1014 | return; |
| 1015 | |
| 1016 | // assume first frame is fine |
| 1017 | auto prev = frames.begin(); |
| 1018 | for (auto it = std::next(x: prev); it != frames.end(); ++it, ++prev) { |
| 1019 | for (QString &line : it->prologue) |
| 1020 | line.replace(before: AliasResolutionFrame::inVar, after: prev->outVar); |
| 1021 | for (QString &line : it->epilogueForWrite) |
| 1022 | line.replace(before: AliasResolutionFrame::inVar, after: prev->outVar); |
| 1023 | it->outVar.replace(before: AliasResolutionFrame::inVar, after: prev->outVar); |
| 1024 | } |
| 1025 | } |
| 1026 | |
| 1027 | template<typename Projection> |
| 1028 | static QStringList joinFrames(const QStack<AliasResolutionFrame> &frames, Projection project) |
| 1029 | { |
| 1030 | QStringList joined; |
| 1031 | for (const AliasResolutionFrame &frame : frames) |
| 1032 | joined += project(frame); |
| 1033 | return joined; |
| 1034 | } |
| 1035 | |
| 1036 | void QmltcCompiler::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &alias, |
| 1037 | const QQmlJSScope::ConstPtr &owner) |
| 1038 | { |
| 1039 | const QString aliasName = alias.propertyName(); |
| 1040 | Q_ASSERT(!aliasName.isEmpty()); |
| 1041 | |
| 1042 | QStringList aliasExprBits = alias.aliasExpression().split(sep: u'.'); |
| 1043 | Q_ASSERT(!aliasExprBits.isEmpty()); |
| 1044 | |
| 1045 | QStack<AliasResolutionFrame> frames; |
| 1046 | |
| 1047 | QQmlJSUtils::AliasResolutionVisitor aliasVisitor; |
| 1048 | qsizetype i = 0; |
| 1049 | aliasVisitor.reset = [&]() { |
| 1050 | frames.clear(); |
| 1051 | i = 0; // we use it in property processing |
| 1052 | |
| 1053 | // first frame is a dummy one: |
| 1054 | frames.push( |
| 1055 | t: AliasResolutionFrame { .prologue: QStringList(), .epilogue: QStringList(), .epilogueForWrite: QStringList(), .outVar: u"this"_s }); |
| 1056 | }; |
| 1057 | aliasVisitor.processResolvedId = [&](const QQmlJSScope::ConstPtr &type) { |
| 1058 | Q_ASSERT(type); |
| 1059 | if (owner != type) { // cannot start at `this`, need to fetch object through context |
| 1060 | const int id = m_visitor->runtimeId(type); |
| 1061 | Q_ASSERT(id >= 0); // since the type is found by id, it must have an id |
| 1062 | |
| 1063 | AliasResolutionFrame queryIdFrame {}; |
| 1064 | Q_ASSERT(frames.top().outVar == u"this"_s ); // so inVar would be "this" as well |
| 1065 | queryIdFrame.prologue << u"auto context = %1::q_qmltc_thisContext;"_s .arg( |
| 1066 | a: owner->internalName()); |
| 1067 | |
| 1068 | // doing the above allows us to lookup id object by index (fast) |
| 1069 | queryIdFrame.outVar = u"alias_objectById_" + aliasExprBits.front(); // unique enough |
| 1070 | const QString cppType = (m_visitor->qmlComponentIndex(type) == -1) |
| 1071 | ? type->internalName() |
| 1072 | : u"QQmlComponent"_s ; |
| 1073 | queryIdFrame.prologue << u"auto " + queryIdFrame.outVar + u" = static_cast<" + cppType |
| 1074 | + u"*>(context->idValue(" + QString::number(id) + u"));" ; |
| 1075 | queryIdFrame.prologue << u"Q_ASSERT(" + queryIdFrame.outVar + u");" ; |
| 1076 | |
| 1077 | frames.push(t: queryIdFrame); |
| 1078 | } |
| 1079 | }; |
| 1080 | aliasVisitor.processResolvedProperty = [&](const QQmlJSMetaProperty &p, |
| 1081 | const QQmlJSScope::ConstPtr &owner) { |
| 1082 | AliasResolutionFrame queryPropertyFrame {}; |
| 1083 | |
| 1084 | auto [extensionPrologue, extensionAccessor, extensionEpilogue] = |
| 1085 | QmltcCodeGenerator::wrap_extensionType( |
| 1086 | type: owner, p, |
| 1087 | accessor: QmltcCodeGenerator::wrap_privateClass(accessor: AliasResolutionFrame::inVar, p)); |
| 1088 | QString inVar = extensionAccessor; |
| 1089 | queryPropertyFrame.prologue += extensionPrologue; |
| 1090 | if (p.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Value) { |
| 1091 | // we need to read the property to a local variable and then |
| 1092 | // write the updated value once the actual operation is done |
| 1093 | const QString aliasVar = u"alias_" + QString::number(i); // should be fairly unique |
| 1094 | ++i; |
| 1095 | queryPropertyFrame.prologue |
| 1096 | << u"auto " + aliasVar + u" = " + inVar + u"->" + p.read() + u"();" ; |
| 1097 | queryPropertyFrame.epilogueForWrite |
| 1098 | << inVar + u"->" + p.write() + u"(" + aliasVar + u");" ; |
| 1099 | // NB: since accessor becomes a value type, wrap it into an |
| 1100 | // addressof operator so that we could access it as a pointer |
| 1101 | inVar = QmltcCodeGenerator::wrap_addressof(addressed: aliasVar); // reset |
| 1102 | } else { |
| 1103 | inVar += u"->" + p.read() + u"()" ; // update |
| 1104 | } |
| 1105 | queryPropertyFrame.outVar = inVar; |
| 1106 | queryPropertyFrame.epilogue += extensionEpilogue; |
| 1107 | |
| 1108 | frames.push(t: queryPropertyFrame); |
| 1109 | }; |
| 1110 | |
| 1111 | QQmlJSUtils::ResolvedAlias result = |
| 1112 | QQmlJSUtils::resolveAlias(typeResolver: m_typeResolver, property: alias, owner, visitor: aliasVisitor); |
| 1113 | Q_ASSERT(result.kind != QQmlJSUtils::AliasTarget_Invalid); |
| 1114 | |
| 1115 | unpackFrames(frames); |
| 1116 | |
| 1117 | if (result.kind == QQmlJSUtils::AliasTarget_Property) { |
| 1118 | // we don't need the last frame here |
| 1119 | frames.pop(); |
| 1120 | |
| 1121 | // instead, add a custom frame |
| 1122 | AliasResolutionFrame customFinalFrame {}; |
| 1123 | auto [extensionPrologue, extensionAccessor, extensionEpilogue] = |
| 1124 | QmltcCodeGenerator::wrap_extensionType( |
| 1125 | type: result.owner, p: result.property, |
| 1126 | accessor: QmltcCodeGenerator::wrap_privateClass(accessor: frames.top().outVar, |
| 1127 | p: result.property)); |
| 1128 | customFinalFrame.prologue = extensionPrologue; |
| 1129 | customFinalFrame.outVar = extensionAccessor; |
| 1130 | customFinalFrame.epilogue = extensionEpilogue; |
| 1131 | frames.push(t: customFinalFrame); |
| 1132 | } |
| 1133 | |
| 1134 | const QString latestAccessor = frames.top().outVar; |
| 1135 | const QStringList prologue = |
| 1136 | joinFrames(frames, project: [](const AliasResolutionFrame &frame) { return frame.prologue; }); |
| 1137 | const QStringList epilogue = |
| 1138 | joinFrames(frames, project: [](const AliasResolutionFrame &frame) { return frame.epilogue; }); |
| 1139 | const QString underlyingType = (result.kind == QQmlJSUtils::AliasTarget_Property) |
| 1140 | ? getUnderlyingType(p: result.property) |
| 1141 | : result.owner->internalName() + u" *" ; |
| 1142 | |
| 1143 | QStringList mocLines; |
| 1144 | mocLines.reserve(asize: 10); |
| 1145 | mocLines << underlyingType << aliasName; |
| 1146 | |
| 1147 | QmltcPropertyData compilationData(aliasName); |
| 1148 | // 1. add setter and getter |
| 1149 | QmltcMethod getter {}; |
| 1150 | getter.returnType = underlyingType; |
| 1151 | getter.name = compilationData.read; |
| 1152 | getter.body += prologue; |
| 1153 | if (result.kind == QQmlJSUtils::AliasTarget_Property) { |
| 1154 | if (QString read = result.property.read(); !read.isEmpty() |
| 1155 | && !QQmlJSUtils::bindablePropertyHasDefaultAccessor( |
| 1156 | p: result.property, accessor: QQmlJSUtils::PropertyAccessor_Read)) { |
| 1157 | getter.body << u"return %1->%2();"_s .arg(args: latestAccessor, args&: read); |
| 1158 | } else { // use QObject::property() as a fallback when read method is unknown |
| 1159 | getter.body << u"return qvariant_cast<%1>(%2->property(\"%3\"));"_s .arg( |
| 1160 | args: underlyingType, args: latestAccessor, args: result.property.propertyName()); |
| 1161 | } |
| 1162 | } else { // AliasTarget_Object |
| 1163 | getter.body << u"return " + latestAccessor + u";" ; |
| 1164 | } |
| 1165 | getter.body += epilogue; |
| 1166 | getter.userVisible = true; |
| 1167 | current.functions.emplaceBack(args&: getter); |
| 1168 | mocLines << u"READ"_s << getter.name; |
| 1169 | |
| 1170 | if (result.property.isWritable()) { |
| 1171 | Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise |
| 1172 | QmltcMethod setter {}; |
| 1173 | setter.returnType = u"void"_s ; |
| 1174 | setter.name = compilationData.write; |
| 1175 | |
| 1176 | const QString setName = result.property.write(); |
| 1177 | QList<QQmlJSMetaMethod> methods = result.owner->methods(name: setName); |
| 1178 | if (methods.isEmpty()) { // when we are compiling the property as well |
| 1179 | // QmltcVariable |
| 1180 | setter.parameterList.emplaceBack(args: QQmlJSUtils::constRefify(type: underlyingType), |
| 1181 | args: aliasName + u"_" , args: u""_s ); |
| 1182 | } else { |
| 1183 | setter.parameterList = compileMethodParameters(parameterInfos: methods.at(i: 0).parameters(), |
| 1184 | /* allow unnamed = */ allowUnnamed: true); |
| 1185 | } |
| 1186 | |
| 1187 | setter.body += prologue; |
| 1188 | QStringList parameterNames; |
| 1189 | parameterNames.reserve(asize: setter.parameterList.size()); |
| 1190 | std::transform(first: setter.parameterList.cbegin(), last: setter.parameterList.cend(), |
| 1191 | result: std::back_inserter(x&: parameterNames), |
| 1192 | unary_op: [](const QmltcVariable &x) { return x.name; }); |
| 1193 | QString commaSeparatedParameterNames = parameterNames.join(sep: u", "_s ); |
| 1194 | if (!setName.isEmpty() |
| 1195 | && !QQmlJSUtils::bindablePropertyHasDefaultAccessor( |
| 1196 | p: result.property, accessor: QQmlJSUtils::PropertyAccessor_Write)) { |
| 1197 | setter.body << u"%1->%2(%3);"_s .arg(args: latestAccessor, args: setName, |
| 1198 | args&: commaSeparatedParameterNames); |
| 1199 | } else { // use QObject::setProperty() as fallback when write method is unknown |
| 1200 | Q_ASSERT(parameterNames.size() == 1); |
| 1201 | const QString variantName = u"var_" + aliasName; // fairly unique |
| 1202 | setter.body << u"QVariant %1;"_s .arg(a: variantName); |
| 1203 | setter.body << u"%1.setValue(%2);"_s .arg(args: variantName, args&: commaSeparatedParameterNames); |
| 1204 | setter.body << u"%1->setProperty(\"%2\", std::move(%3));"_s .arg( |
| 1205 | args: latestAccessor, args: result.property.propertyName(), args: variantName); |
| 1206 | } |
| 1207 | setter.body += joinFrames( |
| 1208 | frames, project: [](const AliasResolutionFrame &frame) { return frame.epilogueForWrite; }); |
| 1209 | setter.body += epilogue; // NB: *after* epilogueForWrite - see prologue construction |
| 1210 | setter.userVisible = true; |
| 1211 | current.functions.emplaceBack(args&: setter); |
| 1212 | mocLines << u"WRITE"_s << setter.name; |
| 1213 | } |
| 1214 | // 2. add bindable |
| 1215 | if (QString bindableName = result.property.bindable(); !bindableName.isEmpty()) { |
| 1216 | Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise |
| 1217 | QmltcMethod bindable {}; |
| 1218 | bindable.returnType = u"QBindable<" + underlyingType + u">" ; |
| 1219 | bindable.name = compilationData.bindable; |
| 1220 | bindable.body += prologue; |
| 1221 | bindable.body << u"return " + latestAccessor + u"->" + bindableName + u"()" + u";" ; |
| 1222 | bindable.body += epilogue; |
| 1223 | bindable.userVisible = true; |
| 1224 | current.functions.emplaceBack(args&: bindable); |
| 1225 | mocLines << u"BINDABLE"_s << bindable.name; |
| 1226 | } |
| 1227 | |
| 1228 | // 3. add notify - which is pretty special |
| 1229 | // step 1: generate the moc instructions |
| 1230 | // mimic the engines behavior: do it even if the notify will never be emitted |
| 1231 | if (const QString aliasNotifyName = alias.notify(); !aliasNotifyName.isEmpty()) { |
| 1232 | |
| 1233 | Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise |
| 1234 | |
| 1235 | mocLines << u"NOTIFY"_s << aliasNotifyName; |
| 1236 | } |
| 1237 | |
| 1238 | // step 2: connect the notifier to the aliased property notifier, if this latter exists |
| 1239 | // otherwise, mimic the engines behavior and generate a useless notify |
| 1240 | if (const QString notifyName = result.property.notify(); !notifyName.isEmpty()) { |
| 1241 | auto notifyFrames = frames; |
| 1242 | notifyFrames.pop(); // we don't need the last frame at all in this case |
| 1243 | |
| 1244 | const QStringList notifyPrologue = joinFrames( |
| 1245 | frames, project: [](const AliasResolutionFrame &frame) { return frame.prologue; }); |
| 1246 | const QStringList notifyEpilogue = joinFrames( |
| 1247 | frames, project: [](const AliasResolutionFrame &frame) { return frame.epilogue; }); |
| 1248 | |
| 1249 | // notify is very special |
| 1250 | current.endInit.body << u"{ // alias notify connection:"_s ; |
| 1251 | current.endInit.body += notifyPrologue; |
| 1252 | // TODO: use non-private accessor since signals must exist on the public |
| 1253 | // type, not on the private one -- otherwise, you can't connect to a |
| 1254 | // private property signal in C++ and so it is useless (hence, use |
| 1255 | // public type) |
| 1256 | const QString cppType = (m_visitor->qmlComponentIndex(type: result.owner) == -1) |
| 1257 | ? result.owner->internalName() |
| 1258 | : u"QQmlComponent"_s ; |
| 1259 | const QString latestAccessorNonPrivate = notifyFrames.top().outVar; |
| 1260 | current.endInit.body << u"QObject::connect(" + latestAccessorNonPrivate + u", &" + cppType |
| 1261 | + u"::" + notifyName + u", this, &" + current.cppType + u"::" |
| 1262 | + compilationData.notify + u");" ; |
| 1263 | current.endInit.body += notifyEpilogue; |
| 1264 | current.endInit.body << u"}"_s ; |
| 1265 | } |
| 1266 | |
| 1267 | if (QString resetName = result.property.reset(); !resetName.isEmpty()) { |
| 1268 | Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise |
| 1269 | QmltcMethod reset {}; |
| 1270 | reset.returnType = u"void"_s ; |
| 1271 | reset.name = compilationData.reset; |
| 1272 | reset.body += prologue; |
| 1273 | reset.body << latestAccessor + u"->" + resetName + u"()" + u";" ; |
| 1274 | reset.body += epilogue; |
| 1275 | reset.userVisible = true; |
| 1276 | current.functions.emplaceBack(args&: reset); |
| 1277 | mocLines << u"RESET"_s << reset.name; |
| 1278 | } |
| 1279 | |
| 1280 | // mimic the engines behavior: aliases are never constants |
| 1281 | // mocLines << u"CONSTANT"_s; |
| 1282 | // mimic the engines behavior: aliases are never stored |
| 1283 | mocLines << u"STORED"_s << u"false"_s ; |
| 1284 | // mimic the engines behavior: aliases are never designable |
| 1285 | mocLines << u"DESIGNABLE"_s << u"false"_s ; |
| 1286 | |
| 1287 | // 4. add moc entry |
| 1288 | // Q_PROPERTY(QString text READ text WRITE setText BINDABLE bindableText NOTIFY textChanged) |
| 1289 | current.mocCode << u"Q_PROPERTY(" + mocLines.join(sep: u" "_s ) + u")" ; |
| 1290 | |
| 1291 | // 5. add extra moc entry if this alias is default one |
| 1292 | if (aliasName == owner->defaultPropertyName()) { |
| 1293 | // Q_CLASSINFO("DefaultProperty", propertyName) |
| 1294 | current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s .arg(a: aliasName); |
| 1295 | } |
| 1296 | } |
| 1297 | |
| 1298 | static QString generate_callCompilationUnit(const QString &urlMethodName) |
| 1299 | { |
| 1300 | return u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(%1())"_s .arg(a: urlMethodName); |
| 1301 | } |
| 1302 | |
| 1303 | static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope, |
| 1304 | const QString &propertyName); |
| 1305 | |
| 1306 | /*! |
| 1307 | * \internal |
| 1308 | * Helper method used to keep compileBindingByType() readable. |
| 1309 | */ |
| 1310 | void QmltcCompiler::compileObjectBinding(QmltcType ¤t, |
| 1311 | const QQmlJSMetaPropertyBinding &binding, |
| 1312 | const QQmlJSScope::ConstPtr &type, |
| 1313 | const BindingAccessorData &accessor) |
| 1314 | { |
| 1315 | Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Object); |
| 1316 | |
| 1317 | const QString &propertyName = binding.propertyName(); |
| 1318 | const QQmlJSMetaProperty property = type->property(name: propertyName); |
| 1319 | QQmlJSScope::ConstPtr propertyType = property.type(); |
| 1320 | |
| 1321 | // NB: object is compiled with compileType(), here just need to use it |
| 1322 | auto object = binding.objectType(); |
| 1323 | |
| 1324 | // Note: despite a binding being set for `accessor`, we use "this" as a |
| 1325 | // parent of a created object. Both attached and grouped properties are |
| 1326 | // parented by "this", so lifetime-wise we should be fine |
| 1327 | const QString qobjectParent = u"this"_s ; |
| 1328 | |
| 1329 | if (!propertyType) { |
| 1330 | recordError(location: binding.sourceLocation(), |
| 1331 | message: u"Binding on property '" + propertyName + u"' of unknown type" ); |
| 1332 | return; |
| 1333 | } |
| 1334 | |
| 1335 | const auto addObjectBinding = [&](const QString &value) { |
| 1336 | if (qIsReferenceTypeList(p: property)) { |
| 1337 | Q_ASSERT(unprocessedListProperty == property || unprocessedListBindings.empty()); |
| 1338 | unprocessedListBindings.append(t: value); |
| 1339 | unprocessedListProperty = property; |
| 1340 | } else { |
| 1341 | QmltcCodeGenerator::generate_assignToProperty(block: ¤t.endInit.body, type, p: property, |
| 1342 | value, accessor: accessor.name, constructFromQObject: true); |
| 1343 | } |
| 1344 | }; |
| 1345 | |
| 1346 | // special case of implicit or explicit component: |
| 1347 | if (qsizetype index = m_visitor->qmlComponentIndex(type: object); index >= 0) { |
| 1348 | const QString objectName = newSymbol(base: u"sc"_s ); |
| 1349 | |
| 1350 | const qsizetype creationIndex = m_visitor->creationIndex(type: object); |
| 1351 | |
| 1352 | QStringList *block = (creationIndex == -1) ? ¤t.endInit.body : ¤t.init.body; |
| 1353 | *block << u"{"_s ; |
| 1354 | *block << QStringLiteral("auto thisContext = QQmlData::get(%1)->outerContext;" ) |
| 1355 | .arg(a: qobjectParent); |
| 1356 | *block << QStringLiteral("auto %1 = QQmlObjectCreator::createComponent(engine, " |
| 1357 | "%2, %3, %4, thisContext);" ) |
| 1358 | .arg(args: objectName, args: generate_callCompilationUnit(urlMethodName: m_urlMethodName), |
| 1359 | args: QString::number(index), args: qobjectParent); |
| 1360 | *block << QStringLiteral("thisContext->installContext(QQmlData::get(%1), " |
| 1361 | "QQmlContextData::OrdinaryObject);" ) |
| 1362 | .arg(a: objectName); |
| 1363 | |
| 1364 | // objects wrapped in implicit components do not have visible ids, |
| 1365 | // however, explicit components can have an id and that one is going |
| 1366 | // to be visible in the common document context |
| 1367 | if (creationIndex != -1) { |
| 1368 | // explicit component |
| 1369 | Q_ASSERT(object->isComposite()); |
| 1370 | Q_ASSERT(object->baseType()->internalName() == u"QQmlComponent"_s ); |
| 1371 | |
| 1372 | if (int id = m_visitor->runtimeId(type: object); id >= 0) { |
| 1373 | QString idString = m_visitor->addressableScopes().id(scope: object, referrer: object); |
| 1374 | if (idString.isEmpty()) |
| 1375 | idString = u"<unknown>"_s ; |
| 1376 | QmltcCodeGenerator::generate_setIdValue(block, context: u"thisContext"_s , index: id, accessor: objectName, |
| 1377 | idString); |
| 1378 | } |
| 1379 | |
| 1380 | const QString creationIndexStr = QString::number(creationIndex); |
| 1381 | *block << QStringLiteral("creator->set(%1, %2);" ).arg(args: creationIndexStr, args: objectName); |
| 1382 | Q_ASSERT(block == ¤t.init.body); |
| 1383 | current.endInit.body << QStringLiteral("auto %1 = creator->get<%2>(%3);" ) |
| 1384 | .arg(args: objectName, args: u"QQmlComponent"_s , args: creationIndexStr); |
| 1385 | } |
| 1386 | addObjectBinding(objectName); |
| 1387 | *block << u"}"_s ; |
| 1388 | return; |
| 1389 | } |
| 1390 | |
| 1391 | const QString objectName = newSymbol(base: u"o"_s ); |
| 1392 | current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s .arg( |
| 1393 | args: objectName, args: object->internalName(), args: qobjectParent); |
| 1394 | current.init.body << u"creator->set(%1, %2);"_s .arg( |
| 1395 | args: QString::number(m_visitor->creationIndex(type: object)), args: objectName); |
| 1396 | |
| 1397 | // refetch the same object during endInit to set the bindings |
| 1398 | current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s .arg( |
| 1399 | args: objectName, args: object->internalName(), args: QString::number(m_visitor->creationIndex(type: object))); |
| 1400 | addObjectBinding(objectName); |
| 1401 | } |
| 1402 | |
| 1403 | /*! |
| 1404 | * \internal |
| 1405 | * Helper method used to keep compileBindingByType() readable. |
| 1406 | */ |
| 1407 | void QmltcCompiler::compileValueSourceOrInterceptorBinding(QmltcType ¤t, |
| 1408 | const QQmlJSMetaPropertyBinding &binding, |
| 1409 | const QQmlJSScope::ConstPtr &type, |
| 1410 | const BindingAccessorData &accessor) |
| 1411 | { |
| 1412 | Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::ValueSource |
| 1413 | || binding.bindingType() == QQmlSA::BindingType::Interceptor); |
| 1414 | |
| 1415 | const QString &propertyName = binding.propertyName(); |
| 1416 | const QQmlJSMetaProperty property = type->property(name: propertyName); |
| 1417 | QQmlJSScope::ConstPtr propertyType = property.type(); |
| 1418 | |
| 1419 | // NB: object is compiled with compileType(), here just need to use it |
| 1420 | QSharedPointer<const QQmlJSScope> object; |
| 1421 | if (binding.bindingType() == QQmlSA::BindingType::Interceptor) |
| 1422 | object = binding.interceptorType(); |
| 1423 | else |
| 1424 | object = binding.valueSourceType(); |
| 1425 | |
| 1426 | // Note: despite a binding being set for `accessor`, we use "this" as a |
| 1427 | // parent of a created object. Both attached and grouped properties are |
| 1428 | // parented by "this", so lifetime-wise we should be fine |
| 1429 | const QString qobjectParent = u"this"_s ; |
| 1430 | |
| 1431 | if (!propertyType) { |
| 1432 | recordError(location: binding.sourceLocation(), |
| 1433 | message: u"Binding on property '" + propertyName + u"' of unknown type" ); |
| 1434 | return; |
| 1435 | } |
| 1436 | |
| 1437 | auto &objectName = m_uniques[UniqueStringId(current, propertyName)].onAssignmentObjectName; |
| 1438 | if (objectName.isEmpty()) { |
| 1439 | objectName = u"onAssign_" + propertyName; |
| 1440 | |
| 1441 | current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s .arg( |
| 1442 | args&: objectName, args: object->internalName(), args: qobjectParent); |
| 1443 | current.init.body << u"creator->set(%1, %2);"_s .arg( |
| 1444 | args: QString::number(m_visitor->creationIndex(type: object)), args&: objectName); |
| 1445 | |
| 1446 | current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s .arg( |
| 1447 | args&: objectName, args: object->internalName(), |
| 1448 | args: QString::number(m_visitor->creationIndex(type: object))); |
| 1449 | } |
| 1450 | |
| 1451 | // NB: we expect one "on" assignment per property, so creating |
| 1452 | // QQmlProperty each time should be fine (unlike QQmlListReference) |
| 1453 | current.endInit.body << u"{"_s ; |
| 1454 | current.endInit.body << u"QQmlProperty qmlprop(%1, %2);"_s .arg( |
| 1455 | args: accessor.name, args: QQmlJSUtils::toLiteral(s: propertyName)); |
| 1456 | current.endInit.body << u"QT_PREPEND_NAMESPACE(QQmlCppOnAssignmentHelper)::set(%1, qmlprop);"_s |
| 1457 | .arg(a: objectName); |
| 1458 | current.endInit.body << u"}"_s ; |
| 1459 | } |
| 1460 | |
| 1461 | /*! |
| 1462 | * \internal |
| 1463 | * Helper method used to keep compileBindingByType() readable. |
| 1464 | */ |
| 1465 | void QmltcCompiler::compileAttachedPropertyBinding(QmltcType ¤t, |
| 1466 | const QQmlJSMetaPropertyBinding &binding, |
| 1467 | const QQmlJSScope::ConstPtr &type, |
| 1468 | const BindingAccessorData &accessor) |
| 1469 | { |
| 1470 | Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::AttachedProperty); |
| 1471 | |
| 1472 | const QString &propertyName = binding.propertyName(); |
| 1473 | const QQmlJSMetaProperty property = type->property(name: propertyName); |
| 1474 | QQmlJSScope::ConstPtr propertyType = property.type(); |
| 1475 | |
| 1476 | Q_ASSERT(accessor.name == u"this"_s ); // doesn't have to hold, in fact |
| 1477 | const auto attachedType = binding.attachedType(); |
| 1478 | Q_ASSERT(attachedType); |
| 1479 | |
| 1480 | const QString attachingTypeName = propertyName; // acts as an identifier |
| 1481 | auto attachingType = m_typeResolver->typeForName(name: attachingTypeName); |
| 1482 | |
| 1483 | QString attachedTypeName = attachedType->baseTypeName(); |
| 1484 | Q_ASSERT(!attachedTypeName.isEmpty()); |
| 1485 | |
| 1486 | auto &attachedMemberName = |
| 1487 | m_uniques[UniqueStringId(current, propertyName)].attachedVariableName; |
| 1488 | if (attachedMemberName.isEmpty()) { |
| 1489 | attachedMemberName = uniqueVariableName(qmlName: attachingTypeName); |
| 1490 | |
| 1491 | // add attached type as a member variable to allow noop lookup |
| 1492 | current.variables.emplaceBack(args: attachedTypeName + u" *" , args&: attachedMemberName, args: u"nullptr"_s ); |
| 1493 | |
| 1494 | if (propertyName == u"Component"_s ) { // Component attached type is special |
| 1495 | current.endInit.body << u"Q_ASSERT(qmlEngine(this));"_s ; |
| 1496 | current.endInit.body |
| 1497 | << u"// attached Component must be added to the object's QQmlData"_s ; |
| 1498 | current.endInit.body |
| 1499 | << u"Q_ASSERT(!QQmlEnginePrivate::get(qmlEngine(this))->activeObjectCreator);"_s ; |
| 1500 | } |
| 1501 | |
| 1502 | // Note: getting attached property is fairly expensive |
| 1503 | const QString getAttachedPropertyLine = u"qobject_cast<" + attachedTypeName |
| 1504 | + u" *>(qmlAttachedPropertiesObject<" + attachingType->internalName() |
| 1505 | + u">(this, /* create = */ true))" ; |
| 1506 | current.endInit.body << attachedMemberName + u" = " + getAttachedPropertyLine + u";" ; |
| 1507 | |
| 1508 | if (propertyName == u"Component"_s ) { |
| 1509 | // call completed/destruction signals appropriately |
| 1510 | current.handleOnCompleted.body << u"Q_EMIT " + attachedMemberName + u"->completed();" ; |
| 1511 | if (!current.dtor) { |
| 1512 | current.dtor = QmltcDtor{}; |
| 1513 | current.dtor->name = u"~" + current.cppType; |
| 1514 | } |
| 1515 | current.dtor->body << u"Q_EMIT " + attachedMemberName + u"->destruction();" ; |
| 1516 | } |
| 1517 | } |
| 1518 | |
| 1519 | auto subbindings = attachedType->ownPropertyBindingsInQmlIROrder(); |
| 1520 | // compile bindings of the attached property |
| 1521 | partitionBindings(first: subbindings.begin(), last: subbindings.end()); |
| 1522 | compileBinding(current, bindingStart: subbindings.begin(), bindingEnd: subbindings.end(), type: attachedType, |
| 1523 | accessor: { .scope: type, .name: attachedMemberName, .propertyName: propertyName, .isValueType: false }); |
| 1524 | } |
| 1525 | |
| 1526 | /*! |
| 1527 | * \internal |
| 1528 | * Helper method used to keep compileBindingByType() readable. |
| 1529 | */ |
| 1530 | void QmltcCompiler::compileGroupPropertyBinding(QmltcType ¤t, |
| 1531 | const QQmlJSMetaPropertyBinding &binding, |
| 1532 | const QQmlJSScope::ConstPtr &type, |
| 1533 | const BindingAccessorData &accessor) |
| 1534 | { |
| 1535 | Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::GroupProperty); |
| 1536 | |
| 1537 | const QString &propertyName = binding.propertyName(); |
| 1538 | const QQmlJSMetaProperty property = type->property(name: propertyName); |
| 1539 | QQmlJSScope::ConstPtr propertyType = property.type(); |
| 1540 | |
| 1541 | Q_ASSERT(accessor.name == u"this"_s ); // doesn't have to hold, in fact |
| 1542 | if (property.read().isEmpty()) { |
| 1543 | recordError(location: binding.sourceLocation(), |
| 1544 | message: u"READ function of group property '" + propertyName + u"' is unknown" ); |
| 1545 | return; |
| 1546 | } |
| 1547 | |
| 1548 | auto groupType = binding.groupType(); |
| 1549 | Q_ASSERT(groupType); |
| 1550 | |
| 1551 | const bool isValueType = propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Value; |
| 1552 | if (!isValueType |
| 1553 | && propertyType->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) { |
| 1554 | recordError(location: binding.sourceLocation(), |
| 1555 | message: u"Group property '" + propertyName + u"' has unsupported access semantics" ); |
| 1556 | return; |
| 1557 | } |
| 1558 | |
| 1559 | auto subbindings = groupType->ownPropertyBindingsInQmlIROrder(); |
| 1560 | auto firstScript = partitionBindings(first: subbindings.begin(), last: subbindings.end()); |
| 1561 | |
| 1562 | // if we have no non-script bindings, we have no bindings that affect |
| 1563 | // the value type group, so no reason to generate the wrapping code |
| 1564 | const bool generateValueTypeCode = isValueType && (subbindings.begin() != firstScript); |
| 1565 | |
| 1566 | QString groupAccessor = QmltcCodeGenerator::wrap_privateClass(accessor: accessor.name, p: property) + u"->" |
| 1567 | + property.read() + u"()" ; |
| 1568 | // NB: used when isValueType == true |
| 1569 | const QString groupPropertyVarName = accessor.name + u"_group_" + propertyName; |
| 1570 | // value types are special |
| 1571 | if (generateValueTypeCode) { |
| 1572 | if (property.write().isEmpty()) { // just reject this |
| 1573 | recordError(location: binding.sourceLocation(), |
| 1574 | message: u"Group property '" + propertyName + u"' is a value type without a setter" ); |
| 1575 | return; |
| 1576 | } |
| 1577 | |
| 1578 | current.endInit.body << u"auto " + groupPropertyVarName + u" = " + groupAccessor + u";" ; |
| 1579 | // addressof operator is to make the binding logic work, which |
| 1580 | // expects that `accessor.name` is a pointer type |
| 1581 | groupAccessor = QmltcCodeGenerator::wrap_addressof(addressed: groupPropertyVarName); |
| 1582 | } |
| 1583 | |
| 1584 | // compile bindings of the grouped property |
| 1585 | const auto compile = [&](const auto &bStart, const auto &bEnd) { |
| 1586 | compileBinding(current, bindingStart: bStart, bindingEnd: bEnd, type: groupType, |
| 1587 | accessor: { type, groupAccessor, propertyName, isValueType }); |
| 1588 | }; |
| 1589 | |
| 1590 | auto it = subbindings.begin(); |
| 1591 | Q_ASSERT(std::all_of(it, firstScript, [](const auto &x) { |
| 1592 | return x.bindingType() != QQmlSA::BindingType::Script; |
| 1593 | })); |
| 1594 | compile(it, firstScript); |
| 1595 | it = firstScript; |
| 1596 | |
| 1597 | // NB: script bindings are special on group properties. if our group is |
| 1598 | // a value type, the binding would be installed on the *object* that |
| 1599 | // holds the value type and not on the value type itself. this may cause |
| 1600 | // subtle side issues (esp. when script binding is actually a simple |
| 1601 | // enum value assignment - which is not recognized specially): |
| 1602 | // |
| 1603 | // auto valueTypeGroupProperty = getCopy(); |
| 1604 | // installBinding(valueTypeGroupProperty, "subproperty1"); // changes subproperty1 value |
| 1605 | // setCopy(valueTypeGroupProperty); // oops, subproperty1 value changed to old again |
| 1606 | if (generateValueTypeCode) { // write the value type back |
| 1607 | current.endInit.body << QmltcCodeGenerator::wrap_privateClass(accessor: accessor.name, p: property) |
| 1608 | + u"->" + property.write() + u"(" + groupPropertyVarName + u");" ; |
| 1609 | } |
| 1610 | |
| 1611 | // once the value is written back, process the script bindings |
| 1612 | Q_ASSERT(std::all_of(it, subbindings.end(), [](const auto &x) { |
| 1613 | return x.bindingType() == QQmlSA::BindingType::Script; |
| 1614 | })); |
| 1615 | compile(it, subbindings.end()); |
| 1616 | } |
| 1617 | |
| 1618 | /*! |
| 1619 | * \internal |
| 1620 | * Helper method used to keep compileBindingByType() readable. |
| 1621 | */ |
| 1622 | void QmltcCompiler::compileTranslationBinding(QmltcType ¤t, |
| 1623 | const QQmlJSMetaPropertyBinding &binding, |
| 1624 | const QQmlJSScope::ConstPtr &type, |
| 1625 | const BindingAccessorData &accessor) |
| 1626 | { |
| 1627 | Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Translation |
| 1628 | || binding.bindingType() == QQmlSA::BindingType::TranslationById); |
| 1629 | |
| 1630 | const QString &propertyName = binding.propertyName(); |
| 1631 | |
| 1632 | auto [property, absoluteIndex] = getMetaPropertyIndex(scope: type, propertyName); |
| 1633 | |
| 1634 | if (absoluteIndex < 0) { |
| 1635 | recordError(location: binding.sourceLocation(), |
| 1636 | message: u"Binding on unknown property '" + propertyName + u"'" ); |
| 1637 | return; |
| 1638 | } |
| 1639 | |
| 1640 | QString bindingTarget = accessor.name; |
| 1641 | |
| 1642 | int valueTypeIndex = -1; |
| 1643 | if (accessor.isValueType) { |
| 1644 | Q_ASSERT(accessor.scope != type); |
| 1645 | bindingTarget = u"this"_s ; // TODO: not necessarily "this"? |
| 1646 | auto [groupProperty, groupPropertyIndex] = |
| 1647 | getMetaPropertyIndex(scope: accessor.scope, propertyName: accessor.propertyName); |
| 1648 | if (groupPropertyIndex < 0) { |
| 1649 | recordError(location: binding.sourceLocation(), |
| 1650 | message: u"Binding on group property '" + accessor.propertyName |
| 1651 | + u"' of unknown type" ); |
| 1652 | return; |
| 1653 | } |
| 1654 | valueTypeIndex = absoluteIndex; |
| 1655 | absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name |
| 1656 | } |
| 1657 | |
| 1658 | QmltcCodeGenerator::TranslationBindingInfo info; |
| 1659 | info.unitVarName = generate_callCompilationUnit(urlMethodName: m_urlMethodName); |
| 1660 | info.scope = u"this"_s ; |
| 1661 | info.target = u"this"_s ; |
| 1662 | info.propertyIndex = absoluteIndex; |
| 1663 | info.property = property; |
| 1664 | info.data = binding.translationDataValue(qmlFileNameForContext: m_url); |
| 1665 | info.valueTypeIndex = valueTypeIndex; |
| 1666 | info.line = binding.sourceLocation().startLine; |
| 1667 | info.column = binding.sourceLocation().startColumn; |
| 1668 | |
| 1669 | QmltcCodeGenerator::generate_createTranslationBindingOnProperty(block: ¤t.endInit.body, info); |
| 1670 | } |
| 1671 | |
| 1672 | void QmltcCompiler::processLastListBindings(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, |
| 1673 | const BindingAccessorData &accessor) |
| 1674 | { |
| 1675 | if (unprocessedListBindings.empty()) |
| 1676 | return; |
| 1677 | |
| 1678 | QmltcCodeGenerator::generate_assignToListProperty( |
| 1679 | block: ¤t.endInit.body, type, p: unprocessedListProperty, value: unprocessedListBindings, |
| 1680 | accessor: accessor.name, |
| 1681 | qmlListVarName&: m_uniques[UniqueStringId(current, unprocessedListProperty.propertyName())] |
| 1682 | .qmlListVariableName); |
| 1683 | |
| 1684 | unprocessedListBindings.clear(); |
| 1685 | } |
| 1686 | |
| 1687 | void QmltcCompiler::compileBinding(QmltcType ¤t, |
| 1688 | QList<QQmlJSMetaPropertyBinding>::iterator bindingStart, |
| 1689 | QList<QQmlJSMetaPropertyBinding>::iterator bindingEnd, |
| 1690 | const QQmlJSScope::ConstPtr &type, |
| 1691 | const BindingAccessorData &accessor) |
| 1692 | { |
| 1693 | for (auto it = bindingStart; it != bindingEnd; it++) { |
| 1694 | const QQmlJSMetaPropertyBinding &binding = *it; |
| 1695 | const QString &propertyName = binding.propertyName(); |
| 1696 | Q_ASSERT(!propertyName.isEmpty()); |
| 1697 | |
| 1698 | // Note: unlike QQmlObjectCreator, we don't have to do a complicated |
| 1699 | // deferral logic for bindings: if a binding is deferred, it is not compiled |
| 1700 | // (potentially, with all the bindings inside of it), period. |
| 1701 | if (type->isNameDeferred(name: propertyName)) { |
| 1702 | const auto location = binding.sourceLocation(); |
| 1703 | // make sure group property is not generalized by checking if type really has a property |
| 1704 | // called propertyName. If not, it is probably an id. |
| 1705 | if (binding.bindingType() == QQmlSA::BindingType::GroupProperty |
| 1706 | && type->hasProperty(name: propertyName)) { |
| 1707 | qCWarning(lcQmltcCompiler) |
| 1708 | << QStringLiteral("Binding at line %1 column %2 is not deferred as it is a " |
| 1709 | "binding on a group property." ) |
| 1710 | .arg(args: QString::number(location.startLine), |
| 1711 | args: QString::number(location.startColumn)); |
| 1712 | // we do not support PropertyChanges and other types with similar |
| 1713 | // behavior yet, so this binding is compiled |
| 1714 | } else { |
| 1715 | qCDebug(lcQmltcCompiler) |
| 1716 | << QStringLiteral( |
| 1717 | "Binding at line %1 column %2 is deferred and thus not compiled" ) |
| 1718 | .arg(args: QString::number(location.startLine), |
| 1719 | args: QString::number(location.startColumn)); |
| 1720 | continue; |
| 1721 | } |
| 1722 | } |
| 1723 | |
| 1724 | const QQmlJSMetaProperty metaProperty = type->property(name: propertyName); |
| 1725 | const QQmlJSScope::ConstPtr propertyType = metaProperty.type(); |
| 1726 | |
| 1727 | if (!(qIsReferenceTypeList(p: metaProperty) && unprocessedListProperty == metaProperty)) { |
| 1728 | processLastListBindings(current, type, accessor); |
| 1729 | } |
| 1730 | |
| 1731 | compileBindingByType(current, binding, type, accessor); |
| 1732 | } |
| 1733 | |
| 1734 | processLastListBindings(current, type, accessor); |
| 1735 | } |
| 1736 | |
| 1737 | void QmltcCompiler::compileBindingByType(QmltcType ¤t, |
| 1738 | const QQmlJSMetaPropertyBinding &binding, |
| 1739 | const QQmlJSScope::ConstPtr &type, |
| 1740 | const BindingAccessorData &accessor) |
| 1741 | { |
| 1742 | const QString &propertyName = binding.propertyName(); |
| 1743 | const QQmlJSMetaProperty metaProperty = type->property(name: propertyName); |
| 1744 | const QQmlJSScope::ConstPtr propertyType = metaProperty.type(); |
| 1745 | |
| 1746 | const auto assignToProperty = [&](const QQmlJSMetaProperty &p, const QString &value, |
| 1747 | bool constructFromQObject = false) { |
| 1748 | QmltcCodeGenerator::generate_assignToProperty(block: ¤t.endInit.body, type, p, value, |
| 1749 | accessor: accessor.name, constructFromQObject); |
| 1750 | }; |
| 1751 | switch (binding.bindingType()) { |
| 1752 | case QQmlSA::BindingType::BoolLiteral: { |
| 1753 | const bool value = binding.boolValue(); |
| 1754 | assignToProperty(metaProperty, value ? u"true"_s : u"false"_s ); |
| 1755 | break; |
| 1756 | } |
| 1757 | case QQmlSA::BindingType::NumberLiteral: { |
| 1758 | assignToProperty(metaProperty, QString::number(binding.numberValue())); |
| 1759 | break; |
| 1760 | } |
| 1761 | case QQmlSA::BindingType::StringLiteral: { |
| 1762 | QString value = QQmlJSUtils::toLiteral(s: binding.stringValue()); |
| 1763 | if (auto type = metaProperty.type()) { |
| 1764 | if (type->internalName() == u"QUrl"_s ) { |
| 1765 | value = u"QUrl(%1)"_s .arg(a: value); |
| 1766 | } |
| 1767 | } |
| 1768 | assignToProperty(metaProperty, value); |
| 1769 | break; |
| 1770 | } |
| 1771 | case QQmlSA::BindingType::RegExpLiteral: { |
| 1772 | const QString value = |
| 1773 | u"QRegularExpression(%1)"_s .arg(a: QQmlJSUtils::toLiteral(s: binding.regExpValue())); |
| 1774 | assignToProperty(metaProperty, value); |
| 1775 | break; |
| 1776 | } |
| 1777 | case QQmlSA::BindingType::Null: { |
| 1778 | // poor check: null bindings are only supported for var and objects |
| 1779 | Q_ASSERT(propertyType->isSameType(m_typeResolver->varType()) |
| 1780 | || propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference); |
| 1781 | if (propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) |
| 1782 | assignToProperty(metaProperty, u"nullptr"_s ); |
| 1783 | else |
| 1784 | assignToProperty(metaProperty, u"QVariant::fromValue(nullptr)"_s ); |
| 1785 | break; |
| 1786 | } |
| 1787 | case QQmlSA::BindingType::Script: { |
| 1788 | QString bindingSymbolName |
| 1789 | = uniqueVariableName(qmlName: type->internalName() + u'_' + propertyName + u"_binding" ); |
| 1790 | compileScriptBinding(current, binding, bindingSymbolName, type, propertyName, propertyType, |
| 1791 | accessor); |
| 1792 | break; |
| 1793 | } |
| 1794 | case QQmlSA::BindingType::Object: { |
| 1795 | compileObjectBinding(current, binding, type, accessor); |
| 1796 | break; |
| 1797 | } |
| 1798 | case QQmlSA::BindingType::Interceptor: |
| 1799 | Q_FALLTHROUGH(); |
| 1800 | case QQmlSA::BindingType::ValueSource: { |
| 1801 | compileValueSourceOrInterceptorBinding(current, binding, type, accessor); |
| 1802 | break; |
| 1803 | } |
| 1804 | case QQmlSA::BindingType::AttachedProperty: { |
| 1805 | compileAttachedPropertyBinding(current, binding, type, accessor); |
| 1806 | break; |
| 1807 | } |
| 1808 | case QQmlSA::BindingType::GroupProperty: { |
| 1809 | compileGroupPropertyBinding(current, binding, type, accessor); |
| 1810 | break; |
| 1811 | } |
| 1812 | |
| 1813 | case QQmlSA::BindingType::TranslationById: |
| 1814 | case QQmlSA::BindingType::Translation: { |
| 1815 | compileTranslationBinding(current, binding, type, accessor); |
| 1816 | break; |
| 1817 | } |
| 1818 | case QQmlSA::BindingType::Invalid: { |
| 1819 | recordError(location: binding.sourceLocation(), message: u"This binding is invalid"_s ); |
| 1820 | break; |
| 1821 | } |
| 1822 | default: { |
| 1823 | recordError(location: binding.sourceLocation(), message: u"Binding is not supported"_s ); |
| 1824 | break; |
| 1825 | } |
| 1826 | } |
| 1827 | } |
| 1828 | |
| 1829 | // returns compiled script binding for "property changed" handler in a form of object type |
| 1830 | static QmltcType compileScriptBindingPropertyChangeHandler(const QQmlJSMetaPropertyBinding &binding, |
| 1831 | const QQmlJSScope::ConstPtr &objectType, |
| 1832 | const QString &urlMethodName, |
| 1833 | const QString &functorCppType, |
| 1834 | const QString &objectCppType) |
| 1835 | { |
| 1836 | QmltcType bindingFunctor {}; |
| 1837 | bindingFunctor.cppType = functorCppType; |
| 1838 | bindingFunctor.ignoreInit = true; |
| 1839 | |
| 1840 | // default member variable and ctor: |
| 1841 | const QString pointerToObject = objectCppType + u" *" ; |
| 1842 | bindingFunctor.variables.emplaceBack( |
| 1843 | args: QmltcVariable { pointerToObject, u"m_self"_s , u"nullptr"_s }); |
| 1844 | bindingFunctor.baselineCtor.name = functorCppType; |
| 1845 | bindingFunctor.baselineCtor.parameterList.emplaceBack( |
| 1846 | args: QmltcVariable { pointerToObject, u"self"_s , QString() }); |
| 1847 | bindingFunctor.baselineCtor.initializerList.emplaceBack(args: u"m_self(self)"_s ); |
| 1848 | |
| 1849 | // call operator: |
| 1850 | QmltcMethod callOperator {}; |
| 1851 | callOperator.returnType = u"void"_s ; |
| 1852 | callOperator.name = u"operator()"_s ; |
| 1853 | callOperator.modifiers << u"const"_s ; |
| 1854 | QmltcCodeGenerator::generate_callExecuteRuntimeFunction( |
| 1855 | block: &callOperator.body, url: urlMethodName + u"()" , |
| 1856 | index: objectType->ownRuntimeFunctionIndex(index: binding.scriptIndex()), accessor: u"m_self"_s , returnType: u"void"_s , parameters: {}); |
| 1857 | |
| 1858 | bindingFunctor.functions.emplaceBack(args: std::move(callOperator)); |
| 1859 | |
| 1860 | return bindingFunctor; |
| 1861 | } |
| 1862 | |
| 1863 | // finds property for given scope and returns it together with the absolute |
| 1864 | // property index in the property array of the corresponding QMetaObject |
| 1865 | static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope, |
| 1866 | const QString &propertyName) |
| 1867 | { |
| 1868 | auto owner = QQmlJSScope::ownerOfProperty(self: scope, name: propertyName).scope; |
| 1869 | Q_ASSERT(owner); |
| 1870 | const QQmlJSMetaProperty p = owner->ownProperty(name: propertyName); |
| 1871 | if (!p.isValid()) |
| 1872 | return { p, -1 }; |
| 1873 | int index = p.index(); |
| 1874 | if (index < 0) // this property doesn't have index - comes from QML |
| 1875 | return { p, -1 }; |
| 1876 | |
| 1877 | const auto increment = [&](const QQmlJSScope::ConstPtr &type, QQmlJSScope::ExtensionKind m) { |
| 1878 | // owner of property is not included in the offset calculation (relative |
| 1879 | // index is already added as p.index()) |
| 1880 | if (type->isSameType(otherScope: owner)) |
| 1881 | return; |
| 1882 | |
| 1883 | // extension namespace and JavaScript properties are ignored |
| 1884 | if (m == QQmlJSScope::ExtensionNamespace || m == QQmlJSScope::ExtensionJavaScript) |
| 1885 | return; |
| 1886 | |
| 1887 | index += int(type->ownProperties().size()); |
| 1888 | }; |
| 1889 | QQmlJSUtils::traverseFollowingMetaObjectHierarchy(scope, start: owner, act: increment); |
| 1890 | return { p, index }; |
| 1891 | } |
| 1892 | |
| 1893 | void QmltcCompiler::compileScriptBinding(QmltcType ¤t, |
| 1894 | const QQmlJSMetaPropertyBinding &binding, |
| 1895 | const QString &bindingSymbolName, |
| 1896 | const QQmlJSScope::ConstPtr &objectType, |
| 1897 | const QString &propertyName, |
| 1898 | const QQmlJSScope::ConstPtr &propertyType, |
| 1899 | const QmltcCompiler::BindingAccessorData &accessor) |
| 1900 | { |
| 1901 | const auto compileScriptSignal = [&](const QString &name) { |
| 1902 | QString This_signal = u"this"_s ; |
| 1903 | QString This_slot = u"this"_s ; |
| 1904 | QString objectClassName_signal = objectType->internalName(); |
| 1905 | QString objectClassName_slot = objectType->internalName(); |
| 1906 | |
| 1907 | // TODO: ugly crutch to make stuff work |
| 1908 | if (accessor.name != u"this"_s ) { // e.g. if attached property |
| 1909 | This_signal = accessor.name; |
| 1910 | This_slot = u"this"_s ; // still |
| 1911 | objectClassName_signal = objectType->baseTypeName(); |
| 1912 | objectClassName_slot = current.cppType; // real base class where slot would go |
| 1913 | } |
| 1914 | Q_ASSERT(!objectClassName_signal.isEmpty()); |
| 1915 | Q_ASSERT(!objectClassName_slot.isEmpty()); |
| 1916 | |
| 1917 | const auto signalMethods = objectType->methods(name, type: QQmlJSMetaMethodType::Signal); |
| 1918 | Q_ASSERT(!signalMethods.isEmpty()); // an error somewhere else |
| 1919 | QQmlJSMetaMethod signal = signalMethods.at(i: 0); |
| 1920 | Q_ASSERT(signal.methodType() == QQmlJSMetaMethodType::Signal); |
| 1921 | |
| 1922 | const QString signalName = signal.methodName(); |
| 1923 | const QString slotName = newSymbol(base: signalName + u"_slot" ); |
| 1924 | |
| 1925 | const QString signalReturnType = signal.returnType()->augmentedInternalName(); |
| 1926 | const QList<QmltcVariable> slotParameters = |
| 1927 | compileMethodParameters(parameterInfos: signal.parameters(), /* allow unnamed = */ allowUnnamed: true); |
| 1928 | |
| 1929 | // SignalHander specific: |
| 1930 | QmltcMethod slotMethod {}; |
| 1931 | slotMethod.returnType = signalReturnType; |
| 1932 | slotMethod.name = slotName; |
| 1933 | slotMethod.parameterList = slotParameters; |
| 1934 | |
| 1935 | QmltcCodeGenerator::generate_callExecuteRuntimeFunction( |
| 1936 | block: &slotMethod.body, url: m_urlMethodName + u"()" , |
| 1937 | index: objectType->ownRuntimeFunctionIndex(index: binding.scriptIndex()), |
| 1938 | accessor: u"this"_s , // Note: because script bindings always use current QML object scope |
| 1939 | returnType: signalReturnType, parameters: slotParameters); |
| 1940 | slotMethod.type = QQmlJSMetaMethodType::Slot; |
| 1941 | |
| 1942 | current.functions << std::move(slotMethod); |
| 1943 | current.setComplexBindings.body << u"QObject::connect(" + This_signal + u", " + u"&" |
| 1944 | + objectClassName_signal + u"::" + signalName + u", " + This_slot + u", &" |
| 1945 | + objectClassName_slot + u"::" + slotName + u");" ; |
| 1946 | }; |
| 1947 | |
| 1948 | switch (binding.scriptKind()) { |
| 1949 | case QQmlSA::ScriptBindingKind::PropertyBinding: { |
| 1950 | if (!propertyType) { |
| 1951 | recordError(location: binding.sourceLocation(), |
| 1952 | message: u"Binding on property '" + propertyName + u"' of unknown type" ); |
| 1953 | return; |
| 1954 | } |
| 1955 | |
| 1956 | auto [property, absoluteIndex] = getMetaPropertyIndex(scope: objectType, propertyName); |
| 1957 | if (absoluteIndex < 0) { |
| 1958 | recordError(location: binding.sourceLocation(), |
| 1959 | message: u"Binding on unknown property '" + propertyName + u"'" ); |
| 1960 | return; |
| 1961 | } |
| 1962 | |
| 1963 | QString bindingTarget = accessor.name; |
| 1964 | |
| 1965 | int valueTypeIndex = -1; |
| 1966 | if (accessor.isValueType) { |
| 1967 | Q_ASSERT(accessor.scope != objectType); |
| 1968 | bindingTarget = u"this"_s ; // TODO: not necessarily "this"? |
| 1969 | auto [groupProperty, groupPropertyIndex] = |
| 1970 | getMetaPropertyIndex(scope: accessor.scope, propertyName: accessor.propertyName); |
| 1971 | if (groupPropertyIndex < 0) { |
| 1972 | recordError(location: binding.sourceLocation(), |
| 1973 | message: u"Binding on group property '" + accessor.propertyName |
| 1974 | + u"' of unknown type" ); |
| 1975 | return; |
| 1976 | } |
| 1977 | valueTypeIndex = absoluteIndex; |
| 1978 | absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name |
| 1979 | } |
| 1980 | |
| 1981 | QmltcCodeGenerator::generate_createBindingOnProperty( |
| 1982 | block: ¤t.setComplexBindings.body, unitVarName: generate_callCompilationUnit(urlMethodName: m_urlMethodName), |
| 1983 | scope: u"this"_s , // NB: always using enclosing object as a scope for the binding |
| 1984 | functionIndex: static_cast<qsizetype>(objectType->ownRuntimeFunctionIndex(index: binding.scriptIndex())), |
| 1985 | target: bindingTarget, // binding target |
| 1986 | // value types are special and are bound through valueTypeIndex |
| 1987 | targetType: accessor.isValueType ? QQmlJSScope::ConstPtr() : objectType, propertyIndex: absoluteIndex, |
| 1988 | p: property, valueTypeIndex, subTarget: accessor.name); |
| 1989 | break; |
| 1990 | } |
| 1991 | case QQmlSA::ScriptBindingKind::SignalHandler: { |
| 1992 | const auto name = QQmlSignalNames::handlerNameToSignalName(handler: propertyName); |
| 1993 | Q_ASSERT(name.has_value()); |
| 1994 | compileScriptSignal(*name); |
| 1995 | break; |
| 1996 | } |
| 1997 | case QQmlSA ::ScriptBindingKind::ChangeHandler: { |
| 1998 | const QString objectClassName = objectType->internalName(); |
| 1999 | const QString bindingFunctorName = newSymbol(base: bindingSymbolName + u"Functor" ); |
| 2000 | |
| 2001 | const auto signalName = QQmlSignalNames::handlerNameToSignalName(handler: propertyName); |
| 2002 | Q_ASSERT(signalName.has_value()); // an error somewhere else |
| 2003 | const auto actualProperty = |
| 2004 | QQmlJSUtils::propertyFromChangedHandler(scope: objectType, changedHandler: propertyName); |
| 2005 | Q_ASSERT(actualProperty.has_value()); // an error somewhere else |
| 2006 | const auto actualPropertyType = actualProperty->type(); |
| 2007 | if (!actualPropertyType) { |
| 2008 | recordError(location: binding.sourceLocation(), |
| 2009 | message: u"Binding on property '" + actualProperty->propertyName() |
| 2010 | + u"' of unknown type" ); |
| 2011 | return; |
| 2012 | } |
| 2013 | |
| 2014 | // due to historical reasons (QQmlObjectCreator), prefer NOTIFY over |
| 2015 | // BINDABLE when both are available. thus, test for notify first |
| 2016 | const QString notifyString = actualProperty->notify(); |
| 2017 | if (!notifyString.isEmpty()) { |
| 2018 | compileScriptSignal(notifyString); |
| 2019 | break; |
| 2020 | } |
| 2021 | const QString bindableString = actualProperty->bindable(); |
| 2022 | QString typeOfQmlBinding = |
| 2023 | u"std::unique_ptr<QPropertyChangeHandler<" + bindingFunctorName + u">>" ; |
| 2024 | |
| 2025 | current.children << compileScriptBindingPropertyChangeHandler( |
| 2026 | binding, objectType, urlMethodName: m_urlMethodName, functorCppType: bindingFunctorName, objectCppType: objectClassName); |
| 2027 | |
| 2028 | current.setComplexBindings.body << u"if (!%1.contains(QStringLiteral(\"%2\")))"_s .arg( |
| 2029 | args&: current.propertyInitializer.initializedCache.name, args: propertyName); |
| 2030 | |
| 2031 | // TODO: this could be dropped if QQmlEngine::setContextForObject() is |
| 2032 | // done before currently generated C++ object is constructed |
| 2033 | current.setComplexBindings.body << u" "_s + bindingSymbolName + u".reset(new QPropertyChangeHandler<" |
| 2034 | + bindingFunctorName + u">(" |
| 2035 | + QmltcCodeGenerator::wrap_privateClass(accessor: accessor.name, p: *actualProperty) |
| 2036 | + u"->" + bindableString + u"().onValueChanged(" + bindingFunctorName + u"(" |
| 2037 | + accessor.name + u"))));" ; |
| 2038 | |
| 2039 | current.variables.emplaceBack( |
| 2040 | args: QmltcVariable { typeOfQmlBinding, bindingSymbolName, QString() }); |
| 2041 | break; |
| 2042 | } |
| 2043 | default: |
| 2044 | recordError(location: binding.sourceLocation(), message: u"Invalid script binding found"_s ); |
| 2045 | break; |
| 2046 | } |
| 2047 | } |
| 2048 | |
| 2049 | QT_END_NAMESPACE |
| 2050 | |