| 1 | // Copyright (C) 2022 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 "qmltccompilerpieces.h" |
| 5 | |
| 6 | #include <private/qqmljsutils_p.h> |
| 7 | |
| 8 | #include <tuple> |
| 9 | |
| 10 | QT_BEGIN_NAMESPACE |
| 11 | |
| 12 | using namespace Qt::StringLiterals; |
| 13 | |
| 14 | static QString scopeName(const QQmlJSScope::ConstPtr &scope) |
| 15 | { |
| 16 | Q_ASSERT(scope->isFullyResolved()); |
| 17 | const auto scopeType = scope->scopeType(); |
| 18 | if (scopeType == QQmlSA::ScopeType::GroupedPropertyScope |
| 19 | || scopeType == QQmlSA::ScopeType::AttachedPropertyScope) { |
| 20 | return scope->baseType()->internalName(); |
| 21 | } |
| 22 | return scope->internalName(); |
| 23 | } |
| 24 | |
| 25 | QmltcCodeGenerator::PreparedValue |
| 26 | QmltcCodeGenerator::wrap_extensionType(const QQmlJSScope::ConstPtr &type, |
| 27 | const QQmlJSMetaProperty &p, const QString &accessor) |
| 28 | { |
| 29 | Q_ASSERT(type->isFullyResolved()); |
| 30 | |
| 31 | QStringList prologue; |
| 32 | QString value = accessor; |
| 33 | QStringList epilogue; |
| 34 | |
| 35 | auto [owner, ownerKind] = QQmlJSScope::ownerOfProperty(self: type, name: p.propertyName()); |
| 36 | Q_ASSERT(owner); |
| 37 | Q_ASSERT(owner->isFullyResolved()); |
| 38 | |
| 39 | // properties are only visible when we use QML_{NAMESPACE_}EXTENDED |
| 40 | if (ownerKind == QQmlJSScope::ExtensionType) { |
| 41 | // extensions is a C++-only feature: |
| 42 | Q_ASSERT(!owner->isComposite()); |
| 43 | |
| 44 | // have to wrap the property into an extension, but we need to figure |
| 45 | // out whether the type is QObject-based or not |
| 46 | prologue << u"{"_s ; |
| 47 | const QString extensionObjectName = u"extObject"_s ; |
| 48 | if (type->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) { |
| 49 | // we have a Q_OBJECT. in this case, we call qmlExtendedObject() |
| 50 | // function that should return us the extension object. for that we |
| 51 | // have to figure out which specific extension we want here |
| 52 | |
| 53 | int extensionIndex = 0; |
| 54 | auto cppBase = QQmlJSScope::nonCompositeBaseType(type); |
| 55 | for (auto t = cppBase; t; t = t->baseType()) { |
| 56 | if (auto [ext, kind] = t->extensionType(); kind != QQmlJSScope::NotExtension) { |
| 57 | if (ext->isSameType(otherScope: owner)) |
| 58 | break; |
| 59 | ++extensionIndex; |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | prologue << u"static_assert(std::is_base_of<%1, %2>::value);"_s .arg(args: u"QObject"_s , |
| 64 | args: scopeName(scope: type)); |
| 65 | prologue << u"auto %1 = qobject_cast<%2 *>(QQmlPrivate::qmlExtendedObject(%3, %4));"_s |
| 66 | .arg(args: extensionObjectName, args: owner->internalName(), args: accessor, |
| 67 | args: QString::number(extensionIndex)); |
| 68 | } else { |
| 69 | // we have a Q_GADGET. the assumption for extension types is that we |
| 70 | // can reinterpret_cast a Q_GADGET object into an extension type |
| 71 | // object and then interact with the extension object right away |
| 72 | prologue << u"static_assert(sizeof(%1) == sizeof(%2));"_s .arg(args: scopeName(scope: type), |
| 73 | args: owner->internalName()); |
| 74 | prologue << u"static_assert(alignof(%1) == alignof(%2));"_s .arg(args: scopeName(scope: type), |
| 75 | args: owner->internalName()); |
| 76 | prologue << u"auto %1 = reinterpret_cast<%2 *>(%3);"_s .arg( |
| 77 | args: extensionObjectName, args: owner->internalName(), args: accessor); |
| 78 | } |
| 79 | prologue << u"Q_ASSERT(%1);"_s .arg(a: extensionObjectName); |
| 80 | value = extensionObjectName; |
| 81 | epilogue << u"}"_s ; |
| 82 | } |
| 83 | |
| 84 | return { .prologue: prologue, .value: value, .epilogue: epilogue }; |
| 85 | } |
| 86 | |
| 87 | void QmltcCodeGenerator::generate_assignToListProperty( |
| 88 | QStringList *block, const QQmlJSScope::ConstPtr &type, const QQmlJSMetaProperty &p, |
| 89 | const QStringList &values, const QString &accessor, QString &qmlListVarName) |
| 90 | { |
| 91 | Q_UNUSED(type); // might be needed |
| 92 | const bool populateLocalListProperty = qmlListVarName.isEmpty(); |
| 93 | |
| 94 | if (populateLocalListProperty) { |
| 95 | auto [extensionPrologue, extensionAccessor, extensionEpilogue] = |
| 96 | QmltcCodeGenerator::wrap_extensionType( |
| 97 | type, p, accessor: QmltcCodeGenerator::wrap_privateClass(accessor, p)); |
| 98 | |
| 99 | qmlListVarName = u"listprop_%1"_s .arg(a: p.propertyName()); |
| 100 | QQmlJSScope::ConstPtr valueType = p.type()->valueType(); |
| 101 | *block << u"QQmlListProperty<%1> %2;"_s .arg(args: valueType->internalName(), args&: qmlListVarName); |
| 102 | *block << extensionPrologue; |
| 103 | *block << u"%1 = %2->%3();"_s .arg(args&: qmlListVarName, args&: extensionAccessor, args: p.read()); |
| 104 | *block << extensionEpilogue; |
| 105 | } |
| 106 | for (const QString &value : values) { |
| 107 | auto [prologue, wrappedValue, epilogue] = |
| 108 | QmltcCodeGenerator::wrap_mismatchingTypeConversion(p, value); |
| 109 | *block << prologue; |
| 110 | *block << u"%1.append(std::addressof(%1), %2);"_s .arg(args&: qmlListVarName, args&: wrappedValue); |
| 111 | *block << epilogue; |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | void QmltcCodeGenerator::generate_assignToProperty(QStringList *block, |
| 116 | const QQmlJSScope::ConstPtr &type, |
| 117 | const QQmlJSMetaProperty &p, |
| 118 | const QString &value, const QString &accessor, |
| 119 | bool constructFromQObject) |
| 120 | { |
| 121 | Q_ASSERT(block); |
| 122 | Q_ASSERT(p.isValid()); |
| 123 | Q_ASSERT(!p.isList()); // NB: this code does not handle list properties |
| 124 | |
| 125 | const QString propertyName = p.propertyName(); |
| 126 | |
| 127 | if (type->hasOwnProperty(name: p.propertyName()) && !p.isAlias()) { |
| 128 | Q_ASSERT(!p.isPrivate()); |
| 129 | // this object is compiled, so just assignment should work fine |
| 130 | auto [prologue, wrappedValue, epilogue] = |
| 131 | QmltcCodeGenerator::wrap_mismatchingTypeConversion(p, value); |
| 132 | *block += prologue; |
| 133 | *block << u"%1->m_%2 = %3;"_s .arg(args: accessor, args: propertyName, args&: wrappedValue); |
| 134 | *block += epilogue; |
| 135 | } else if (QString propertySetter = p.write(); !propertySetter.isEmpty() |
| 136 | && !QQmlJSUtils::bindablePropertyHasDefaultAccessor( |
| 137 | p, accessor: QQmlJSUtils::PropertyAccessor_Write)) { |
| 138 | // there's a WRITE function |
| 139 | auto [prologue, wrappedValue, epilogue] = |
| 140 | QmltcCodeGenerator::wrap_mismatchingTypeConversion(p, value); |
| 141 | *block += prologue; |
| 142 | |
| 143 | auto [extensionPrologue, extensionAccessor, extensionEpilogue] = |
| 144 | QmltcCodeGenerator::wrap_extensionType( |
| 145 | type, p, accessor: QmltcCodeGenerator::wrap_privateClass(accessor, p)); |
| 146 | *block += extensionPrologue; |
| 147 | *block << extensionAccessor + u"->" + propertySetter + u"(" + wrappedValue + u");" ; |
| 148 | *block += extensionEpilogue; |
| 149 | |
| 150 | *block += epilogue; |
| 151 | } else { |
| 152 | // this property is weird, fallback to `setProperty` |
| 153 | *block << u"{ // couldn't find property setter, so using QObject::setProperty()"_s ; |
| 154 | QString val = value; |
| 155 | if (constructFromQObject) { |
| 156 | const QString variantName = u"var_" + propertyName; |
| 157 | *block << u"QVariant " + variantName + u";" ; |
| 158 | *block << variantName + u".setValue(" + val + u");" ; |
| 159 | val = u"std::move(" + variantName + u")" ; |
| 160 | } |
| 161 | // NB: setProperty() would handle private properties |
| 162 | *block << accessor + u"->setProperty(\"" + propertyName + u"\", " + val + u");" ; |
| 163 | *block << u"}"_s ; |
| 164 | } |
| 165 | } |
| 166 | |
| 167 | void QmltcCodeGenerator::generate_setIdValue(QStringList *block, const QString &context, |
| 168 | qsizetype index, const QString &accessor, |
| 169 | const QString &idString) |
| 170 | { |
| 171 | Q_ASSERT(index >= 0); |
| 172 | *block << u"Q_ASSERT(%1 < %2->numIdValues()); // make sure Id is in bounds"_s .arg(a: index).arg( |
| 173 | a: context); |
| 174 | *block << u"%1->setIdValue(%2 /* id: %3 */, %4);"_s .arg(args: context, args: QString::number(index), |
| 175 | args: idString, args: accessor); |
| 176 | } |
| 177 | |
| 178 | void QmltcCodeGenerator::generate_callExecuteRuntimeFunction( |
| 179 | QStringList *block, const QString &url, QQmlJSMetaMethod::AbsoluteFunctionIndex index, |
| 180 | const QString &accessor, const QString &returnType, const QList<QmltcVariable> ¶meters) |
| 181 | { |
| 182 | *block << u"QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(" + accessor + u"));" ; |
| 183 | |
| 184 | const QString returnValueName = u"_ret"_s ; |
| 185 | QStringList args; |
| 186 | args.reserve(asize: parameters.size() + 1); |
| 187 | QStringList types; |
| 188 | types.reserve(asize: parameters.size() + 1); |
| 189 | if (returnType == u"void"_s ) { |
| 190 | args << u"nullptr"_s ; |
| 191 | types << u"QMetaType::fromType<void>()"_s ; |
| 192 | } else { |
| 193 | *block << returnType + u" " + returnValueName + u"{};" ; // TYPE _ret{}; |
| 194 | args << u"const_cast<void *>(reinterpret_cast<const void *>(std::addressof(" |
| 195 | + returnValueName + u")))" ; |
| 196 | types << u"QMetaType::fromType<std::decay_t<" + returnType + u">>()" ; |
| 197 | } |
| 198 | |
| 199 | for (const QmltcVariable &p : parameters) { |
| 200 | args << u"const_cast<void *>(reinterpret_cast<const void *>(std::addressof(" + p.name |
| 201 | + u")))" ; |
| 202 | types << u"QMetaType::fromType<std::decay_t<" + p.cppType + u">>()" ; |
| 203 | } |
| 204 | |
| 205 | *block << u"void *_a[] = { " + args.join(sep: u", "_s ) + u" };" ; |
| 206 | *block << u"QMetaType _t[] = { " + types.join(sep: u", "_s ) + u" };" ; |
| 207 | const qsizetype runtimeIndex = static_cast<qsizetype>(index); |
| 208 | Q_ASSERT(runtimeIndex >= 0); |
| 209 | *block << u"e->executeRuntimeFunction(" + url + u", " + QString::number(runtimeIndex) + u", " |
| 210 | + accessor + u", " + QString::number(parameters.size()) + u", _a, _t);" ; |
| 211 | if (returnType != u"void"_s ) |
| 212 | *block << u"return " + returnValueName + u";" ; |
| 213 | } |
| 214 | |
| 215 | void QmltcCodeGenerator::generate_createBindingOnProperty( |
| 216 | QStringList *block, const QString &unitVarName, const QString &scope, |
| 217 | qsizetype functionIndex, const QString &target, const QQmlJSScope::ConstPtr &targetType, |
| 218 | int propertyIndex, const QQmlJSMetaProperty &p, int valueTypeIndex, |
| 219 | const QString &subTarget) |
| 220 | { |
| 221 | const QString propName = QQmlJSUtils::toLiteral(s: p.propertyName()); |
| 222 | if (QString bindable = p.bindable(); !bindable.isEmpty()) { |
| 223 | // TODO: test that private properties are bindable |
| 224 | QString createBindingForBindable = u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::" |
| 225 | u"createBindingForBindable(" |
| 226 | + unitVarName + u", " + scope + u", " + QString::number(functionIndex) + u", " |
| 227 | + target + u", " + QString::number(propertyIndex) + u", " |
| 228 | + QString::number(valueTypeIndex) + u", " + propName + u")" ; |
| 229 | const QString accessor = (valueTypeIndex == -1) ? target : subTarget; |
| 230 | |
| 231 | QStringList prologue; |
| 232 | QString value = QmltcCodeGenerator::wrap_privateClass(accessor, p); |
| 233 | QStringList epilogue; |
| 234 | if (targetType) { |
| 235 | auto [pro, v, epi] = QmltcCodeGenerator::wrap_extensionType(type: targetType, p, accessor: value); |
| 236 | std::tie(args&: prologue, args&: value, args&: epilogue) = std::make_tuple(args&: pro, args&: v, args&: epi); |
| 237 | } |
| 238 | |
| 239 | *block += prologue; |
| 240 | *block << u"if (!initializedCache.contains(QStringLiteral(\"%1\")))"_s .arg(a: p.propertyName()); |
| 241 | *block << u" "_s + value + u"->" + bindable + u"().setBinding(" + createBindingForBindable + u");" ; |
| 242 | *block += epilogue; |
| 243 | } else { |
| 244 | QString createBindingForNonBindable = |
| 245 | u" "_s |
| 246 | + u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::createBindingForNonBindable(" + unitVarName |
| 247 | + u", " + scope + u", " + QString::number(functionIndex) + u", " + target + u", " |
| 248 | + QString::number(propertyIndex) + u", " + QString::number(valueTypeIndex) + u", " |
| 249 | + propName + u")" ; |
| 250 | // Note: in this version, the binding is set implicitly |
| 251 | *block << u"if (!initializedCache.contains(QStringLiteral(\"%1\")))"_s .arg(a: p.propertyName()); |
| 252 | *block << createBindingForNonBindable + u";" ; |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | void QmltcCodeGenerator::generate_createTranslationBindingOnProperty( |
| 257 | QStringList *block, const TranslationBindingInfo &info) |
| 258 | { |
| 259 | const QString propName = QQmlJSUtils::toLiteral(s: info.property.propertyName()); |
| 260 | const QString qqmlTranslation = info.data.serializeForQmltc(); |
| 261 | |
| 262 | if (QString bindable = info.property.bindable(); !bindable.isEmpty()) { |
| 263 | // TODO: test that private properties are bindable |
| 264 | QString createTranslationCode = uR"(QT_PREPEND_NAMESPACE(QQmlCppBinding) |
| 265 | ::createTranslationBindingForBindable(%1, %2, %3, %4, %5))"_s |
| 266 | .arg(args: info.unitVarName, args: info.target) |
| 267 | .arg(a: info.propertyIndex) |
| 268 | .arg(args: qqmlTranslation, args: propName); |
| 269 | |
| 270 | *block << QmltcCodeGenerator::wrap_privateClass(accessor: info.target, p: info.property) + u"->" |
| 271 | + bindable + u"().setBinding(" + createTranslationCode + u");" ; |
| 272 | } else { |
| 273 | QString locationString = |
| 274 | u"QQmlSourceLocation(%1->fileName(), %2, %3)"_s .arg(a: info.unitVarName) |
| 275 | .arg(a: info.line) |
| 276 | .arg(a: info.column); |
| 277 | QString createTranslationCode = uR"(QT_PREPEND_NAMESPACE(QQmlCppBinding) |
| 278 | ::createTranslationBindingForNonBindable( |
| 279 | %1, //unit |
| 280 | %2, //location |
| 281 | %3, //translationData |
| 282 | %4, //thisObject |
| 283 | %5, //bindingTarget |
| 284 | %6, //metaPropertyIndex |
| 285 | %7, //propertyName |
| 286 | %8) //valueTypePropertyIndex |
| 287 | )"_s .arg(args: info.unitVarName, args&: locationString, args: qqmlTranslation, args: info.scope, args: info.target) |
| 288 | .arg(a: info.propertyIndex) |
| 289 | .arg(a: propName) |
| 290 | .arg(a: info.valueTypeIndex); |
| 291 | // Note: in this version, the binding is set implicitly |
| 292 | *block << createTranslationCode + u";" ; |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | QmltcCodeGenerator::PreparedValue |
| 297 | QmltcCodeGenerator::wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p, QString value) |
| 298 | { |
| 299 | auto isDerivedFromBuiltin = [](const QQmlJSScope::ConstPtr &derived, const QString &builtin) { |
| 300 | for (QQmlJSScope::ConstPtr t = derived; t; t = t->baseType()) { |
| 301 | if (t->internalName() == builtin) |
| 302 | return true; |
| 303 | } |
| 304 | return false; |
| 305 | }; |
| 306 | QStringList prologue; |
| 307 | QStringList epilogue; |
| 308 | const QQmlJSScope::ConstPtr propType = p.type(); |
| 309 | if (isDerivedFromBuiltin(propType, u"QVariant"_s )) { |
| 310 | const QString variantName = u"var_" + p.propertyName(); |
| 311 | prologue << u"{ // accepts QVariant"_s ; |
| 312 | prologue << u"QVariant " + variantName + u";" ; |
| 313 | prologue << variantName + u".setValue(" + value + u");" ; |
| 314 | epilogue << u"}"_s ; |
| 315 | value = u"std::move(" + variantName + u")" ; |
| 316 | } else if (isDerivedFromBuiltin(propType, u"QJSValue"_s )) { |
| 317 | const QString jsvalueName = u"jsvalue_" + p.propertyName(); |
| 318 | prologue << u"{ // accepts QJSValue"_s ; |
| 319 | // Note: do not assume we have the engine, acquire it from `this` |
| 320 | prologue << u"auto e = qmlEngine(this);"_s ; |
| 321 | prologue << u"QJSValue " + jsvalueName + u" = e->toScriptValue(" + value + u");" ; |
| 322 | epilogue << u"}"_s ; |
| 323 | value = u"std::move(" + jsvalueName + u")" ; |
| 324 | } |
| 325 | return { .prologue: prologue, .value: value, .epilogue: epilogue }; |
| 326 | } |
| 327 | |
| 328 | QString QmltcCodeGenerator::wrap_privateClass(const QString &accessor, const QQmlJSMetaProperty &p) |
| 329 | { |
| 330 | if (!p.isPrivate()) |
| 331 | return accessor; |
| 332 | |
| 333 | const QString privateType = p.privateClass(); |
| 334 | return u"static_cast<" + privateType + u" *>(QObjectPrivate::get(" + accessor + u"))" ; |
| 335 | } |
| 336 | |
| 337 | QString QmltcCodeGenerator::wrap_qOverload(const QList<QmltcVariable> ¶meters, |
| 338 | const QString &overloaded) |
| 339 | { |
| 340 | QStringList types; |
| 341 | types.reserve(asize: parameters.size()); |
| 342 | for (const QmltcVariable &p : parameters) |
| 343 | types.emplaceBack(args: p.cppType); |
| 344 | return u"qOverload<" + types.join(sep: u", "_s ) + u">(" + overloaded + u")" ; |
| 345 | } |
| 346 | |
| 347 | QString QmltcCodeGenerator::wrap_addressof(const QString &addressed) |
| 348 | { |
| 349 | return u"std::addressof(" + addressed + u")" ; |
| 350 | } |
| 351 | |
| 352 | QT_END_NAMESPACE |
| 353 | |