qtdeclarative/tools/qmltc/qmltccompiler.cpp

2050 lines
90 KiB
C++

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qmltccompiler.h"
#include "qmltcoutputir.h"
#include "qmltccodewriter.h"
#include "qmltcpropertyutils.h"
#include "qmltccompilerpieces.h"
#include <QtCore/qloggingcategory.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <private/qqmljsutils_p.h>
#include <algorithm>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
bool qIsReferenceTypeList(const QQmlJSMetaProperty &p)
{
if (QQmlJSScope::ConstPtr type = p.type())
return type->isListProperty();
return false;
}
static QList<QQmlJSMetaProperty> unboundRequiredProperties(
const QQmlJSScope::ConstPtr &type,
QmltcTypeResolver *resolver
) {
QList<QQmlJSMetaProperty> requiredProperties{};
auto isPropertyRequired = [&type, &resolver](const auto &property) {
if (!type->isPropertyRequired(property.propertyName()))
return false;
if (type->hasPropertyBindings(property.propertyName()))
return false;
if (property.isAlias()) {
QQmlJSUtils::AliasResolutionVisitor aliasVisitor;
QQmlJSUtils::ResolvedAlias result =
QQmlJSUtils::resolveAlias(resolver, property, type, aliasVisitor);
if (result.kind != QQmlJSUtils::AliasTarget_Property)
return false;
// If the top level alias targets a property that is in
// the top level scope and that property is required, then
// we will already pick up the property during one of the
// iterations.
// Setting the property or the alias is the same so we
// discard one of the two, as otherwise we would require
// the user to pass two values for the same property ,in
// this case the alias.
//
// For example in:
//
// ```
// Item {
// id: self
// required property int foo
// property alias bar: self.foo
// }
// ```
//
// Both foo and bar are required but setting one or the
// other is the same operation so that we should choose
// only one.
if (result.owner == type &&
type->isPropertyRequired(result.property.propertyName()))
return false;
if (result.owner->hasPropertyBindings(result.property.propertyName()))
return false;
}
return true;
};
const auto properties = type->properties();
std::copy_if(properties.cbegin(), properties.cend(),
std::back_inserter(requiredProperties), isPropertyRequired);
std::sort(requiredProperties.begin(), requiredProperties.end(),
[](const auto &left, const auto &right) {
return left.propertyName() < right.propertyName();
});
return requiredProperties;
}
// Populates the internal representation for a
// RequiredPropertiesBundle, a class that acts as a bundle of initial
// values that should be set for the required properties of a type.
static void compileRequiredPropertiesBundle(
QmltcType &current,
const QQmlJSScope::ConstPtr &type,
QmltcTypeResolver *resolver
) {
QList<QQmlJSMetaProperty> requiredProperties = unboundRequiredProperties(type, resolver);
if (requiredProperties.isEmpty())
return;
current.requiredPropertiesBundle.emplace();
current.requiredPropertiesBundle->name = u"RequiredPropertiesBundle"_s;
current.requiredPropertiesBundle->members.reserve(requiredProperties.size());
std::transform(requiredProperties.cbegin(), requiredProperties.cend(),
std::back_inserter(current.requiredPropertiesBundle->members),
[](const QQmlJSMetaProperty &property) {
QString type = qIsReferenceTypeList(property)
? u"const QList<%1*>&"_s.arg(
property.type()->elementType()->internalName())
: u"passByConstRefOrValue<%1>"_s.arg(
property.type()->augmentedInternalName());
return QmltcVariable{ type, property.propertyName() };
});
}
static void compileRootExternalConstructorBody(
QmltcType& current,
const QQmlJSScope::ConstPtr &type
) {
current.externalCtor.body << u"// document root:"_s;
// if it's document root, we want to create our QQmltcObjectCreationBase
// that would store all the created objects
current.externalCtor.body << u"QQmltcObjectCreationBase<%1> objectHolder;"_s.arg(
type->internalName());
current.externalCtor.body
<< u"QQmltcObjectCreationHelper creator = objectHolder.view();"_s;
current.externalCtor.body << u"creator.set(0, this);"_s; // special case
QString initializerName = u"initializer"_s;
if (current.requiredPropertiesBundle) {
// Compose new initializer based on the initial values for required properties.
current.externalCtor.body << u"auto newInitializer = [&](auto& propertyInitializer) {"_s;
for (const auto& member : current.requiredPropertiesBundle->members) {
current.externalCtor.body << u" propertyInitializer.%1(requiredPropertiesBundle.%2);"_s.arg(
QmltcPropertyData(member.name).write, member.name
);
}
current.externalCtor.body << u" initializer(propertyInitializer);"_s;
current.externalCtor.body << u"};"_s;
initializerName = u"newInitializer"_s;
}
// now call init
current.externalCtor.body << current.init.name
+ u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* "
u"endInit */ true, %1);"_s.arg(initializerName);
};
Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg);
const QString QmltcCodeGenerator::privateEngineName = u"ePriv"_s;
const QString QmltcCodeGenerator::typeCountName = u"q_qmltc_typeCount"_s;
QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor,
QQmlJSLogger *logger)
: m_url(url), m_typeResolver(resolver), m_visitor(visitor), m_logger(logger)
{
Q_UNUSED(m_typeResolver);
Q_ASSERT(!hasErrors());
}
// needed due to std::unique_ptr<CodeGenerator> with CodeGenerator being
// incomplete type in the header (~std::unique_ptr<> fails with a static_assert)
QmltcCompiler::~QmltcCompiler() = default;
QString QmltcCompiler::newSymbol(const QString &base)
{
QString symbol = base;
symbol.replace(QLatin1String("."), QLatin1String("_"));
while (symbol.startsWith(QLatin1Char('_')) && symbol.size() >= 2
&& (symbol[1].isUpper() || symbol[1] == QLatin1Char('_'))) {
symbol.remove(0, 1);
}
if (!m_symbols.contains(symbol)) {
m_symbols.insert(symbol, 1);
} else {
symbol += u"_" + QString::number(m_symbols[symbol]++);
}
return symbol;
}
void QmltcCompiler::compile(const QmltcCompilerInfo &info)
{
m_info = info;
Q_ASSERT(!m_info.outputCppFile.isEmpty());
Q_ASSERT(!m_info.outputHFile.isEmpty());
Q_ASSERT(!m_info.resourcePath.isEmpty());
// Note: we only compile "pure" QML types. any component-wrapped type is
// expected to appear through a binding
const auto isComponent = [](const QQmlJSScope::ConstPtr &type) {
auto base = type->baseType();
return base && base->internalName() == u"QQmlComponent"_s;
};
QmltcCodeGenerator generator { m_url, m_visitor };
QmltcMethod urlMethod;
compileUrlMethod(urlMethod, generator.urlMethodName());
m_urlMethodName = urlMethod.name;
// sort inline components to compile them in the right order
// a inherits b => b needs to be defined in the cpp file before a!
// r is the root => r needs to be compiled at the end!
// otherwise => sort them by inline component names to have consistent output
auto sortedInlineComponentNames = m_visitor->inlineComponentNames();
std::sort(sortedInlineComponentNames.begin(), sortedInlineComponentNames.end(),
[&](const InlineComponentOrDocumentRootName &a,
const InlineComponentOrDocumentRootName &b) {
const auto *inlineComponentAName = std::get_if<InlineComponentNameType>(&a);
const auto *inlineComponentBName = std::get_if<InlineComponentNameType>(&b);
if (inlineComponentAName == inlineComponentBName)
return false;
// the root comes at last, so (a < b) == true when b is the root and a is not
if (inlineComponentAName && !inlineComponentBName)
return true;
// b requires a to be declared before b when b inherits from a, therefore (a < b)
// == true
if (inlineComponentAName && inlineComponentBName) {
QQmlJSScope::ConstPtr inlineComponentA = m_visitor->inlineComponent(a);
QQmlJSScope::ConstPtr inlineComponentB = m_visitor->inlineComponent(b);
if (inlineComponentB->inherits(inlineComponentA)) {
return true;
} else if (inlineComponentA->inherits(inlineComponentB)) {
return false;
} else {
// fallback to default sorting based on names
return *inlineComponentAName < *inlineComponentBName;
}
}
Q_ASSERT(!inlineComponentAName || !inlineComponentBName);
// a is the root or both a and b are the root
return false;
});
QList<QmltcType> compiledTypes;
for (const auto &inlineComponent : sortedInlineComponentNames) {
const QList<QQmlJSScope::ConstPtr> &pureTypes = m_visitor->pureQmlTypes(inlineComponent);
Q_ASSERT(!pureTypes.empty());
const QQmlJSScope::ConstPtr &root = pureTypes.front();
if (isComponent(root)) {
compiledTypes.emplaceBack(); // create empty type
const auto compile = [&](QmltcType &current, const QQmlJSScope::ConstPtr &type) {
generator.generate_initCodeForTopLevelComponent(current, type);
};
compileType(compiledTypes.back(), root, compile);
} else {
const auto compile = [this](QmltcType &current, const QQmlJSScope::ConstPtr &type) {
compileTypeElements(current, type);
};
for (const auto &type : pureTypes) {
Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope);
compiledTypes.emplaceBack(); // create empty type
compileType(compiledTypes.back(), type, compile);
}
}
}
if (hasErrors())
return;
QmltcProgram program;
program.url = m_url;
program.cppPath = m_info.outputCppFile;
program.hPath = m_info.outputHFile;
program.outNamespace = m_info.outputNamespace;
program.exportMacro = m_info.exportMacro;
program.compiledTypes = compiledTypes;
program.includes = m_visitor->cppIncludeFiles();
if (!m_info.exportMacro.isEmpty() && !m_info.exportInclude.isEmpty())
program.includes += (m_info.exportInclude);
program.urlMethod = urlMethod;
QmltcOutput out;
QmltcOutputWrapper code(out);
QmltcCodeWriter::write(code, program);
}
void QmltcCompiler::compileUrlMethod(QmltcMethod &urlMethod, const QString &urlMethodName)
{
urlMethod.name = urlMethodName;
urlMethod.returnType = u"const QUrl&"_s;
urlMethod.body << u"static QUrl url {QStringLiteral(\"qrc:%1\")};"_s.arg(m_info.resourcePath);
urlMethod.body << u"return url;"_s;
urlMethod.declarationPrefixes << u"static"_s;
urlMethod.modifiers << u"noexcept"_s;
}
void QmltcCompiler::compileType(
QmltcType &current, const QQmlJSScope::ConstPtr &type,
std::function<void(QmltcType &, const QQmlJSScope::ConstPtr &)> compileElements)
{
Q_ASSERT(!type->internalName().isEmpty());
current.cppType = type->internalName();
Q_ASSERT(!type->baseType()->internalName().isEmpty());
const QString baseClass = type->baseType()->internalName();
const auto rootType = m_visitor->result();
const InlineComponentOrDocumentRootName name = type->enclosingInlineComponentName();
QQmlJSScope::ConstPtr inlineComponentType = m_visitor->inlineComponent(name);
Q_ASSERT(inlineComponentType);
const bool documentRoot = (type == rootType);
const bool inlineComponent = type->isInlineComponent();
const bool isAnonymous = !documentRoot || type->internalName().at(0).isLower();
const bool isSingleton = type->isSingleton();
QmltcCodeGenerator generator { m_url, m_visitor };
current.baseClasses = { baseClass };
if (!documentRoot) {
// make document root a friend to allow it to access init and endInit
const QString rootInternalName =
m_visitor->inlineComponent(type->enclosingInlineComponentName())->internalName();
if (rootInternalName != current.cppType) // avoid GCC13 warning on self-befriending
current.otherCode << "friend class %1;"_L1.arg(rootInternalName);
}
if (documentRoot || inlineComponent) {
auto name = type->inlineComponentName()
? InlineComponentOrDocumentRootName(*type->inlineComponentName())
: InlineComponentOrDocumentRootName(RootDocumentNameType());
// make QQmltcObjectCreationBase<DocumentRoot> a friend to allow it to
// be created for the root object
current.otherCode << u"friend class QQmltcObjectCreationBase<%1>;"_s.arg(
inlineComponentType->internalName());
// generate typeCount for all components (root + inlineComponents)
QmltcMethod typeCountMethod;
typeCountMethod.name = QmltcCodeGenerator::typeCountName;
typeCountMethod.returnType = u"uint"_s;
typeCountMethod.body << u"return " + generator.generate_typeCount(name) + u";";
current.typeCount = typeCountMethod;
} else {
// make an immediate parent a friend since that parent
// would create the object through a non-public constructor
const auto realQmlScope = [](const QQmlJSScope::ConstPtr &scope) {
if (scope->isArrayScope())
return scope->parentScope();
return scope;
};
const auto& realScope = realQmlScope(type->parentScope());
if (realScope != rootType) {
current.otherCode << u"friend class %1;"_s.arg(realScope->internalName());
}
}
// make QQmltcObjectCreationHelper a friend of every type since it provides
// useful helper methods for all types
current.otherCode << u"friend class QT_PREPEND_NAMESPACE(QQmltcObjectCreationHelper);"_s;
current.mocCode = {
u"Q_OBJECT"_s,
// Note: isAnonymous holds for non-root types in the document as well
type->isInlineComponent() ? (u"QML_NAMED_ELEMENT(%1)"_s.arg(*type->inlineComponentName()))
: (isAnonymous ? u"QML_ANONYMOUS"_s : u"QML_ELEMENT"_s),
};
// add special member functions
current.baselineCtor.access = QQmlJSMetaMethod::Protected;
if (documentRoot || inlineComponent || isSingleton) {
current.externalCtor.access = QQmlJSMetaMethod::Public;
} else {
current.externalCtor.access = QQmlJSMetaMethod::Protected;
}
current.init.access = QQmlJSMetaMethod::Protected;
current.beginClass.access = QQmlJSMetaMethod::Protected;
current.endInit.access = QQmlJSMetaMethod::Protected;
current.setComplexBindings.access = QQmlJSMetaMethod::Protected;
current.completeComponent.access = QQmlJSMetaMethod::Protected;
current.finalizeComponent.access = QQmlJSMetaMethod::Protected;
current.handleOnCompleted.access = QQmlJSMetaMethod::Protected;
current.propertyInitializer.name = u"PropertyInitializer"_s;
current.propertyInitializer.constructor.access = QQmlJSMetaMethod::Public;
current.propertyInitializer.constructor.name = current.propertyInitializer.name;
current.propertyInitializer.constructor.parameterList = {
QmltcVariable(u"%1&"_s.arg(current.cppType), u"component"_s)
};
current.propertyInitializer.component.cppType = current.cppType + u"&";
current.propertyInitializer.component.name = u"component"_s;
current.propertyInitializer.initializedCache.cppType = u"QSet<QString>"_s;
current.propertyInitializer.initializedCache.name = u"initializedCache"_s;
current.baselineCtor.name = current.cppType;
current.externalCtor.name = current.cppType;
current.init.name = u"QML_init"_s;
current.init.returnType = u"QQmlRefPointer<QQmlContextData>"_s;
current.beginClass.name = u"QML_beginClass"_s;
current.beginClass.returnType = u"void"_s;
current.endInit.name = u"QML_endInit"_s;
current.endInit.returnType = u"void"_s;
current.setComplexBindings.name = u"QML_setComplexBindings"_s;
current.setComplexBindings.returnType = u"void"_s;
current.completeComponent.name = u"QML_completeComponent"_s;
current.completeComponent.returnType = u"void"_s;
current.finalizeComponent.name = u"QML_finalizeComponent"_s;
current.finalizeComponent.returnType = u"void"_s;
current.handleOnCompleted.name = u"QML_handleOnCompleted"_s;
current.handleOnCompleted.returnType = u"void"_s;
QmltcVariable creator(u"QQmltcObjectCreationHelper*"_s, u"creator"_s);
QmltcVariable engine(u"QQmlEngine*"_s, u"engine"_s);
QmltcVariable parent(u"QObject*"_s, u"parent"_s, u"nullptr"_s);
QmltcVariable initializedCache(
u"[[maybe_unused]] const QSet<QString>&"_s,
u"initializedCache"_s,
u"{}"_s
);
QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_s, u"parentContext"_s);
QmltcVariable finalizeFlag(u"bool"_s, u"canFinalize"_s);
current.baselineCtor.parameterList = { parent };
current.endInit.parameterList = { creator, engine };
current.setComplexBindings.parameterList = { creator, engine, initializedCache };
current.handleOnCompleted.parameterList = { creator };
if (documentRoot || inlineComponent) {
const QmltcVariable initializer(
u"[[maybe_unused]] qxp::function_ref<void(%1&)>"_s.arg(current.propertyInitializer.name),
u"initializer"_s,
u"[](%1&){}"_s.arg(current.propertyInitializer.name));
current.init.parameterList = { creator, engine, ctxtdata, finalizeFlag, initializer };
current.beginClass.parameterList = { creator, finalizeFlag };
current.completeComponent.parameterList = { creator, finalizeFlag };
current.finalizeComponent.parameterList = { creator, finalizeFlag };
compileRequiredPropertiesBundle(current, type, m_typeResolver);
if (current.requiredPropertiesBundle) {
QmltcVariable bundle{
u"const %1&"_s.arg(current.requiredPropertiesBundle->name),
u"requiredPropertiesBundle"_s,
};
current.externalCtor.parameterList = { engine, bundle, parent, initializer };
} else {
current.externalCtor.parameterList = { engine, parent, initializer };
}
} else {
current.externalCtor.parameterList = { creator, engine, parent };
current.init.parameterList = { creator, engine, ctxtdata };
current.beginClass.parameterList = { creator };
current.completeComponent.parameterList = { creator };
current.finalizeComponent.parameterList = { creator };
}
current.externalCtor.initializerList = { current.baselineCtor.name + u"(" + parent.name
+ u")" };
if (QQmlJSUtils::hasCompositeBase(type)) {
// call parent's (QML type's) basic ctor from this. that one will take
// care about QObject::setParent()
current.baselineCtor.initializerList = { baseClass + u"(" + parent.name + u")" };
} else {
// default call to ctor is enough, but QQml_setParent_noEvent() is
// needed (note: faster? version of QObject::setParent())
current.baselineCtor.body << u"QQml_setParent_noEvent(this, " + parent.name + u");";
}
// compilation stub:
current.externalCtor.body << u"Q_UNUSED(engine)"_s;
if (documentRoot || inlineComponent) {
compileRootExternalConstructorBody(current, type);
} else {
current.externalCtor.body << u"// not document root:"_s;
// just call init, we don't do any setup here otherwise
current.externalCtor.body << current.init.name
+ u"(creator, engine, QQmlData::get(parent)->outerContext);";
}
if (isSingleton) {
// see https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON for context
current.mocCode.append(u"QML_SINGLETON"_s);
auto &staticCreate = current.staticCreate.emplace();
staticCreate.comments
<< u"Used by the engine for singleton creation."_s
<< u"See also \\l {https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON}."_s;
staticCreate.type = QQmlJSMetaMethodType::StaticMethod;
staticCreate.access = QQmlJSMetaMethod::Public;
staticCreate.name = u"create"_s;
staticCreate.returnType = u"%1 *"_s.arg(current.cppType);
QmltcVariable jsEngine(u"QJSEngine*"_s, u"jsEngine"_s);
staticCreate.parameterList = { engine, jsEngine };
staticCreate.body << u"Q_UNUSED(jsEngine);"_s
<< u"%1 *result = new %1(engine, nullptr);"_s.arg(current.cppType)
<< u"return result;"_s;
}
auto postponedQmlContextSetup = generator.generate_initCode(current, type);
generator.generate_endInitCode(current, type);
generator.generate_setComplexBindingsCode(current, type);
generator.generate_beginClassCode(current, type);
generator.generate_completeComponentCode(current, type);
generator.generate_finalizeComponentCode(current, type);
generator.generate_handleOnCompletedCode(current, type);
compileElements(current, type);
}
template<typename Iterator>
static Iterator partitionBindings(Iterator first, Iterator last)
{
// NB: the code generator cares about script bindings being processed at a
// later point, so we should sort or partition the range. we do a stable
// partition since the relative order of binding evaluation affects the UI
return std::stable_partition(first, last, [](const QQmlJSMetaPropertyBinding &b) {
// we want complex bindings to be at the end, so do the negation
return !QmltcCompiler::isComplexBinding(b);
});
}
// Populates the propertyInitializer of the current type based on the
// available properties.
//
// A propertyInitializer is a generated class that provides a
// restricted interface that only allows setting property values and
// internally keep tracks of which properties where actually set,
// intended to be used to allow the user to set up the initial values
// when creating an instance of a component.
//
// For each property of the current type that is known, is not private
// and is writable, a setter method is generated.
// Each setter method knows how to set a specific property, so as to
// provide a strongly typed interface to property setting, as if the
// relevant C++ type was used directly.
//
// Each setter uses the write method for the proprerty when available
// and otherwise falls back to a the more generic
// `QObject::setProperty` for properties where a WRITE method is not
// available or in scope.
void QmltcCompiler::compilePropertyInitializer(
QmltcType &current, const QQmlJSScope::ConstPtr &type) {
static auto isFromExtension
= [](const QQmlJSMetaProperty &property, const QQmlJSScope::ConstPtr &scope) {
return scope->ownerOfProperty(scope, property.propertyName()).extensionSpecifier
!= QQmlJSScope::NotExtension;
};
current.propertyInitializer.constructor.initializerList << u"component{component}"_s;
const auto properties = type->properties().values();
for (const auto &property: properties) {
if (property.index() == -1) continue;
if (property.isPrivate()) continue;
if (!property.isWritable() && !qIsReferenceTypeList(property)) continue;
const QString name = property.propertyName();
const auto propertyType = property.type();
if (propertyType.isNull()) {
recordError(type->sourceLocation(), u"Type of property '%1' is unknown"_s.arg(name));
continue;
}
current.propertyInitializer.propertySetters.emplace_back();
auto& compiledSetter = current.propertyInitializer.propertySetters.back();
compiledSetter.userVisible = true;
compiledSetter.returnType = u"void"_s;
compiledSetter.name = QmltcPropertyData(property).write;
if (qIsReferenceTypeList(property)) {
compiledSetter.parameterList.emplaceBack(
QQmlJSUtils::constRefify(
u"QList<%1*>"_s.arg(propertyType->elementType()->internalName())),
name + u"_", QString()
);
} else {
compiledSetter.parameterList.emplaceBack(
QQmlJSUtils::constRefify(getUnderlyingType(property)), name + u"_", QString()
);
}
if (qIsReferenceTypeList(property)) {
compiledSetter.body << u"QQmlListReference list_ref_(&%1, \"%2\");"_s.arg(
current.propertyInitializer.component.name, name
);
compiledSetter.body << u"list_ref_.clear();"_s;
compiledSetter.body << u"for (const auto& list_item_ : %1_)"_s.arg(name);
compiledSetter.body << u" list_ref_.append(list_item_);"_s;
} else if (
QQmlJSUtils::bindablePropertyHasDefaultAccessor(property, QQmlJSUtils::PropertyAccessor_Write)
) {
compiledSetter.body << u"%1.%2().setValue(%3_);"_s.arg(
current.propertyInitializer.component.name, property.bindable(), name);
} else if (type->hasOwnProperty(name)) {
compiledSetter.body << u"%1.%2(%3_);"_s.arg(
current.propertyInitializer.component.name, QmltcPropertyData(property).write, name);
} else if (property.write().isEmpty() || isFromExtension(property, type)) {
// We can end here if a WRITE method is not available or
// if the method is available but not in this scope, so
// that we fallback to the string-based setters..
//
// For example, types that makes use of QML_EXTENDED
// types, will have the extension types properties
// available and with a WRITE method, but the WRITE method
// will not be available to the extended type, from C++,
// as the type does not directly inherit from the
// extension type.
//
// We specifically scope `setProperty` to `QObject` as
// certain types might have shadowed the method.
// For example, in QtQuick, some types have a property
// called `property` with a `setProperty` WRITE method
// that will produce the shadowing.
compiledSetter.body << u"%1.QObject::setProperty(\"%2\", QVariant::fromValue(%2_));"_s.arg(
current.propertyInitializer.component.name, name);
} else {
compiledSetter.body << u"%1.%2(%3_);"_s.arg(
current.propertyInitializer.component.name, property.write(), name);
}
compiledSetter.body << u"%1.insert(QStringLiteral(\"%2\"));"_s.arg(
current.propertyInitializer.initializedCache.name, name);
}
}
void QmltcCompiler::compileTypeElements(QmltcType &current, const QQmlJSScope::ConstPtr &type)
{
// compile components of a type:
// - enums
// - properties
// - methods
// - bindings
const auto enums = type->ownEnumerations();
current.enums.reserve(enums.size());
for (auto it = enums.begin(); it != enums.end(); ++it)
compileEnum(current, it.value());
auto properties = type->ownProperties().values();
current.properties.reserve(properties.size());
// Note: index() is the (future) meta property index, so make sure given
// properties are ordered by that index before compiling
std::sort(properties.begin(), properties.end(),
[](const QQmlJSMetaProperty &x, const QQmlJSMetaProperty &y) {
return x.index() < y.index();
});
for (const QQmlJSMetaProperty &p : std::as_const(properties)) {
if (p.index() == -1) {
recordError(type->sourceLocation(),
u"Internal error: property '%1' has incomplete information"_s.arg(
p.propertyName()));
continue;
}
if (p.isAlias()) {
compileAlias(current, p, type);
} else {
compileProperty(current, p, type);
}
}
const auto methods = type->ownMethods();
for (const QQmlJSMetaMethod &m : methods)
compileMethod(current, m, type);
auto bindings = type->ownPropertyBindingsInQmlIROrder();
partitionBindings(bindings.begin(), bindings.end());
compilePropertyInitializer(current, type);
compileBinding(current, bindings.begin(), bindings.end(), type, { type });
}
void QmltcCompiler::compileEnum(QmltcType &current, const QQmlJSMetaEnum &e)
{
const auto intValues = e.values();
QStringList values;
values.reserve(intValues.size());
std::transform(intValues.cbegin(), intValues.cend(), std::back_inserter(values),
[](int x) { return QString::number(x); });
// structure: (C++ type name, enum keys, enum values, MOC line)
current.enums.emplaceBack(e.name(), e.keys(), std::move(values),
u"Q_ENUM(%1)"_s.arg(e.name()));
}
static QList<QmltcVariable>
compileMethodParameters(const QList<QQmlJSMetaParameter> &parameterInfos, bool allowUnnamed = false)
{
QList<QmltcVariable> parameters;
const auto size = parameterInfos.size();
parameters.reserve(size);
for (qsizetype i = 0; i < size; ++i) {
const auto &p = parameterInfos[i];
Q_ASSERT(p.type()); // assume verified
QString name = p.name();
Q_ASSERT(allowUnnamed || !name.isEmpty()); // assume verified
if (name.isEmpty() && allowUnnamed)
name = u"unnamed_" + QString::number(i);
QString internalName;
const QQmlJSScope::AccessSemantics semantics = p.type()->accessSemantics();
switch (semantics) {
case QQmlJSScope::AccessSemantics::Reference:
if (p.typeQualifier() == QQmlJSMetaParameter::Const)
internalName = u"const "_s;
internalName += u"%1*"_s.arg(p.type()->internalName());
break;
case QQmlJSScope::AccessSemantics::Value:
case QQmlJSScope::AccessSemantics::Sequence:
internalName = u"passByConstRefOrValue<%1>"_s.arg(p.type()->internalName());
break;
case QQmlJSScope::AccessSemantics::None:
Q_ASSERT(false); // or maybe print an error message
}
parameters.emplaceBack(internalName, name, QString());
}
return parameters;
}
void QmltcCompiler::compileMethod(QmltcType &current, const QQmlJSMetaMethod &m,
const QQmlJSScope::ConstPtr &owner)
{
const QString returnType = m.returnType()->augmentedInternalName();
const QList<QmltcVariable> compiledParams = compileMethodParameters(m.parameters());
const auto methodType = m.methodType();
QStringList code;
if (methodType != QQmlJSMetaMethodType::Signal) {
QmltcCodeGenerator urlGenerator { m_url, m_visitor };
QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
&code, urlGenerator.urlMethodName() + u"()",
owner->ownRuntimeFunctionIndex(m.jsFunctionIndex()), u"this"_s, returnType,
compiledParams);
}
QmltcMethod compiled {};
compiled.returnType = returnType;
compiled.name = m.methodName();
compiled.parameterList = std::move(compiledParams);
compiled.body = std::move(code);
compiled.type = methodType;
compiled.access = m.access();
if (methodType != QQmlJSMetaMethodType::Signal) {
compiled.declarationPrefixes << u"Q_INVOKABLE"_s;
compiled.userVisible = m.access() == QQmlJSMetaMethod::Public;
} else {
compiled.userVisible = !m.isImplicitQmlPropertyChangeSignal();
}
current.functions.emplaceBack(compiled);
}
/*! \internal
Compiles an extra set of methods for Lists, that makes manipulating lists easier from C++
for the user.
*/
void QmltcCompiler::compileExtraListMethods(QmltcType &current, const QQmlJSMetaProperty &p)
{
QmltcPropertyData data(p);
const QString elementType = p.type()->elementType()->internalName() + u'*';
const QString variableName = data.read + u"()"_s;
const QStringList ownershipWarning = {
u"\\note {This method does not change the ownership of its argument."_s,
u"The caller is responsible for setting the argument's \\c {QObject::parent} or"_s,
u"for ensuring that the argument lives long enough."_s,
u"For example, an argument created with \\c {createObject()} that has no parent"_s,
u"will eventually be garbage-collected, leaving a dangling pointer.}"_s
};
// generate append() sugar for users
{
QmltcMethod append{};
append.comments.emplaceBack(u"\\brief Append an element to %1."_s.arg(data.read));
append.comments << ownershipWarning;
append.returnType = u"void"_s;
append.name = u"%1Append"_s.arg(data.read);
append.parameterList.emplaceBack(elementType, u"toBeAppended"_s);
append.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName);
append.body
<< u"q_qmltc_localList.append(std::addressof(q_qmltc_localList), toBeAppended);"_s;
// append.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 is
// resolved
append.userVisible = true;
current.functions.emplaceBack(append);
}
// generate count() sugar for users
{
QmltcMethod count{};
count.comments.emplaceBack(u"\\brief Number of elements in %1."_s.arg(data.read));
count.returnType = u"int"_s;
count.name = u"%1Count"_s.arg(data.read);
count.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName);
count.body << u"int result = q_qmltc_localList.count(std::addressof(q_qmltc_localList));"_s;
count.body << u"return result;"_s;
count.userVisible = true;
current.functions.emplaceBack(count);
}
// generate at() sugar for users
{
QmltcMethod at{};
at.comments.emplaceBack(u"\\brief Access an element in %1."_s.arg(data.read));
at.returnType = elementType;
at.name = u"%1At"_s.arg(data.read);
at.parameterList.emplaceBack(u"qsizetype"_s, u"position"_s, QString());
at.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName);
at.body << u"auto result = q_qmltc_localList.at(std::addressof(q_qmltc_localList), position);"_s;
at.body << u"return result;"_s;
at.userVisible = true;
current.functions.emplaceBack(at);
}
// generate clear() sugar for users
{
QmltcMethod clear{};
clear.comments.emplaceBack(u"\\brief Clear %1."_s.arg(data.read));
clear.returnType = u"void"_s;
clear.name = u"%1Clear"_s.arg(data.read);
clear.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName);
clear.body << u"q_qmltc_localList.clear(std::addressof(q_qmltc_localList));"_s;
// clear.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 is
// resolved
clear.userVisible = true;
current.functions.emplaceBack(clear);
}
// generate replace() sugar for users
{
QmltcMethod replace{};
replace.comments.emplaceBack(u"\\brief Replace an element in %1."_s.arg(data.read));
replace.comments << ownershipWarning;
replace.returnType = u"void"_s;
replace.name = u"%1Replace"_s.arg(data.read);
replace.parameterList.emplaceBack(u"qsizetype"_s, u"position"_s, QString());
replace.parameterList.emplaceBack(elementType, u"element"_s,
QString());
replace.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName);
replace.body
<< u"q_qmltc_localList.replace(std::addressof(q_qmltc_localList), position, element);"_s;
// replace.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587
// is resolved
replace.userVisible = true;
current.functions.emplaceBack(replace);
}
// generate removeLast() sugar for users
{
QmltcMethod removeLast{};
removeLast.comments.emplaceBack(u"\\brief Remove the last element in %1."_s.arg(data.read));
removeLast.returnType = u"void"_s;
removeLast.name = u"%1RemoveLast"_s.arg(data.read);
removeLast.body << u"auto q_qmltc_localList = %1;"_s.arg(variableName);
removeLast.body << u"q_qmltc_localList.removeLast(std::addressof(q_qmltc_localList));"_s;
// removeLast.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when
// QTBUG-106587 is resolved
removeLast.userVisible = true;
current.functions.emplaceBack(removeLast);
}
}
void QmltcCompiler::compileProperty(QmltcType &current, const QQmlJSMetaProperty &p,
const QQmlJSScope::ConstPtr &owner)
{
Q_ASSERT(!p.isAlias()); // will be handled separately
Q_ASSERT(p.type());
const QString name = p.propertyName();
const QString variableName = u"m_" + name;
const QString underlyingType = getUnderlyingType(p);
if (qIsReferenceTypeList(p)) {
const QString storageName = variableName + u"_storage";
current.variables.emplaceBack(
u"QList<" + p.type()->elementType()->internalName() + u"*>", storageName,
QString());
current.baselineCtor.initializerList.emplaceBack(variableName + u"(" + underlyingType
+ u"(this, std::addressof(" + storageName
+ u")))");
compileExtraListMethods(current, p);
}
// along with property, also add relevant moc code, so that we can use the
// property in Qt/QML contexts
QStringList mocPieces;
mocPieces.reserve(10);
mocPieces << underlyingType << name;
QmltcPropertyData compilationData(p);
// 1. add setter and getter
// If p.isList(), it's a QQmlListProperty. Then you can write the underlying list through
// the QQmlListProperty object retrieved with the getter. Setting it would make no sense.
QmltcMethod getter{};
getter.returnType = underlyingType;
getter.name = compilationData.read;
getter.body << u"return " + variableName + u".value();";
getter.userVisible = true;
current.functions.emplaceBack(getter);
mocPieces << u"READ"_s << getter.name;
if (p.isWritable() && !qIsReferenceTypeList(p)) {
QmltcMethod setter {};
setter.returnType = u"void"_s;
setter.name = compilationData.write;
// QmltcVariable
setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(underlyingType), name + u"_",
u""_s);
setter.body << variableName + u".setValue(" + name + u"_);";
setter.body << u"Q_EMIT " + compilationData.notify + u"();";
setter.userVisible = true;
current.functions.emplaceBack(setter);
mocPieces << u"WRITE"_s << setter.name;
}
// 2. add bindable
if (!qIsReferenceTypeList(p)) {
QmltcMethod bindable {};
bindable.returnType = u"QBindable<" + underlyingType + u">";
bindable.name = compilationData.bindable;
bindable.body << u"return QBindable<" + underlyingType + u">(std::addressof(" + variableName
+ u"));";
bindable.userVisible = true;
current.functions.emplaceBack(bindable);
mocPieces << u"BINDABLE"_s << bindable.name;
}
// 3. add/check notify (actually, this is already done inside QmltcVisitor)
if (owner->isPropertyRequired(name))
mocPieces << u"REQUIRED"_s;
// 4. add moc entry
// e.g. Q_PROPERTY(QString p READ getP WRITE setP BINDABLE bindableP)
current.mocCode << u"Q_PROPERTY(" + mocPieces.join(u" "_s) + u")";
// 5. add extra moc entry if this property is marked default
if (name == owner->defaultPropertyName())
current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(name);
// structure: (C++ type name, name, C++ class name, C++ signal name)
current.properties.emplaceBack(underlyingType, variableName, current.cppType,
compilationData.notify);
}
/*!
* \internal
*
* Models one step of the alias resolution. If the current alias to be resolved
* points to \c {x.y.z} and that \c {x.y} is already resolved, then this struct
* contains the information on how to obtain the \c {z} part from \c {x.y}.
*/
struct AliasResolutionFrame
{
/*!
* \internal
*
* Placeholder for the current resolved state. It is replaced later with
* the result from previous resolutions from the \c QStack<AliasResolutionFrame>.
*
* \sa unpackFrames()
*/
static QString inVar;
/*!
* \internal
*
* Steps to access this value as a list of C++ statements, to be used in
* conjunction with \c {epilogue}.
*/
QStringList prologue;
/*!
* \internal
*
* Steps to finish the statements of the \c prologue (e.g. closing brackets).
*/
QStringList epilogue;
/*!
* \internal
*
* Instructions on how to write the property, after it was loaded with the
* instructions from \c prologue. Has to happen before \c epilogue.
*/
QStringList epilogueForWrite;
/*!
* \internal
*
* Name of the variable holding the result of this resolution step, to be
* used in the following resolution steps.
*/
QString outVar;
};
// special string replaced by outVar of the previous frame
QString AliasResolutionFrame::inVar = QStringLiteral("__QMLTC_ALIAS_FRAME_INPUT_VAR__");
/*!
* \internal
*
* Process the frames by replacing the placeholder \c invar
* used in \c epilogueForWrite and \c prologue with the result
* obtained from the previous frame.
*/
static void unpackFrames(QStack<AliasResolutionFrame> &frames)
{
if (frames.size() < 2)
return;
// assume first frame is fine
auto prev = frames.begin();
for (auto it = std::next(prev); it != frames.end(); ++it, ++prev) {
for (QString &line : it->prologue)
line.replace(AliasResolutionFrame::inVar, prev->outVar);
for (QString &line : it->epilogueForWrite)
line.replace(AliasResolutionFrame::inVar, prev->outVar);
it->outVar.replace(AliasResolutionFrame::inVar, prev->outVar);
}
}
template<typename Projection>
static QStringList joinFrames(const QStack<AliasResolutionFrame> &frames, Projection project)
{
QStringList joined;
for (const AliasResolutionFrame &frame : frames)
joined += project(frame);
return joined;
}
void QmltcCompiler::compileAlias(QmltcType &current, const QQmlJSMetaProperty &alias,
const QQmlJSScope::ConstPtr &owner)
{
const QString aliasName = alias.propertyName();
Q_ASSERT(!aliasName.isEmpty());
QStringList aliasExprBits = alias.aliasExpression().split(u'.');
Q_ASSERT(!aliasExprBits.isEmpty());
QStack<AliasResolutionFrame> frames;
QQmlJSUtils::AliasResolutionVisitor aliasVisitor;
qsizetype i = 0;
aliasVisitor.reset = [&]() {
frames.clear();
i = 0; // we use it in property processing
// first frame is a dummy one:
frames.push(
AliasResolutionFrame { QStringList(), QStringList(), QStringList(), u"this"_s });
};
aliasVisitor.processResolvedId = [&](const QQmlJSScope::ConstPtr &type) {
Q_ASSERT(type);
if (owner != type) { // cannot start at `this`, need to fetch object through context
const int id = m_visitor->runtimeId(type);
Q_ASSERT(id >= 0); // since the type is found by id, it must have an id
AliasResolutionFrame queryIdFrame {};
Q_ASSERT(frames.top().outVar == u"this"_s); // so inVar would be "this" as well
queryIdFrame.prologue << u"auto context = %1::q_qmltc_thisContext;"_s.arg(
owner->internalName());
// doing the above allows us to lookup id object by index (fast)
queryIdFrame.outVar = u"alias_objectById_" + aliasExprBits.front(); // unique enough
const QString cppType = (m_visitor->qmlComponentIndex(type) == -1)
? type->internalName()
: u"QQmlComponent"_s;
queryIdFrame.prologue << u"auto " + queryIdFrame.outVar + u" = static_cast<" + cppType
+ u"*>(context->idValue(" + QString::number(id) + u"));";
queryIdFrame.prologue << u"Q_ASSERT(" + queryIdFrame.outVar + u");";
frames.push(queryIdFrame);
}
};
aliasVisitor.processResolvedProperty = [&](const QQmlJSMetaProperty &p,
const QQmlJSScope::ConstPtr &owner) {
AliasResolutionFrame queryPropertyFrame {};
auto [extensionPrologue, extensionAccessor, extensionEpilogue] =
QmltcCodeGenerator::wrap_extensionType(
owner, p,
QmltcCodeGenerator::wrap_privateClass(AliasResolutionFrame::inVar, p));
QString inVar = extensionAccessor;
queryPropertyFrame.prologue += extensionPrologue;
if (p.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Value) {
// we need to read the property to a local variable and then
// write the updated value once the actual operation is done
const QString aliasVar = u"alias_" + QString::number(i); // should be fairly unique
++i;
queryPropertyFrame.prologue
<< u"auto " + aliasVar + u" = " + inVar + u"->" + p.read() + u"();";
queryPropertyFrame.epilogueForWrite
<< inVar + u"->" + p.write() + u"(" + aliasVar + u");";
// NB: since accessor becomes a value type, wrap it into an
// addressof operator so that we could access it as a pointer
inVar = QmltcCodeGenerator::wrap_addressof(aliasVar); // reset
} else {
inVar += u"->" + p.read() + u"()"; // update
}
queryPropertyFrame.outVar = inVar;
queryPropertyFrame.epilogue += extensionEpilogue;
frames.push(queryPropertyFrame);
};
QQmlJSUtils::ResolvedAlias result =
QQmlJSUtils::resolveAlias(m_typeResolver, alias, owner, aliasVisitor);
Q_ASSERT(result.kind != QQmlJSUtils::AliasTarget_Invalid);
unpackFrames(frames);
if (result.kind == QQmlJSUtils::AliasTarget_Property) {
// we don't need the last frame here
frames.pop();
// instead, add a custom frame
AliasResolutionFrame customFinalFrame {};
auto [extensionPrologue, extensionAccessor, extensionEpilogue] =
QmltcCodeGenerator::wrap_extensionType(
result.owner, result.property,
QmltcCodeGenerator::wrap_privateClass(frames.top().outVar,
result.property));
customFinalFrame.prologue = extensionPrologue;
customFinalFrame.outVar = extensionAccessor;
customFinalFrame.epilogue = extensionEpilogue;
frames.push(customFinalFrame);
}
const QString latestAccessor = frames.top().outVar;
const QStringList prologue =
joinFrames(frames, [](const AliasResolutionFrame &frame) { return frame.prologue; });
const QStringList epilogue =
joinFrames(frames, [](const AliasResolutionFrame &frame) { return frame.epilogue; });
const QString underlyingType = (result.kind == QQmlJSUtils::AliasTarget_Property)
? getUnderlyingType(result.property)
: result.owner->internalName() + u" *";
QStringList mocLines;
mocLines.reserve(10);
mocLines << underlyingType << aliasName;
QmltcPropertyData compilationData(aliasName);
// 1. add setter and getter
QmltcMethod getter {};
getter.returnType = underlyingType;
getter.name = compilationData.read;
getter.body += prologue;
if (result.kind == QQmlJSUtils::AliasTarget_Property) {
if (QString read = result.property.read(); !read.isEmpty()
&& !QQmlJSUtils::bindablePropertyHasDefaultAccessor(
result.property, QQmlJSUtils::PropertyAccessor_Read)) {
getter.body << u"return %1->%2();"_s.arg(latestAccessor, read);
} else { // use QObject::property() as a fallback when read method is unknown
getter.body << u"return qvariant_cast<%1>(%2->property(\"%3\"));"_s.arg(
underlyingType, latestAccessor, result.property.propertyName());
}
} else { // AliasTarget_Object
getter.body << u"return " + latestAccessor + u";";
}
getter.body += epilogue;
getter.userVisible = true;
current.functions.emplaceBack(getter);
mocLines << u"READ"_s << getter.name;
if (result.property.isWritable()) {
Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
QmltcMethod setter {};
setter.returnType = u"void"_s;
setter.name = compilationData.write;
const QString setName = result.property.write();
QList<QQmlJSMetaMethod> methods = result.owner->methods(setName);
if (methods.isEmpty()) { // when we are compiling the property as well
// QmltcVariable
setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(underlyingType),
aliasName + u"_", u""_s);
} else {
setter.parameterList = compileMethodParameters(methods.at(0).parameters(),
/* allow unnamed = */ true);
}
setter.body += prologue;
QStringList parameterNames;
parameterNames.reserve(setter.parameterList.size());
std::transform(setter.parameterList.cbegin(), setter.parameterList.cend(),
std::back_inserter(parameterNames),
[](const QmltcVariable &x) { return x.name; });
QString commaSeparatedParameterNames = parameterNames.join(u", "_s);
if (!setName.isEmpty()
&& !QQmlJSUtils::bindablePropertyHasDefaultAccessor(
result.property, QQmlJSUtils::PropertyAccessor_Write)) {
setter.body << u"%1->%2(%3);"_s.arg(latestAccessor, setName,
commaSeparatedParameterNames);
} else { // use QObject::setProperty() as fallback when write method is unknown
Q_ASSERT(parameterNames.size() == 1);
const QString variantName = u"var_" + aliasName; // fairly unique
setter.body << u"QVariant %1;"_s.arg(variantName);
setter.body << u"%1.setValue(%2);"_s.arg(variantName, commaSeparatedParameterNames);
setter.body << u"%1->setProperty(\"%2\", std::move(%3));"_s.arg(
latestAccessor, result.property.propertyName(), variantName);
}
setter.body += joinFrames(
frames, [](const AliasResolutionFrame &frame) { return frame.epilogueForWrite; });
setter.body += epilogue; // NB: *after* epilogueForWrite - see prologue construction
setter.userVisible = true;
current.functions.emplaceBack(setter);
mocLines << u"WRITE"_s << setter.name;
}
// 2. add bindable
if (QString bindableName = result.property.bindable(); !bindableName.isEmpty()) {
Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
QmltcMethod bindable {};
bindable.returnType = u"QBindable<" + underlyingType + u">";
bindable.name = compilationData.bindable;
bindable.body += prologue;
bindable.body << u"return " + latestAccessor + u"->" + bindableName + u"()" + u";";
bindable.body += epilogue;
bindable.userVisible = true;
current.functions.emplaceBack(bindable);
mocLines << u"BINDABLE"_s << bindable.name;
}
// 3. add notify - which is pretty special
// step 1: generate the moc instructions
// mimic the engines behavior: do it even if the notify will never be emitted
if (const QString aliasNotifyName = alias.notify(); !aliasNotifyName.isEmpty()) {
Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
mocLines << u"NOTIFY"_s << aliasNotifyName;
}
// step 2: connect the notifier to the aliased property notifier, if this latter exists
// otherwise, mimic the engines behavior and generate a useless notify
if (const QString notifyName = result.property.notify(); !notifyName.isEmpty()) {
auto notifyFrames = frames;
notifyFrames.pop(); // we don't need the last frame at all in this case
const QStringList notifyPrologue = joinFrames(
frames, [](const AliasResolutionFrame &frame) { return frame.prologue; });
const QStringList notifyEpilogue = joinFrames(
frames, [](const AliasResolutionFrame &frame) { return frame.epilogue; });
// notify is very special
current.endInit.body << u"{ // alias notify connection:"_s;
current.endInit.body += notifyPrologue;
// TODO: use non-private accessor since signals must exist on the public
// type, not on the private one -- otherwise, you can't connect to a
// private property signal in C++ and so it is useless (hence, use
// public type)
const QString cppType = (m_visitor->qmlComponentIndex(result.owner) == -1)
? result.owner->internalName()
: u"QQmlComponent"_s;
const QString latestAccessorNonPrivate = notifyFrames.top().outVar;
current.endInit.body << u"QObject::connect(" + latestAccessorNonPrivate + u", &" + cppType
+ u"::" + notifyName + u", this, &" + current.cppType + u"::"
+ compilationData.notify + u");";
current.endInit.body += notifyEpilogue;
current.endInit.body << u"}"_s;
}
if (QString resetName = result.property.reset(); !resetName.isEmpty()) {
Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
QmltcMethod reset {};
reset.returnType = u"void"_s;
reset.name = compilationData.reset;
reset.body += prologue;
reset.body << latestAccessor + u"->" + resetName + u"()" + u";";
reset.body += epilogue;
reset.userVisible = true;
current.functions.emplaceBack(reset);
mocLines << u"RESET"_s << reset.name;
}
// mimic the engines behavior: aliases are never constants
// mocLines << u"CONSTANT"_s;
// mimic the engines behavior: aliases are never stored
mocLines << u"STORED"_s << u"false"_s;
// mimic the engines behavior: aliases are never designable
mocLines << u"DESIGNABLE"_s << u"false"_s;
// 4. add moc entry
// Q_PROPERTY(QString text READ text WRITE setText BINDABLE bindableText NOTIFY textChanged)
current.mocCode << u"Q_PROPERTY(" + mocLines.join(u" "_s) + u")";
// 5. add extra moc entry if this alias is default one
if (aliasName == owner->defaultPropertyName()) {
// Q_CLASSINFO("DefaultProperty", propertyName)
current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(aliasName);
}
}
static QString generate_callCompilationUnit(const QString &urlMethodName)
{
return u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(%1())"_s.arg(urlMethodName);
}
static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope,
const QString &propertyName);
/*!
* \internal
* Helper method used to keep compileBindingByType() readable.
*/
void QmltcCompiler::compileObjectBinding(QmltcType &current,
const QQmlJSMetaPropertyBinding &binding,
const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Object);
const QString &propertyName = binding.propertyName();
const QQmlJSMetaProperty property = type->property(propertyName);
QQmlJSScope::ConstPtr propertyType = property.type();
// NB: object is compiled with compileType(), here just need to use it
auto object = binding.objectType();
// Note: despite a binding being set for `accessor`, we use "this" as a
// parent of a created object. Both attached and grouped properties are
// parented by "this", so lifetime-wise we should be fine
const QString qobjectParent = u"this"_s;
if (!propertyType) {
recordError(binding.sourceLocation(),
u"Binding on property '" + propertyName + u"' of unknown type");
return;
}
const auto addObjectBinding = [&](const QString &value) {
if (qIsReferenceTypeList(property)) {
Q_ASSERT(unprocessedListProperty == property || unprocessedListBindings.empty());
unprocessedListBindings.append(value);
unprocessedListProperty = property;
} else {
QmltcCodeGenerator::generate_assignToProperty(&current.endInit.body, type, property,
value, accessor.name, true);
}
};
// special case of implicit or explicit component:
if (qsizetype index = m_visitor->qmlComponentIndex(object); index >= 0) {
const QString objectName = newSymbol(u"sc"_s);
const qsizetype creationIndex = m_visitor->creationIndex(object);
QStringList *block = (creationIndex == -1) ? &current.endInit.body : &current.init.body;
*block << u"{"_s;
*block << QStringLiteral("auto thisContext = QQmlData::get(%1)->outerContext;")
.arg(qobjectParent);
*block << QStringLiteral("auto %1 = QQmlObjectCreator::createComponent(engine, "
"%2, %3, %4, thisContext);")
.arg(objectName, generate_callCompilationUnit(m_urlMethodName),
QString::number(index), qobjectParent);
*block << QStringLiteral("thisContext->installContext(QQmlData::get(%1), "
"QQmlContextData::OrdinaryObject);")
.arg(objectName);
// objects wrapped in implicit components do not have visible ids,
// however, explicit components can have an id and that one is going
// to be visible in the common document context
if (creationIndex != -1) {
// explicit component
Q_ASSERT(object->isComposite());
Q_ASSERT(object->baseType()->internalName() == u"QQmlComponent"_s);
if (int id = m_visitor->runtimeId(object); id >= 0) {
QString idString = m_visitor->addressableScopes().id(object, object);
if (idString.isEmpty())
idString = u"<unknown>"_s;
QmltcCodeGenerator::generate_setIdValue(block, u"thisContext"_s, id, objectName,
idString);
}
const QString creationIndexStr = QString::number(creationIndex);
*block << QStringLiteral("creator->set(%1, %2);").arg(creationIndexStr, objectName);
Q_ASSERT(block == &current.init.body);
current.endInit.body << QStringLiteral("auto %1 = creator->get<%2>(%3);")
.arg(objectName, u"QQmlComponent"_s, creationIndexStr);
}
addObjectBinding(objectName);
*block << u"}"_s;
return;
}
const QString objectName = newSymbol(u"o"_s);
current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg(
objectName, object->internalName(), qobjectParent);
current.init.body << u"creator->set(%1, %2);"_s.arg(
QString::number(m_visitor->creationIndex(object)), objectName);
// refetch the same object during endInit to set the bindings
current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg(
objectName, object->internalName(), QString::number(m_visitor->creationIndex(object)));
addObjectBinding(objectName);
}
/*!
* \internal
* Helper method used to keep compileBindingByType() readable.
*/
void QmltcCompiler::compileValueSourceOrInterceptorBinding(QmltcType &current,
const QQmlJSMetaPropertyBinding &binding,
const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::ValueSource
|| binding.bindingType() == QQmlSA::BindingType::Interceptor);
const QString &propertyName = binding.propertyName();
const QQmlJSMetaProperty property = type->property(propertyName);
QQmlJSScope::ConstPtr propertyType = property.type();
// NB: object is compiled with compileType(), here just need to use it
QSharedPointer<const QQmlJSScope> object;
if (binding.bindingType() == QQmlSA::BindingType::Interceptor)
object = binding.interceptorType();
else
object = binding.valueSourceType();
// Note: despite a binding being set for `accessor`, we use "this" as a
// parent of a created object. Both attached and grouped properties are
// parented by "this", so lifetime-wise we should be fine
const QString qobjectParent = u"this"_s;
if (!propertyType) {
recordError(binding.sourceLocation(),
u"Binding on property '" + propertyName + u"' of unknown type");
return;
}
auto &objectName = m_uniques[UniqueStringId(current, propertyName)].onAssignmentObjectName;
if (objectName.isEmpty()) {
objectName = u"onAssign_" + propertyName;
current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg(
objectName, object->internalName(), qobjectParent);
current.init.body << u"creator->set(%1, %2);"_s.arg(
QString::number(m_visitor->creationIndex(object)), objectName);
current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg(
objectName, object->internalName(),
QString::number(m_visitor->creationIndex(object)));
}
// NB: we expect one "on" assignment per property, so creating
// QQmlProperty each time should be fine (unlike QQmlListReference)
current.endInit.body << u"{"_s;
current.endInit.body << u"QQmlProperty qmlprop(%1, %2);"_s.arg(
accessor.name, QQmlJSUtils::toLiteral(propertyName));
current.endInit.body << u"QT_PREPEND_NAMESPACE(QQmlCppOnAssignmentHelper)::set(%1, qmlprop);"_s
.arg(objectName);
current.endInit.body << u"}"_s;
}
/*!
* \internal
* Helper method used to keep compileBindingByType() readable.
*/
void QmltcCompiler::compileAttachedPropertyBinding(QmltcType &current,
const QQmlJSMetaPropertyBinding &binding,
const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::AttachedProperty);
const QString &propertyName = binding.propertyName();
const QQmlJSMetaProperty property = type->property(propertyName);
QQmlJSScope::ConstPtr propertyType = property.type();
Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact
const auto attachedType = binding.attachedType();
Q_ASSERT(attachedType);
const QString attachingTypeName = propertyName; // acts as an identifier
auto attachingType = m_typeResolver->typeForName(attachingTypeName);
QString attachedTypeName = attachedType->baseTypeName();
Q_ASSERT(!attachedTypeName.isEmpty());
auto &attachedMemberName =
m_uniques[UniqueStringId(current, propertyName)].attachedVariableName;
if (attachedMemberName.isEmpty()) {
attachedMemberName = uniqueVariableName(attachingTypeName);
// add attached type as a member variable to allow noop lookup
current.variables.emplaceBack(attachedTypeName + u" *", attachedMemberName, u"nullptr"_s);
if (propertyName == u"Component"_s) { // Component attached type is special
current.endInit.body << u"Q_ASSERT(qmlEngine(this));"_s;
current.endInit.body
<< u"// attached Component must be added to the object's QQmlData"_s;
current.endInit.body
<< u"Q_ASSERT(!QQmlEnginePrivate::get(qmlEngine(this))->activeObjectCreator);"_s;
}
// Note: getting attached property is fairly expensive
const QString getAttachedPropertyLine = u"qobject_cast<" + attachedTypeName
+ u" *>(qmlAttachedPropertiesObject<" + attachingType->internalName()
+ u">(this, /* create = */ true))";
current.endInit.body << attachedMemberName + u" = " + getAttachedPropertyLine + u";";
if (propertyName == u"Component"_s) {
// call completed/destruction signals appropriately
current.handleOnCompleted.body << u"Q_EMIT " + attachedMemberName + u"->completed();";
if (!current.dtor) {
current.dtor = QmltcDtor{};
current.dtor->name = u"~" + current.cppType;
}
current.dtor->body << u"Q_EMIT " + attachedMemberName + u"->destruction();";
}
}
auto subbindings = attachedType->ownPropertyBindingsInQmlIROrder();
// compile bindings of the attached property
partitionBindings(subbindings.begin(), subbindings.end());
compileBinding(current, subbindings.begin(), subbindings.end(), attachedType,
{ type, attachedMemberName, propertyName, false });
}
/*!
* \internal
* Helper method used to keep compileBindingByType() readable.
*/
void QmltcCompiler::compileGroupPropertyBinding(QmltcType &current,
const QQmlJSMetaPropertyBinding &binding,
const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::GroupProperty);
const QString &propertyName = binding.propertyName();
const QQmlJSMetaProperty property = type->property(propertyName);
QQmlJSScope::ConstPtr propertyType = property.type();
Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact
if (property.read().isEmpty()) {
recordError(binding.sourceLocation(),
u"READ function of group property '" + propertyName + u"' is unknown");
return;
}
auto groupType = binding.groupType();
Q_ASSERT(groupType);
const bool isValueType = propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Value;
if (!isValueType
&& propertyType->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
recordError(binding.sourceLocation(),
u"Group property '" + propertyName + u"' has unsupported access semantics");
return;
}
auto subbindings = groupType->ownPropertyBindingsInQmlIROrder();
auto firstScript = partitionBindings(subbindings.begin(), subbindings.end());
// if we have no non-script bindings, we have no bindings that affect
// the value type group, so no reason to generate the wrapping code
const bool generateValueTypeCode = isValueType && (subbindings.begin() != firstScript);
QString groupAccessor = QmltcCodeGenerator::wrap_privateClass(accessor.name, property) + u"->"
+ property.read() + u"()";
// NB: used when isValueType == true
const QString groupPropertyVarName = accessor.name + u"_group_" + propertyName;
// value types are special
if (generateValueTypeCode) {
if (property.write().isEmpty()) { // just reject this
recordError(binding.sourceLocation(),
u"Group property '" + propertyName + u"' is a value type without a setter");
return;
}
current.endInit.body << u"auto " + groupPropertyVarName + u" = " + groupAccessor + u";";
// addressof operator is to make the binding logic work, which
// expects that `accessor.name` is a pointer type
groupAccessor = QmltcCodeGenerator::wrap_addressof(groupPropertyVarName);
}
// compile bindings of the grouped property
const auto compile = [&](const auto &bStart, const auto &bEnd) {
compileBinding(current, bStart, bEnd, groupType,
{ type, groupAccessor, propertyName, isValueType });
};
auto it = subbindings.begin();
Q_ASSERT(std::all_of(it, firstScript, [](const auto &x) {
return x.bindingType() != QQmlSA::BindingType::Script;
}));
compile(it, firstScript);
it = firstScript;
// NB: script bindings are special on group properties. if our group is
// a value type, the binding would be installed on the *object* that
// holds the value type and not on the value type itself. this may cause
// subtle side issues (esp. when script binding is actually a simple
// enum value assignment - which is not recognized specially):
//
// auto valueTypeGroupProperty = getCopy();
// installBinding(valueTypeGroupProperty, "subproperty1"); // changes subproperty1 value
// setCopy(valueTypeGroupProperty); // oops, subproperty1 value changed to old again
if (generateValueTypeCode) { // write the value type back
current.endInit.body << QmltcCodeGenerator::wrap_privateClass(accessor.name, property)
+ u"->" + property.write() + u"(" + groupPropertyVarName + u");";
}
// once the value is written back, process the script bindings
Q_ASSERT(std::all_of(it, subbindings.end(), [](const auto &x) {
return x.bindingType() == QQmlSA::BindingType::Script;
}));
compile(it, subbindings.end());
}
/*!
* \internal
* Helper method used to keep compileBindingByType() readable.
*/
void QmltcCompiler::compileTranslationBinding(QmltcType &current,
const QQmlJSMetaPropertyBinding &binding,
const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Translation
|| binding.bindingType() == QQmlSA::BindingType::TranslationById);
const QString &propertyName = binding.propertyName();
auto [property, absoluteIndex] = getMetaPropertyIndex(type, propertyName);
if (absoluteIndex < 0) {
recordError(binding.sourceLocation(),
u"Binding on unknown property '" + propertyName + u"'");
return;
}
QString bindingTarget = accessor.name;
int valueTypeIndex = -1;
if (accessor.isValueType) {
Q_ASSERT(accessor.scope != type);
bindingTarget = u"this"_s; // TODO: not necessarily "this"?
auto [groupProperty, groupPropertyIndex] =
getMetaPropertyIndex(accessor.scope, accessor.propertyName);
if (groupPropertyIndex < 0) {
recordError(binding.sourceLocation(),
u"Binding on group property '" + accessor.propertyName
+ u"' of unknown type");
return;
}
valueTypeIndex = absoluteIndex;
absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name
}
QmltcCodeGenerator::TranslationBindingInfo info;
info.unitVarName = generate_callCompilationUnit(m_urlMethodName);
info.scope = u"this"_s;
info.target = u"this"_s;
info.propertyIndex = absoluteIndex;
info.property = property;
info.data = binding.translationDataValue(m_url);
info.valueTypeIndex = valueTypeIndex;
info.line = binding.sourceLocation().startLine;
info.column = binding.sourceLocation().startColumn;
QmltcCodeGenerator::generate_createTranslationBindingOnProperty(&current.endInit.body, info);
}
void QmltcCompiler::processLastListBindings(QmltcType &current, const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
if (unprocessedListBindings.empty())
return;
QmltcCodeGenerator::generate_assignToListProperty(
&current.endInit.body, type, unprocessedListProperty, unprocessedListBindings,
accessor.name,
m_uniques[UniqueStringId(current, unprocessedListProperty.propertyName())]
.qmlListVariableName);
unprocessedListBindings.clear();
}
void QmltcCompiler::compileBinding(QmltcType &current,
QList<QQmlJSMetaPropertyBinding>::iterator bindingStart,
QList<QQmlJSMetaPropertyBinding>::iterator bindingEnd,
const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
for (auto it = bindingStart; it != bindingEnd; it++) {
const QQmlJSMetaPropertyBinding &binding = *it;
const QString &propertyName = binding.propertyName();
Q_ASSERT(!propertyName.isEmpty());
// Note: unlike QQmlObjectCreator, we don't have to do a complicated
// deferral logic for bindings: if a binding is deferred, it is not compiled
// (potentially, with all the bindings inside of it), period.
if (type->isNameDeferred(propertyName)) {
const auto location = binding.sourceLocation();
// make sure group property is not generalized by checking if type really has a property
// called propertyName. If not, it is probably an id.
if (binding.bindingType() == QQmlSA::BindingType::GroupProperty
&& type->hasProperty(propertyName)) {
qCWarning(lcQmltcCompiler)
<< QStringLiteral("Binding at line %1 column %2 is not deferred as it is a "
"binding on a group property.")
.arg(QString::number(location.startLine),
QString::number(location.startColumn));
// we do not support PropertyChanges and other types with similar
// behavior yet, so this binding is compiled
} else {
qCDebug(lcQmltcCompiler)
<< QStringLiteral(
"Binding at line %1 column %2 is deferred and thus not compiled")
.arg(QString::number(location.startLine),
QString::number(location.startColumn));
continue;
}
}
const QQmlJSMetaProperty metaProperty = type->property(propertyName);
const QQmlJSScope::ConstPtr propertyType = metaProperty.type();
if (!(qIsReferenceTypeList(metaProperty) && unprocessedListProperty == metaProperty)) {
processLastListBindings(current, type, accessor);
}
compileBindingByType(current, binding, type, accessor);
}
processLastListBindings(current, type, accessor);
}
void QmltcCompiler::compileBindingByType(QmltcType &current,
const QQmlJSMetaPropertyBinding &binding,
const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
const QString &propertyName = binding.propertyName();
const QQmlJSMetaProperty metaProperty = type->property(propertyName);
const QQmlJSScope::ConstPtr propertyType = metaProperty.type();
const auto assignToProperty = [&](const QQmlJSMetaProperty &p, const QString &value,
bool constructFromQObject = false) {
QmltcCodeGenerator::generate_assignToProperty(&current.endInit.body, type, p, value,
accessor.name, constructFromQObject);
};
switch (binding.bindingType()) {
case QQmlSA::BindingType::BoolLiteral: {
const bool value = binding.boolValue();
assignToProperty(metaProperty, value ? u"true"_s : u"false"_s);
break;
}
case QQmlSA::BindingType::NumberLiteral: {
assignToProperty(metaProperty, QString::number(binding.numberValue()));
break;
}
case QQmlSA::BindingType::StringLiteral: {
QString value = QQmlJSUtils::toLiteral(binding.stringValue());
if (auto type = metaProperty.type()) {
if (type->internalName() == u"QUrl"_s) {
value = u"QUrl(%1)"_s.arg(value);
}
}
assignToProperty(metaProperty, value);
break;
}
case QQmlSA::BindingType::RegExpLiteral: {
const QString value =
u"QRegularExpression(%1)"_s.arg(QQmlJSUtils::toLiteral(binding.regExpValue()));
assignToProperty(metaProperty, value);
break;
}
case QQmlSA::BindingType::Null: {
// poor check: null bindings are only supported for var and objects
Q_ASSERT(propertyType->isSameType(m_typeResolver->varType())
|| propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference);
if (propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
assignToProperty(metaProperty, u"nullptr"_s);
else
assignToProperty(metaProperty, u"QVariant::fromValue(nullptr)"_s);
break;
}
case QQmlSA::BindingType::Script: {
QString bindingSymbolName
= uniqueVariableName(type->internalName() + u'_' + propertyName + u"_binding");
compileScriptBinding(current, binding, bindingSymbolName, type, propertyName, propertyType,
accessor);
break;
}
case QQmlSA::BindingType::Object: {
compileObjectBinding(current, binding, type, accessor);
break;
}
case QQmlSA::BindingType::Interceptor:
Q_FALLTHROUGH();
case QQmlSA::BindingType::ValueSource: {
compileValueSourceOrInterceptorBinding(current, binding, type, accessor);
break;
}
case QQmlSA::BindingType::AttachedProperty: {
compileAttachedPropertyBinding(current, binding, type, accessor);
break;
}
case QQmlSA::BindingType::GroupProperty: {
compileGroupPropertyBinding(current, binding, type, accessor);
break;
}
case QQmlSA::BindingType::TranslationById:
case QQmlSA::BindingType::Translation: {
compileTranslationBinding(current, binding, type, accessor);
break;
}
case QQmlSA::BindingType::Invalid: {
recordError(binding.sourceLocation(), u"This binding is invalid"_s);
break;
}
default: {
recordError(binding.sourceLocation(), u"Binding is not supported"_s);
break;
}
}
}
// returns compiled script binding for "property changed" handler in a form of object type
static QmltcType compileScriptBindingPropertyChangeHandler(const QQmlJSMetaPropertyBinding &binding,
const QQmlJSScope::ConstPtr &objectType,
const QString &urlMethodName,
const QString &functorCppType,
const QString &objectCppType)
{
QmltcType bindingFunctor {};
bindingFunctor.cppType = functorCppType;
bindingFunctor.ignoreInit = true;
// default member variable and ctor:
const QString pointerToObject = objectCppType + u" *";
bindingFunctor.variables.emplaceBack(
QmltcVariable { pointerToObject, u"m_self"_s, u"nullptr"_s });
bindingFunctor.baselineCtor.name = functorCppType;
bindingFunctor.baselineCtor.parameterList.emplaceBack(
QmltcVariable { pointerToObject, u"self"_s, QString() });
bindingFunctor.baselineCtor.initializerList.emplaceBack(u"m_self(self)"_s);
// call operator:
QmltcMethod callOperator {};
callOperator.returnType = u"void"_s;
callOperator.name = u"operator()"_s;
callOperator.modifiers << u"const"_s;
QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
&callOperator.body, urlMethodName + u"()",
objectType->ownRuntimeFunctionIndex(binding.scriptIndex()), u"m_self"_s, u"void"_s, {});
bindingFunctor.functions.emplaceBack(std::move(callOperator));
return bindingFunctor;
}
// finds property for given scope and returns it together with the absolute
// property index in the property array of the corresponding QMetaObject
static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope,
const QString &propertyName)
{
auto owner = QQmlJSScope::ownerOfProperty(scope, propertyName).scope;
Q_ASSERT(owner);
const QQmlJSMetaProperty p = owner->ownProperty(propertyName);
if (!p.isValid())
return { p, -1 };
int index = p.index();
if (index < 0) // this property doesn't have index - comes from QML
return { p, -1 };
const auto increment = [&](const QQmlJSScope::ConstPtr &type, QQmlJSScope::ExtensionKind m) {
// owner of property is not included in the offset calculation (relative
// index is already added as p.index())
if (type->isSameType(owner))
return;
// extension namespace and JavaScript properties are ignored
if (m == QQmlJSScope::ExtensionNamespace || m == QQmlJSScope::ExtensionJavaScript)
return;
index += int(type->ownProperties().size());
};
QQmlJSUtils::traverseFollowingMetaObjectHierarchy(scope, owner, increment);
return { p, index };
}
void QmltcCompiler::compileScriptBinding(QmltcType &current,
const QQmlJSMetaPropertyBinding &binding,
const QString &bindingSymbolName,
const QQmlJSScope::ConstPtr &objectType,
const QString &propertyName,
const QQmlJSScope::ConstPtr &propertyType,
const QmltcCompiler::BindingAccessorData &accessor)
{
const auto compileScriptSignal = [&](const QString &name) {
QString This_signal = u"this"_s;
QString This_slot = u"this"_s;
QString objectClassName_signal = objectType->internalName();
QString objectClassName_slot = objectType->internalName();
// TODO: ugly crutch to make stuff work
if (accessor.name != u"this"_s) { // e.g. if attached property
This_signal = accessor.name;
This_slot = u"this"_s; // still
objectClassName_signal = objectType->baseTypeName();
objectClassName_slot = current.cppType; // real base class where slot would go
}
Q_ASSERT(!objectClassName_signal.isEmpty());
Q_ASSERT(!objectClassName_slot.isEmpty());
const auto signalMethods = objectType->methods(name, QQmlJSMetaMethodType::Signal);
Q_ASSERT(!signalMethods.isEmpty()); // an error somewhere else
QQmlJSMetaMethod signal = signalMethods.at(0);
Q_ASSERT(signal.methodType() == QQmlJSMetaMethodType::Signal);
const QString signalName = signal.methodName();
const QString slotName = newSymbol(signalName + u"_slot");
const QString signalReturnType = signal.returnType()->augmentedInternalName();
const QList<QmltcVariable> slotParameters =
compileMethodParameters(signal.parameters(), /* allow unnamed = */ true);
// SignalHander specific:
QmltcMethod slotMethod {};
slotMethod.returnType = signalReturnType;
slotMethod.name = slotName;
slotMethod.parameterList = slotParameters;
QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
&slotMethod.body, m_urlMethodName + u"()",
objectType->ownRuntimeFunctionIndex(binding.scriptIndex()),
u"this"_s, // Note: because script bindings always use current QML object scope
signalReturnType, slotParameters);
slotMethod.type = QQmlJSMetaMethodType::Slot;
current.functions << std::move(slotMethod);
current.setComplexBindings.body << u"QObject::connect(" + This_signal + u", " + u"&"
+ objectClassName_signal + u"::" + signalName + u", " + This_slot + u", &"
+ objectClassName_slot + u"::" + slotName + u");";
};
switch (binding.scriptKind()) {
case QQmlSA::ScriptBindingKind::PropertyBinding: {
if (!propertyType) {
recordError(binding.sourceLocation(),
u"Binding on property '" + propertyName + u"' of unknown type");
return;
}
auto [property, absoluteIndex] = getMetaPropertyIndex(objectType, propertyName);
if (absoluteIndex < 0) {
recordError(binding.sourceLocation(),
u"Binding on unknown property '" + propertyName + u"'");
return;
}
QString bindingTarget = accessor.name;
int valueTypeIndex = -1;
if (accessor.isValueType) {
Q_ASSERT(accessor.scope != objectType);
bindingTarget = u"this"_s; // TODO: not necessarily "this"?
auto [groupProperty, groupPropertyIndex] =
getMetaPropertyIndex(accessor.scope, accessor.propertyName);
if (groupPropertyIndex < 0) {
recordError(binding.sourceLocation(),
u"Binding on group property '" + accessor.propertyName
+ u"' of unknown type");
return;
}
valueTypeIndex = absoluteIndex;
absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name
}
QmltcCodeGenerator::generate_createBindingOnProperty(
&current.setComplexBindings.body, generate_callCompilationUnit(m_urlMethodName),
u"this"_s, // NB: always using enclosing object as a scope for the binding
static_cast<qsizetype>(objectType->ownRuntimeFunctionIndex(binding.scriptIndex())),
bindingTarget, // binding target
// value types are special and are bound through valueTypeIndex
accessor.isValueType ? QQmlJSScope::ConstPtr() : objectType, absoluteIndex,
property, valueTypeIndex, accessor.name);
break;
}
case QQmlSA::ScriptBindingKind::SignalHandler: {
const auto name = QQmlSignalNames::handlerNameToSignalName(propertyName);
Q_ASSERT(name.has_value());
compileScriptSignal(*name);
break;
}
case QQmlSA ::ScriptBindingKind::ChangeHandler: {
const QString objectClassName = objectType->internalName();
const QString bindingFunctorName = newSymbol(bindingSymbolName + u"Functor");
const auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName);
Q_ASSERT(signalName.has_value()); // an error somewhere else
const auto actualProperty =
QQmlJSUtils::propertyFromChangedHandler(objectType, propertyName);
Q_ASSERT(actualProperty.has_value()); // an error somewhere else
const auto actualPropertyType = actualProperty->type();
if (!actualPropertyType) {
recordError(binding.sourceLocation(),
u"Binding on property '" + actualProperty->propertyName()
+ u"' of unknown type");
return;
}
// due to historical reasons (QQmlObjectCreator), prefer NOTIFY over
// BINDABLE when both are available. thus, test for notify first
const QString notifyString = actualProperty->notify();
if (!notifyString.isEmpty()) {
compileScriptSignal(notifyString);
break;
}
const QString bindableString = actualProperty->bindable();
QString typeOfQmlBinding =
u"std::unique_ptr<QPropertyChangeHandler<" + bindingFunctorName + u">>";
current.children << compileScriptBindingPropertyChangeHandler(
binding, objectType, m_urlMethodName, bindingFunctorName, objectClassName);
current.setComplexBindings.body << u"if (!%1.contains(QStringLiteral(\"%2\")))"_s.arg(
current.propertyInitializer.initializedCache.name, propertyName);
// TODO: this could be dropped if QQmlEngine::setContextForObject() is
// done before currently generated C++ object is constructed
current.setComplexBindings.body << u" "_s + bindingSymbolName + u".reset(new QPropertyChangeHandler<"
+ bindingFunctorName + u">("
+ QmltcCodeGenerator::wrap_privateClass(accessor.name, *actualProperty)
+ u"->" + bindableString + u"().onValueChanged(" + bindingFunctorName + u"("
+ accessor.name + u"))));";
current.variables.emplaceBack(
QmltcVariable { typeOfQmlBinding, bindingSymbolName, QString() });
break;
}
default:
recordError(binding.sourceLocation(), u"Invalid script binding found"_s);
break;
}
}
QT_END_NAMESPACE