// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmltccompilerpieces.h" #include #include QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; static QString scopeName(const QQmlJSScope::ConstPtr &scope) { Q_ASSERT(scope->isFullyResolved()); const auto scopeType = scope->scopeType(); if (scopeType == QQmlSA::ScopeType::GroupedPropertyScope || scopeType == QQmlSA::ScopeType::AttachedPropertyScope) { return scope->baseType()->internalName(); } return scope->internalName(); } QmltcCodeGenerator::PreparedValue QmltcCodeGenerator::wrap_extensionType(const QQmlJSScope::ConstPtr &type, const QQmlJSMetaProperty &p, const QString &accessor) { Q_ASSERT(type->isFullyResolved()); QStringList prologue; QString value = accessor; QStringList epilogue; auto [owner, ownerKind] = QQmlJSScope::ownerOfProperty(type, p.propertyName()); Q_ASSERT(owner); Q_ASSERT(owner->isFullyResolved()); // properties are only visible when we use QML_{NAMESPACE_}EXTENDED if (ownerKind == QQmlJSScope::ExtensionType) { // extensions is a C++-only feature: Q_ASSERT(!owner->isComposite()); // have to wrap the property into an extension, but we need to figure // out whether the type is QObject-based or not prologue << u"{"_s; const QString extensionObjectName = u"extObject"_s; if (type->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) { // we have a Q_OBJECT. in this case, we call qmlExtendedObject() // function that should return us the extension object. for that we // have to figure out which specific extension we want here int extensionIndex = 0; auto cppBase = QQmlJSScope::nonCompositeBaseType(type); for (auto t = cppBase; t; t = t->baseType()) { if (auto [ext, kind] = t->extensionType(); kind != QQmlJSScope::NotExtension) { if (ext->isSameType(owner)) break; ++extensionIndex; } } prologue << u"static_assert(std::is_base_of<%1, %2>::value);"_s.arg(u"QObject"_s, scopeName(type)); prologue << u"auto %1 = qobject_cast<%2 *>(QQmlPrivate::qmlExtendedObject(%3, %4));"_s .arg(extensionObjectName, owner->internalName(), accessor, QString::number(extensionIndex)); } else { // we have a Q_GADGET. the assumption for extension types is that we // can reinterpret_cast a Q_GADGET object into an extension type // object and then interact with the extension object right away prologue << u"static_assert(sizeof(%1) == sizeof(%2));"_s.arg(scopeName(type), owner->internalName()); prologue << u"static_assert(alignof(%1) == alignof(%2));"_s.arg(scopeName(type), owner->internalName()); prologue << u"auto %1 = reinterpret_cast<%2 *>(%3);"_s.arg( extensionObjectName, owner->internalName(), accessor); } prologue << u"Q_ASSERT(%1);"_s.arg(extensionObjectName); value = extensionObjectName; epilogue << u"}"_s; } return { prologue, value, epilogue }; } void QmltcCodeGenerator::generate_assignToListProperty( QStringList *block, const QQmlJSScope::ConstPtr &type, const QQmlJSMetaProperty &p, const QStringList &values, const QString &accessor, QString &qmlListVarName) { Q_UNUSED(type); // might be needed const bool populateLocalListProperty = qmlListVarName.isEmpty(); if (populateLocalListProperty) { auto [extensionPrologue, extensionAccessor, extensionEpilogue] = QmltcCodeGenerator::wrap_extensionType( type, p, QmltcCodeGenerator::wrap_privateClass(accessor, p)); qmlListVarName = u"listprop_%1"_s.arg(p.propertyName()); QQmlJSScope::ConstPtr valueType = p.type()->valueType(); *block << u"QQmlListProperty<%1> %2;"_s.arg(valueType->internalName(), qmlListVarName); *block << extensionPrologue; *block << u"%1 = %2->%3();"_s.arg(qmlListVarName, extensionAccessor, p.read()); *block << extensionEpilogue; } for (const QString &value : values) { auto [prologue, wrappedValue, epilogue] = QmltcCodeGenerator::wrap_mismatchingTypeConversion(p, value); *block << prologue; *block << u"%1.append(std::addressof(%1), %2);"_s.arg(qmlListVarName, wrappedValue); *block << epilogue; } } void QmltcCodeGenerator::generate_assignToProperty(QStringList *block, const QQmlJSScope::ConstPtr &type, const QQmlJSMetaProperty &p, const QString &value, const QString &accessor, bool constructFromQObject) { Q_ASSERT(block); Q_ASSERT(p.isValid()); Q_ASSERT(!p.isList()); // NB: this code does not handle list properties const QString propertyName = p.propertyName(); if (type->hasOwnProperty(p.propertyName()) && !p.isAlias()) { Q_ASSERT(!p.isPrivate()); // this object is compiled, so just assignment should work fine auto [prologue, wrappedValue, epilogue] = QmltcCodeGenerator::wrap_mismatchingTypeConversion(p, value); *block += prologue; *block << u"%1->m_%2 = %3;"_s.arg(accessor, propertyName, wrappedValue); *block += epilogue; } else if (QString propertySetter = p.write(); !propertySetter.isEmpty() && !QQmlJSUtils::bindablePropertyHasDefaultAccessor( p, QQmlJSUtils::PropertyAccessor_Write)) { // there's a WRITE function auto [prologue, wrappedValue, epilogue] = QmltcCodeGenerator::wrap_mismatchingTypeConversion(p, value); *block += prologue; auto [extensionPrologue, extensionAccessor, extensionEpilogue] = QmltcCodeGenerator::wrap_extensionType( type, p, QmltcCodeGenerator::wrap_privateClass(accessor, p)); *block += extensionPrologue; *block << extensionAccessor + u"->" + propertySetter + u"(" + wrappedValue + u");"; *block += extensionEpilogue; *block += epilogue; } else { // this property is weird, fallback to `setProperty` *block << u"{ // couldn't find property setter, so using QObject::setProperty()"_s; QString val = value; if (constructFromQObject) { const QString variantName = u"var_" + propertyName; *block << u"QVariant " + variantName + u";"; *block << variantName + u".setValue(" + val + u");"; val = u"std::move(" + variantName + u")"; } // NB: setProperty() would handle private properties *block << accessor + u"->setProperty(\"" + propertyName + u"\", " + val + u");"; *block << u"}"_s; } } void QmltcCodeGenerator::generate_setIdValue(QStringList *block, const QString &context, qsizetype index, const QString &accessor, const QString &idString) { Q_ASSERT(index >= 0); *block << u"Q_ASSERT(%1 < %2->numIdValues()); // make sure Id is in bounds"_s.arg(index).arg( context); *block << u"%1->setIdValue(%2 /* id: %3 */, %4);"_s.arg(context, QString::number(index), idString, accessor); } void QmltcCodeGenerator::generate_callExecuteRuntimeFunction( QStringList *block, const QString &url, QQmlJSMetaMethod::AbsoluteFunctionIndex index, const QString &accessor, const QString &returnType, const QList ¶meters) { *block << u"QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(" + accessor + u"));"; const QString returnValueName = u"_ret"_s; QStringList args; args.reserve(parameters.size() + 1); QStringList types; types.reserve(parameters.size() + 1); if (returnType == u"void"_s) { args << u"nullptr"_s; types << u"QMetaType::fromType()"_s; } else { *block << returnType + u" " + returnValueName + u"{};"; // TYPE _ret{}; args << u"const_cast(reinterpret_cast(std::addressof(" + returnValueName + u")))"; types << u"QMetaType::fromType>()"; } for (const QmltcVariable &p : parameters) { args << u"const_cast(reinterpret_cast(std::addressof(" + p.name + u")))"; types << u"QMetaType::fromType>()"; } *block << u"void *_a[] = { " + args.join(u", "_s) + u" };"; *block << u"QMetaType _t[] = { " + types.join(u", "_s) + u" };"; const qsizetype runtimeIndex = static_cast(index); Q_ASSERT(runtimeIndex >= 0); *block << u"e->executeRuntimeFunction(" + url + u", " + QString::number(runtimeIndex) + u", " + accessor + u", " + QString::number(parameters.size()) + u", _a, _t);"; if (returnType != u"void"_s) *block << u"return " + returnValueName + u";"; } void QmltcCodeGenerator::generate_createBindingOnProperty( QStringList *block, const QString &unitVarName, const QString &scope, qsizetype functionIndex, const QString &target, const QQmlJSScope::ConstPtr &targetType, int propertyIndex, const QQmlJSMetaProperty &p, int valueTypeIndex, const QString &subTarget) { const QString propName = QQmlJSUtils::toLiteral(p.propertyName()); if (QString bindable = p.bindable(); !bindable.isEmpty()) { // TODO: test that private properties are bindable QString createBindingForBindable = u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::" u"createBindingForBindable(" + unitVarName + u", " + scope + u", " + QString::number(functionIndex) + u", " + target + u", " + QString::number(propertyIndex) + u", " + QString::number(valueTypeIndex) + u", " + propName + u")"; const QString accessor = (valueTypeIndex == -1) ? target : subTarget; QStringList prologue; QString value = QmltcCodeGenerator::wrap_privateClass(accessor, p); QStringList epilogue; if (targetType) { auto [pro, v, epi] = QmltcCodeGenerator::wrap_extensionType(targetType, p, value); std::tie(prologue, value, epilogue) = std::make_tuple(pro, v, epi); } *block += prologue; *block << u"if (!initializedCache.contains(\"%1\"))"_s.arg(p.propertyName()); *block << u" "_s + value + u"->" + bindable + u"().setBinding(" + createBindingForBindable + u");"; *block += epilogue; } else { QString createBindingForNonBindable = u" "_s + u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::createBindingForNonBindable(" + unitVarName + u", " + scope + u", " + QString::number(functionIndex) + u", " + target + u", " + QString::number(propertyIndex) + u", " + QString::number(valueTypeIndex) + u", " + propName + u")"; // Note: in this version, the binding is set implicitly *block << u"if (!initializedCache.contains(\"%1\"))"_s.arg(p.propertyName()); *block << createBindingForNonBindable + u";"; } } void QmltcCodeGenerator::generate_createTranslationBindingOnProperty( QStringList *block, const TranslationBindingInfo &info) { const QString propName = QQmlJSUtils::toLiteral(info.property.propertyName()); const QString qqmlTranslation = info.data.serializeForQmltc(); if (QString bindable = info.property.bindable(); !bindable.isEmpty()) { // TODO: test that private properties are bindable QString createTranslationCode = uR"(QT_PREPEND_NAMESPACE(QQmlCppBinding) ::createTranslationBindingForBindable(%1, %2, %3, %4, %5))"_s .arg(info.unitVarName, info.target) .arg(info.propertyIndex) .arg(qqmlTranslation, propName); *block << QmltcCodeGenerator::wrap_privateClass(info.target, info.property) + u"->" + bindable + u"().setBinding(" + createTranslationCode + u");"; } else { QString locationString = u"QQmlSourceLocation(%1->fileName(), %2, %3)"_s.arg(info.unitVarName) .arg(info.line) .arg(info.column); QString createTranslationCode = uR"(QT_PREPEND_NAMESPACE(QQmlCppBinding) ::createTranslationBindingForNonBindable( %1, //unit %2, //location %3, //translationData %4, //thisObject %5, //bindingTarget %6, //metaPropertyIndex %7, //propertyName %8) //valueTypePropertyIndex )"_s.arg(info.unitVarName, locationString, qqmlTranslation, info.scope, info.target) .arg(info.propertyIndex) .arg(propName) .arg(info.valueTypeIndex); // Note: in this version, the binding is set implicitly *block << createTranslationCode + u";"; } } QmltcCodeGenerator::PreparedValue QmltcCodeGenerator::wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p, QString value) { auto isDerivedFromBuiltin = [](const QQmlJSScope::ConstPtr &derived, const QString &builtin) { for (QQmlJSScope::ConstPtr t = derived; t; t = t->baseType()) { if (t->internalName() == builtin) return true; } return false; }; QStringList prologue; QStringList epilogue; const QQmlJSScope::ConstPtr propType = p.type(); if (isDerivedFromBuiltin(propType, u"QVariant"_s)) { const QString variantName = u"var_" + p.propertyName(); prologue << u"{ // accepts QVariant"_s; prologue << u"QVariant " + variantName + u";"; prologue << variantName + u".setValue(" + value + u");"; epilogue << u"}"_s; value = u"std::move(" + variantName + u")"; } else if (isDerivedFromBuiltin(propType, u"QJSValue"_s)) { const QString jsvalueName = u"jsvalue_" + p.propertyName(); prologue << u"{ // accepts QJSValue"_s; // Note: do not assume we have the engine, acquire it from `this` prologue << u"auto e = qmlEngine(this);"_s; prologue << u"QJSValue " + jsvalueName + u" = e->toScriptValue(" + value + u");"; epilogue << u"}"_s; value = u"std::move(" + jsvalueName + u")"; } return { prologue, value, epilogue }; } QString QmltcCodeGenerator::wrap_privateClass(const QString &accessor, const QQmlJSMetaProperty &p) { if (!p.isPrivate()) return accessor; const QString privateType = p.privateClass(); return u"static_cast<" + privateType + u" *>(QObjectPrivate::get(" + accessor + u"))"; } QString QmltcCodeGenerator::wrap_qOverload(const QList ¶meters, const QString &overloaded) { QStringList types; types.reserve(parameters.size()); for (const QmltcVariable &p : parameters) types.emplaceBack(p.cppType); return u"qOverload<" + types.join(u", "_s) + u">(" + overloaded + u")"; } QString QmltcCodeGenerator::wrap_addressof(const QString &addressed) { return u"std::addressof(" + addressed + u")"; } QT_END_NAMESPACE