qtdeclarative/tools/qmltc/qmltccompiler.cpp

2050 lines
90 KiB
C++
Raw Permalink Normal View History

// 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;
}
qmltc: Enforce basic required properties Types in QML can expose "required" properties. A required property is a property that should be initialized when an instance of the type is built. Instantiating a type without initializing its required properties is an hard error. Currently, `qmltc` generated types do not respect the semantic of "required" properties. Instead, `qmltc` will generally default-initialize a required property without notifying the user of the error. `qmtlc`, so as to respect the semantic of required properties, will now require the user to pass an initial value for all required properties at construction time. To do so, `qmltc` will now generate a new inner record, `RequiredPropertiesBundle`, for each compiled top-level type, that contains the required amount of data to initialize each top-level required property that is reachable from the compiled type. An instance of `RequiredPropertiesBundle` will be required, as long as the type presents at least one required property, in the user-facing constructor for the generated type. The information stored in the instance will later be used to provide an initial value for each required property during the construction of the component. An intermediate representation for `RequiredPropertiesBundle` was added to "qmltcoutputir.h". `QmltcCodeWriter`, the component responsible for writing the final C++ code, was modified to take into consideration the presence, or lack thereof, of a `RequiredPropertiesBundle` and output the necessary code when required. The code taking care of populating the various IRs was modified to populate a `RequiredPropertiesBundle` for top-level components as necessary. Similarly, the code populating the parameters of the user-facing constructor was modified to require an instance of `RequiredPropertiesBundle` when necessary. The code that populates the body of the user-facing constructor for top-level types was modified to make use of the parameter by tying into the existing structure for setting initial values. `qmltc` uses a user-provided callback to allow the user to set the initial values for properties when constructing a top-level component. The body of the user-facing constructor was modified to compose the user-provided callback with a callable that sets the initial values for top-level required properties based on the bundle of data in the new `RequiredPropertiesBundle` instance. The code that populates the body of the user-facing constructor was moved into its own free-function, `compileRootExternalConstructorBody`, to be slightly more explicit about the structure of the code. A new test was provided to evaluate, some basic cases for the new behavior. Some pre-existing tests, which made use of required properties, were modified to comply with the new generated API. The documentation for `qmltc` was modified with a note about the new behavior. Task-number: QTBUG-120698 Change-Id: I1e916dcd91ae976629dad8adc7eacc6390bce7e9 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
2024-03-25 12:13:28 +00:00
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())
qmltc: Enforce basic required properties Types in QML can expose "required" properties. A required property is a property that should be initialized when an instance of the type is built. Instantiating a type without initializing its required properties is an hard error. Currently, `qmltc` generated types do not respect the semantic of "required" properties. Instead, `qmltc` will generally default-initialize a required property without notifying the user of the error. `qmtlc`, so as to respect the semantic of required properties, will now require the user to pass an initial value for all required properties at construction time. To do so, `qmltc` will now generate a new inner record, `RequiredPropertiesBundle`, for each compiled top-level type, that contains the required amount of data to initialize each top-level required property that is reachable from the compiled type. An instance of `RequiredPropertiesBundle` will be required, as long as the type presents at least one required property, in the user-facing constructor for the generated type. The information stored in the instance will later be used to provide an initial value for each required property during the construction of the component. An intermediate representation for `RequiredPropertiesBundle` was added to "qmltcoutputir.h". `QmltcCodeWriter`, the component responsible for writing the final C++ code, was modified to take into consideration the presence, or lack thereof, of a `RequiredPropertiesBundle` and output the necessary code when required. The code taking care of populating the various IRs was modified to populate a `RequiredPropertiesBundle` for top-level components as necessary. Similarly, the code populating the parameters of the user-facing constructor was modified to require an instance of `RequiredPropertiesBundle` when necessary. The code that populates the body of the user-facing constructor for top-level types was modified to make use of the parameter by tying into the existing structure for setting initial values. `qmltc` uses a user-provided callback to allow the user to set the initial values for properties when constructing a top-level component. The body of the user-facing constructor was modified to compose the user-provided callback with a callable that sets the initial values for top-level required properties based on the bundle of data in the new `RequiredPropertiesBundle` instance. The code that populates the body of the user-facing constructor was moved into its own free-function, `compileRootExternalConstructorBody`, to be slightly more explicit about the structure of the code. A new test was provided to evaluate, some basic cases for the new behavior. Some pre-existing tests, which made use of required properties, were modified to comply with the new generated API. The documentation for `qmltc` was modified with a note about the new behavior. Task-number: QTBUG-120698 Change-Id: I1e916dcd91ae976629dad8adc7eacc6390bce7e9 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
2024-03-25 12:13:28 +00:00
: 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;
qmltc: support basic inline components This patch adds basic inline component support to qmltc. Implementation details: * added tests: ** (for all tests: see if QQmlComponent would do the same thing) ** try simple inline components ** try inline components extending other inline components ** try recursive inline components (inline component A contain a B that itself contains a A) ** make sure ids in inline components are in their own context, and ** make sure alias inside of inline components work ** remove qmltc_qprocess that tests the error message when inline components are encountered ** test that inline components get their own context (by aliasing a property that they should not be able to see) ** test that also empty components work ** other tests inspired from those at tst_qqmllanguage * separate types depending on the inline component they belong in qmltcvisitor and qmltccompiler ** mostly by replacing T with QHash<QString, T>, where the belonging inline component name serves as hash key ** added a list of inline component names in qmltc * generate correct qmltc-code for inline components ** as they sometimes have mimic a document root and sometimes not ** generate their own "typeCount"-method ** fixed naming used in qmltc generated code by using the inline component names from the qqmljsscope. There is one missing feature (using inline components from other files, using QmlFileName.MyInlineComponent) that will be added in a separate commit. Fixes: QTBUG-106882 Change-Id: Iabe1a8b787027367d73d34bcd93c5f7b5480d217 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-09-09 11:47:28 +00:00
// 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);
QmltcCompiler: Adhere to Compare requirement ("!(a < a)") The Compare requirements [1] say: > The return value of the function call operation applied to an object of > a type satisfying Compare, when contextually converted to bool, yields > true if the first argument of the call appears before the second in the > strict weak ordering relation induced by this type, and false otherwise. That requirement was violated here, because passing the same value for both arguments would return true, since the inlineComponentB->inherits(inlineComponentA) check returns true when both inline components are the same. Add an equality check to fix that. Fixes a build failure when building with CXXFLAGS='-D_GLIBCXX_DEBUG': /usr/include/c++/13/bits/stl_algo.h:4892: In function: void std::sort(_RAIter, _RAIter, _Compare) [with _RAIter = QList<variant<QString, monostate> >::iterator; _Compare = QmltcCompiler::compile(const QmltcCompilerInfo&)::<lambda(const QmltcCompiler::InlineComponentOrDocumentRootName&, const QmltcCompiler::InlineComponentOrDocumentRootName&)>] Error: comparison doesn't meet irreflexive requirements, assert(!(a < a)). Objects involved in the operation: instance "functor" @ 0x7ffec14329f8 { type = QmltcCompiler::compile(QmltcCompilerInfo const&)::{lambda(std::variant<QString, std::monostate> const&, std::variant<QString, std::monostate> const&)#1}; } iterator::value_type "ordered type" { type = std::variant<QString, std::monostate>; } Aborted (core dumped) ninja: build stopped: subcommand failed. GDB backtrace: Program terminated with signal SIGABRT, Aborted. #0 __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at ./nptl/pthread_kill.c:44 44 ./nptl/pthread_kill.c: No such file or directory. (gdb) bt #0 __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at ./nptl/pthread_kill.c:44 #1 0x00007fbe6b4a815f in __pthread_kill_internal (signo=6, threadid=<optimized out>) at ./nptl/pthread_kill.c:78 #2 0x00007fbe6b45a472 in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26 #3 0x00007fbe6b4444b2 in __GI_abort () at ./stdlib/abort.c:79 #4 0x00007fbe6b6a300d in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6 #5 0x0000560ae899bec4 in std::sort<QList<std::variant<QString, std::monostate> >::iterator, QmltcCompiler::compile(const QmltcCompilerInfo&)::<lambda(const QmltcCompiler::InlineComponentOrDocumentRootName&, const QmltcCompiler::InlineComponentOrDocumentRootName&)> >(QList<std::variant<QString, std::monostate> >::iterator, QList<std::variant<QString, std::monostate> >::iterator, struct {...}) (__first=..., __last=..., __comp=...) at /usr/include/c++/13/bits/stl_algo.h:4892 #6 0x0000560ae898bd67 in QmltcCompiler::compile (this=0x7ffec1432e80, info=...) at /home/michi/development/git/qt5/qtdeclarative/tools/qmltc/qmltccompiler.cpp:85 #7 0x0000560ae89264b6 in main (argc=23, argv=0x7ffec1434fc8) at /home/michi/development/git/qt5/qtdeclarative/tools/qmltc/main.cpp:284 [1] https://en.cppreference.com/w/cpp/named_req/Compare Change-Id: Ie8934b8381ef217c1f8860ee110f6fa2aa0c86fa Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
2023-12-07 17:41:13 +00:00
if (inlineComponentAName == inlineComponentBName)
return false;
qmltc: support basic inline components This patch adds basic inline component support to qmltc. Implementation details: * added tests: ** (for all tests: see if QQmlComponent would do the same thing) ** try simple inline components ** try inline components extending other inline components ** try recursive inline components (inline component A contain a B that itself contains a A) ** make sure ids in inline components are in their own context, and ** make sure alias inside of inline components work ** remove qmltc_qprocess that tests the error message when inline components are encountered ** test that inline components get their own context (by aliasing a property that they should not be able to see) ** test that also empty components work ** other tests inspired from those at tst_qqmllanguage * separate types depending on the inline component they belong in qmltcvisitor and qmltccompiler ** mostly by replacing T with QHash<QString, T>, where the belonging inline component name serves as hash key ** added a list of inline component names in qmltc * generate correct qmltc-code for inline components ** as they sometimes have mimic a document root and sometimes not ** generate their own "typeCount"-method ** fixed naming used in qmltc generated code by using the inline component names from the qqmljsscope. There is one missing feature (using inline components from other files, using QmlFileName.MyInlineComponent) that will be added in a separate commit. Fixes: QTBUG-106882 Change-Id: Iabe1a8b787027367d73d34bcd93c5f7b5480d217 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-09-09 11:47:28 +00:00
// 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;
qmltc: support basic inline components This patch adds basic inline component support to qmltc. Implementation details: * added tests: ** (for all tests: see if QQmlComponent would do the same thing) ** try simple inline components ** try inline components extending other inline components ** try recursive inline components (inline component A contain a B that itself contains a A) ** make sure ids in inline components are in their own context, and ** make sure alias inside of inline components work ** remove qmltc_qprocess that tests the error message when inline components are encountered ** test that inline components get their own context (by aliasing a property that they should not be able to see) ** test that also empty components work ** other tests inspired from those at tst_qqmllanguage * separate types depending on the inline component they belong in qmltcvisitor and qmltccompiler ** mostly by replacing T with QHash<QString, T>, where the belonging inline component name serves as hash key ** added a list of inline component names in qmltc * generate correct qmltc-code for inline components ** as they sometimes have mimic a document root and sometimes not ** generate their own "typeCount"-method ** fixed naming used in qmltc generated code by using the inline component names from the qqmljsscope. There is one missing feature (using inline components from other files, using QmlFileName.MyInlineComponent) that will be added in a separate commit. Fixes: QTBUG-106882 Change-Id: Iabe1a8b787027367d73d34bcd93c5f7b5480d217 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-09-09 11:47:28 +00:00
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
qmltc: support basic inline components This patch adds basic inline component support to qmltc. Implementation details: * added tests: ** (for all tests: see if QQmlComponent would do the same thing) ** try simple inline components ** try inline components extending other inline components ** try recursive inline components (inline component A contain a B that itself contains a A) ** make sure ids in inline components are in their own context, and ** make sure alias inside of inline components work ** remove qmltc_qprocess that tests the error message when inline components are encountered ** test that inline components get their own context (by aliasing a property that they should not be able to see) ** test that also empty components work ** other tests inspired from those at tst_qqmllanguage * separate types depending on the inline component they belong in qmltcvisitor and qmltccompiler ** mostly by replacing T with QHash<QString, T>, where the belonging inline component name serves as hash key ** added a list of inline component names in qmltc * generate correct qmltc-code for inline components ** as they sometimes have mimic a document root and sometimes not ** generate their own "typeCount"-method ** fixed naming used in qmltc generated code by using the inline component names from the qqmljsscope. There is one missing feature (using inline components from other files, using QmlFileName.MyInlineComponent) that will be added in a separate commit. Fixes: QTBUG-106882 Change-Id: Iabe1a8b787027367d73d34bcd93c5f7b5480d217 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-09-09 11:47:28 +00:00
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);
qmltc: support basic inline components This patch adds basic inline component support to qmltc. Implementation details: * added tests: ** (for all tests: see if QQmlComponent would do the same thing) ** try simple inline components ** try inline components extending other inline components ** try recursive inline components (inline component A contain a B that itself contains a A) ** make sure ids in inline components are in their own context, and ** make sure alias inside of inline components work ** remove qmltc_qprocess that tests the error message when inline components are encountered ** test that inline components get their own context (by aliasing a property that they should not be able to see) ** test that also empty components work ** other tests inspired from those at tst_qqmllanguage * separate types depending on the inline component they belong in qmltcvisitor and qmltccompiler ** mostly by replacing T with QHash<QString, T>, where the belonging inline component name serves as hash key ** added a list of inline component names in qmltc * generate correct qmltc-code for inline components ** as they sometimes have mimic a document root and sometimes not ** generate their own "typeCount"-method ** fixed naming used in qmltc generated code by using the inline component names from the qqmljsscope. There is one missing feature (using inline components from other files, using QmlFileName.MyInlineComponent) that will be added in a separate commit. Fixes: QTBUG-106882 Change-Id: Iabe1a8b787027367d73d34bcd93c5f7b5480d217 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-09-09 11:47:28 +00:00
compiledTypes.emplaceBack(); // create empty type
compileType(compiledTypes.back(), type, compile);
}
}
}
qmltc: support basic inline components This patch adds basic inline component support to qmltc. Implementation details: * added tests: ** (for all tests: see if QQmlComponent would do the same thing) ** try simple inline components ** try inline components extending other inline components ** try recursive inline components (inline component A contain a B that itself contains a A) ** make sure ids in inline components are in their own context, and ** make sure alias inside of inline components work ** remove qmltc_qprocess that tests the error message when inline components are encountered ** test that inline components get their own context (by aliasing a property that they should not be able to see) ** test that also empty components work ** other tests inspired from those at tst_qqmllanguage * separate types depending on the inline component they belong in qmltcvisitor and qmltccompiler ** mostly by replacing T with QHash<QString, T>, where the belonging inline component name serves as hash key ** added a list of inline component names in qmltc * generate correct qmltc-code for inline components ** as they sometimes have mimic a document root and sometimes not ** generate their own "typeCount"-method ** fixed naming used in qmltc generated code by using the inline component names from the qqmljsscope. There is one missing feature (using inline components from other files, using QmlFileName.MyInlineComponent) that will be added in a separate commit. Fixes: QTBUG-106882 Change-Id: Iabe1a8b787027367d73d34bcd93c5f7b5480d217 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-09-09 11:47:28 +00:00
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();
qmltc: support basic inline components This patch adds basic inline component support to qmltc. Implementation details: * added tests: ** (for all tests: see if QQmlComponent would do the same thing) ** try simple inline components ** try inline components extending other inline components ** try recursive inline components (inline component A contain a B that itself contains a A) ** make sure ids in inline components are in their own context, and ** make sure alias inside of inline components work ** remove qmltc_qprocess that tests the error message when inline components are encountered ** test that inline components get their own context (by aliasing a property that they should not be able to see) ** test that also empty components work ** other tests inspired from those at tst_qqmllanguage * separate types depending on the inline component they belong in qmltcvisitor and qmltccompiler ** mostly by replacing T with QHash<QString, T>, where the belonging inline component name serves as hash key ** added a list of inline component names in qmltc * generate correct qmltc-code for inline components ** as they sometimes have mimic a document root and sometimes not ** generate their own "typeCount"-method ** fixed naming used in qmltc generated code by using the inline component names from the qqmljsscope. There is one missing feature (using inline components from other files, using QmlFileName.MyInlineComponent) that will be added in a separate commit. Fixes: QTBUG-106882 Change-Id: Iabe1a8b787027367d73d34bcd93c5f7b5480d217 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-09-09 11:47:28 +00:00
const InlineComponentOrDocumentRootName name = type->enclosingInlineComponentName();
QQmlJSScope::ConstPtr inlineComponentType = m_visitor->inlineComponent(name);
Q_ASSERT(inlineComponentType);
const bool documentRoot = (type == rootType);
qmltc: support basic inline components This patch adds basic inline component support to qmltc. Implementation details: * added tests: ** (for all tests: see if QQmlComponent would do the same thing) ** try simple inline components ** try inline components extending other inline components ** try recursive inline components (inline component A contain a B that itself contains a A) ** make sure ids in inline components are in their own context, and ** make sure alias inside of inline components work ** remove qmltc_qprocess that tests the error message when inline components are encountered ** test that inline components get their own context (by aliasing a property that they should not be able to see) ** test that also empty components work ** other tests inspired from those at tst_qqmllanguage * separate types depending on the inline component they belong in qmltcvisitor and qmltccompiler ** mostly by replacing T with QHash<QString, T>, where the belonging inline component name serves as hash key ** added a list of inline component names in qmltc * generate correct qmltc-code for inline components ** as they sometimes have mimic a document root and sometimes not ** generate their own "typeCount"-method ** fixed naming used in qmltc generated code by using the inline component names from the qqmljsscope. There is one missing feature (using inline components from other files, using QmlFileName.MyInlineComponent) that will be added in a separate commit. Fixes: QTBUG-106882 Change-Id: Iabe1a8b787027367d73d34bcd93c5f7b5480d217 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-09-09 11:47:28 +00:00
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
qmltc: support basic inline components This patch adds basic inline component support to qmltc. Implementation details: * added tests: ** (for all tests: see if QQmlComponent would do the same thing) ** try simple inline components ** try inline components extending other inline components ** try recursive inline components (inline component A contain a B that itself contains a A) ** make sure ids in inline components are in their own context, and ** make sure alias inside of inline components work ** remove qmltc_qprocess that tests the error message when inline components are encountered ** test that inline components get their own context (by aliasing a property that they should not be able to see) ** test that also empty components work ** other tests inspired from those at tst_qqmllanguage * separate types depending on the inline component they belong in qmltcvisitor and qmltccompiler ** mostly by replacing T with QHash<QString, T>, where the belonging inline component name serves as hash key ** added a list of inline component names in qmltc * generate correct qmltc-code for inline components ** as they sometimes have mimic a document root and sometimes not ** generate their own "typeCount"-method ** fixed naming used in qmltc generated code by using the inline component names from the qqmljsscope. There is one missing feature (using inline components from other files, using QmlFileName.MyInlineComponent) that will be added in a separate commit. Fixes: QTBUG-106882 Change-Id: Iabe1a8b787027367d73d34bcd93c5f7b5480d217 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-09-09 11:47:28 +00:00
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);
qmltc: support basic inline components This patch adds basic inline component support to qmltc. Implementation details: * added tests: ** (for all tests: see if QQmlComponent would do the same thing) ** try simple inline components ** try inline components extending other inline components ** try recursive inline components (inline component A contain a B that itself contains a A) ** make sure ids in inline components are in their own context, and ** make sure alias inside of inline components work ** remove qmltc_qprocess that tests the error message when inline components are encountered ** test that inline components get their own context (by aliasing a property that they should not be able to see) ** test that also empty components work ** other tests inspired from those at tst_qqmllanguage * separate types depending on the inline component they belong in qmltcvisitor and qmltccompiler ** mostly by replacing T with QHash<QString, T>, where the belonging inline component name serves as hash key ** added a list of inline component names in qmltc * generate correct qmltc-code for inline components ** as they sometimes have mimic a document root and sometimes not ** generate their own "typeCount"-method ** fixed naming used in qmltc generated code by using the inline component names from the qqmljsscope. There is one missing feature (using inline components from other files, using QmlFileName.MyInlineComponent) that will be added in a separate commit. Fixes: QTBUG-106882 Change-Id: Iabe1a8b787027367d73d34bcd93c5f7b5480d217 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-09-09 11:47:28 +00:00
}
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;
};
qmltc: Avoid befriending the root component twice When `qmltc` compiles a type to C++, if the type is not the root of the document, it will generate code to befriend the root component. Similarly, it will generate code to befriend the direct parent of the component. In both cases this is done as the component may be created from those other generated types by accessing some private members of the generated API. When a component is not the root of the document, is not an inline component and its parent is the root of the document, `qmltc` will generate the same friend declaration twice, once to befriend the root of the document and once to befriend the direct parent of the type. For example, if we have some component `Foo` and a root component that refers to it, say: ``` Window { Foo {} } ``` The generated code for `Foo` will befriend the generated type for the root component twice. While multiple friend declaration are not an issue, they are generally uncommon, as there is no reason to befriend something twice, so that they may generally stand out as an error or a typo, while making the code noisier. Thus, to avoid befriending the document root twice for generated non-root, non-inline components whose parent is the root of the document, the code that generates the friend declaration for the direct parent of such a component was conditioned on that parent not being the document root, as we would have already befriended it at that point of the compilation. Change-Id: Ie6eb4b786317c3c555718b594c689c085b4f2336 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2024-03-18 11:51:59 +00:00
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
qmltc: support basic inline components This patch adds basic inline component support to qmltc. Implementation details: * added tests: ** (for all tests: see if QQmlComponent would do the same thing) ** try simple inline components ** try inline components extending other inline components ** try recursive inline components (inline component A contain a B that itself contains a A) ** make sure ids in inline components are in their own context, and ** make sure alias inside of inline components work ** remove qmltc_qprocess that tests the error message when inline components are encountered ** test that inline components get their own context (by aliasing a property that they should not be able to see) ** test that also empty components work ** other tests inspired from those at tst_qqmllanguage * separate types depending on the inline component they belong in qmltcvisitor and qmltccompiler ** mostly by replacing T with QHash<QString, T>, where the belonging inline component name serves as hash key ** added a list of inline component names in qmltc * generate correct qmltc-code for inline components ** as they sometimes have mimic a document root and sometimes not ** generate their own "typeCount"-method ** fixed naming used in qmltc generated code by using the inline component names from the qqmljsscope. There is one missing feature (using inline components from other files, using QmlFileName.MyInlineComponent) that will be added in a separate commit. Fixes: QTBUG-106882 Change-Id: Iabe1a8b787027367d73d34bcd93c5f7b5480d217 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-09-09 11:47:28 +00:00
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;
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
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);
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
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 };
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
current.setComplexBindings.parameterList = { creator, engine, initializedCache };
current.handleOnCompleted.parameterList = { creator };
qmltc: support basic inline components This patch adds basic inline component support to qmltc. Implementation details: * added tests: ** (for all tests: see if QQmlComponent would do the same thing) ** try simple inline components ** try inline components extending other inline components ** try recursive inline components (inline component A contain a B that itself contains a A) ** make sure ids in inline components are in their own context, and ** make sure alias inside of inline components work ** remove qmltc_qprocess that tests the error message when inline components are encountered ** test that inline components get their own context (by aliasing a property that they should not be able to see) ** test that also empty components work ** other tests inspired from those at tst_qqmllanguage * separate types depending on the inline component they belong in qmltcvisitor and qmltccompiler ** mostly by replacing T with QHash<QString, T>, where the belonging inline component name serves as hash key ** added a list of inline component names in qmltc * generate correct qmltc-code for inline components ** as they sometimes have mimic a document root and sometimes not ** generate their own "typeCount"-method ** fixed naming used in qmltc generated code by using the inline component names from the qqmljsscope. There is one missing feature (using inline components from other files, using QmlFileName.MyInlineComponent) that will be added in a separate commit. Fixes: QTBUG-106882 Change-Id: Iabe1a8b787027367d73d34bcd93c5f7b5480d217 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-09-09 11:47:28 +00:00
if (documentRoot || inlineComponent) {
const QmltcVariable initializer(
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
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 };
qmltc: Enforce basic required properties Types in QML can expose "required" properties. A required property is a property that should be initialized when an instance of the type is built. Instantiating a type without initializing its required properties is an hard error. Currently, `qmltc` generated types do not respect the semantic of "required" properties. Instead, `qmltc` will generally default-initialize a required property without notifying the user of the error. `qmtlc`, so as to respect the semantic of required properties, will now require the user to pass an initial value for all required properties at construction time. To do so, `qmltc` will now generate a new inner record, `RequiredPropertiesBundle`, for each compiled top-level type, that contains the required amount of data to initialize each top-level required property that is reachable from the compiled type. An instance of `RequiredPropertiesBundle` will be required, as long as the type presents at least one required property, in the user-facing constructor for the generated type. The information stored in the instance will later be used to provide an initial value for each required property during the construction of the component. An intermediate representation for `RequiredPropertiesBundle` was added to "qmltcoutputir.h". `QmltcCodeWriter`, the component responsible for writing the final C++ code, was modified to take into consideration the presence, or lack thereof, of a `RequiredPropertiesBundle` and output the necessary code when required. The code taking care of populating the various IRs was modified to populate a `RequiredPropertiesBundle` for top-level components as necessary. Similarly, the code populating the parameters of the user-facing constructor was modified to require an instance of `RequiredPropertiesBundle` when necessary. The code that populates the body of the user-facing constructor for top-level types was modified to make use of the parameter by tying into the existing structure for setting initial values. `qmltc` uses a user-provided callback to allow the user to set the initial values for properties when constructing a top-level component. The body of the user-facing constructor was modified to compose the user-provided callback with a callable that sets the initial values for top-level required properties based on the bundle of data in the new `RequiredPropertiesBundle` instance. The code that populates the body of the user-facing constructor was moved into its own free-function, `compileRootExternalConstructorBody`, to be slightly more explicit about the structure of the code. A new test was provided to evaluate, some basic cases for the new behavior. Some pre-existing tests, which made use of required properties, were modified to comply with the new generated API. The documentation for `qmltc` was modified with a note about the new behavior. Task-number: QTBUG-120698 Change-Id: I1e916dcd91ae976629dad8adc7eacc6390bce7e9 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
2024-03-25 12:13:28 +00:00
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;
qmltc: support basic inline components This patch adds basic inline component support to qmltc. Implementation details: * added tests: ** (for all tests: see if QQmlComponent would do the same thing) ** try simple inline components ** try inline components extending other inline components ** try recursive inline components (inline component A contain a B that itself contains a A) ** make sure ids in inline components are in their own context, and ** make sure alias inside of inline components work ** remove qmltc_qprocess that tests the error message when inline components are encountered ** test that inline components get their own context (by aliasing a property that they should not be able to see) ** test that also empty components work ** other tests inspired from those at tst_qqmllanguage * separate types depending on the inline component they belong in qmltcvisitor and qmltccompiler ** mostly by replacing T with QHash<QString, T>, where the belonging inline component name serves as hash key ** added a list of inline component names in qmltc * generate correct qmltc-code for inline components ** as they sometimes have mimic a document root and sometimes not ** generate their own "typeCount"-method ** fixed naming used in qmltc generated code by using the inline component names from the qqmljsscope. There is one missing feature (using inline components from other files, using QmlFileName.MyInlineComponent) that will be added in a separate commit. Fixes: QTBUG-106882 Change-Id: Iabe1a8b787027367d73d34bcd93c5f7b5480d217 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2022-09-09 11:47:28 +00:00
if (documentRoot || inlineComponent) {
qmltc: Enforce basic required properties Types in QML can expose "required" properties. A required property is a property that should be initialized when an instance of the type is built. Instantiating a type without initializing its required properties is an hard error. Currently, `qmltc` generated types do not respect the semantic of "required" properties. Instead, `qmltc` will generally default-initialize a required property without notifying the user of the error. `qmtlc`, so as to respect the semantic of required properties, will now require the user to pass an initial value for all required properties at construction time. To do so, `qmltc` will now generate a new inner record, `RequiredPropertiesBundle`, for each compiled top-level type, that contains the required amount of data to initialize each top-level required property that is reachable from the compiled type. An instance of `RequiredPropertiesBundle` will be required, as long as the type presents at least one required property, in the user-facing constructor for the generated type. The information stored in the instance will later be used to provide an initial value for each required property during the construction of the component. An intermediate representation for `RequiredPropertiesBundle` was added to "qmltcoutputir.h". `QmltcCodeWriter`, the component responsible for writing the final C++ code, was modified to take into consideration the presence, or lack thereof, of a `RequiredPropertiesBundle` and output the necessary code when required. The code taking care of populating the various IRs was modified to populate a `RequiredPropertiesBundle` for top-level components as necessary. Similarly, the code populating the parameters of the user-facing constructor was modified to require an instance of `RequiredPropertiesBundle` when necessary. The code that populates the body of the user-facing constructor for top-level types was modified to make use of the parameter by tying into the existing structure for setting initial values. `qmltc` uses a user-provided callback to allow the user to set the initial values for properties when constructing a top-level component. The body of the user-facing constructor was modified to compose the user-provided callback with a callable that sets the initial values for top-level required properties based on the bundle of data in the new `RequiredPropertiesBundle` instance. The code that populates the body of the user-facing constructor was moved into its own free-function, `compileRootExternalConstructorBody`, to be slightly more explicit about the structure of the code. A new test was provided to evaluate, some basic cases for the new behavior. Some pre-existing tests, which made use of required properties, were modified to comply with the new generated API. The documentation for `qmltc` was modified with a note about the new behavior. Task-number: QTBUG-120698 Change-Id: I1e916dcd91ae976629dad8adc7eacc6390bce7e9 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Sami Shalayel <sami.shalayel@qt.io>
2024-03-25 12:13:28 +00:00
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);
});
}
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
// 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;
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
};
current.propertyInitializer.constructor.initializerList << u"component{component}"_s;
const auto properties = type->properties().values();
for (const auto &property: properties) {
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
if (property.index() == -1) continue;
if (property.isPrivate()) continue;
if (!property.isWritable() && !qIsReferenceTypeList(property)) continue;
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
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;
}
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
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)
) {
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
compiledSetter.body << u"%1.%2().setValue(%3_);"_s.arg(
current.propertyInitializer.component.name, property.bindable(), name);
qmltc: Ensure usage of generated setters in `PropertyInitializer` When `qmltc` compiles a QML type to C++, it generates a proxy object for the compiled type, `PropertyInitializer`, that knows how to set the top-level property of the component. A user can then pass a call-back to the constructor for the compiled type and use an instance of the generated `PropertyInitializer` to set the initial values for the top-level properties of the component. For each top-level property, the generated `PropertyInitializer` generates a setter. Based on various parameters, the generated setter can have a different body. Generally, the generated setter will prefer calling back to the component setter for the properties when that is available. When that is not the case, the generated setter would fall-back to a more general approach based on `QObject::setProperty`, which has additional overhead. This second approach will currently be chosen, when generating the setter for an owned property of the compiled type. While `qmltc` will generate a typed setter for those properties, on the C++ side, at the point where `PropertyInitializer` is compiled, the inspected owned properties, that come from the QML side, will not have a write accessor, which in turn makes the generated code fall-back to the more general implementation for the setter of those properties. To ensure that the generated setters for `PropertyInitializer` use a write accessor when present, `compilePropertyInitializer`, which takes care of producing the code for the generated `PropertyInitializer`, was modified to special case the generation of the setter for properties that are owned by the compiled type, ensuring that the generated setter will be used to set the initial value for the property. Change-Id: I0c34d6dd9d85a613d1e8909e94a584bf976a009f Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
2024-03-20 15:04:27 +00:00
} else if (type->hasOwnProperty(name)) {
compiledSetter.body << u"%1.%2(%3_);"_s.arg(
current.propertyInitializer.component.name, QmltcPropertyData(property).write, name);
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
} 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(
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
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());
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
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(
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
current.propertyInitializer.initializedCache.name, propertyName);
// TODO: this could be dropped if QQmlEngine::setContextForObject() is
// done before currently generated C++ object is constructed
qmltc: Allow setting initial values when creating a compiled type `qmltc`-generated types currently do not allow setting any initial values for the property of a component during creation. For example, some component `Foo` with a property `bar`, will have no way to set `bar` to a specific value from the C++ side of the code before an instance of `Foo` is obtained by the user. This lack of control prohibits the user from interacting with certain processes that are part of the component creation. For example, if a component provides am `onCompleted` binding that depends on some of the values of its properties, the user is inhibited from varying the per-instance values that `onCompleted` depends on, as the user would be able to vary those values only after the component is created and the `onCompleted` signal is emitted. This differs, from example, from the `QQmlComponent` interface, where the user is able to provide some initialization values as part of the creation of an instance of the component. To allow the user to have more control in the initialization of the instance of a component, before it is fully created, `qmltc` generated code now allows the user to provide an initialization callback that is processed as part of the creation cycle of an instance of the component. The callback provides the user with a generated proxy object, `PropertyInitializer`, that only knows how to set the writable, non-private properties of the component. The generated code for the public constructor of a `qmltc`-generated type was modified to provide an optional `initializer` parameter that stores the callback. The private `QML_init` method that `qmltc` generates for each type, that performs the required setup to create an instance of a component, was modified to allow for the same optional parameter, which is passed on by the public constructor. The body of `QML_init` was modified to call the new parameter, after having performed the general set-up for the created instance but before the instance is completed and before setting up "complex bindings" such as an `onPropertyChanged` handler. The callback is called with an instance of the generated proxy object that is built on-site. The proxy-object keeps track of the properties that were actually initialized by the callback. This information is now passed down to `QML_setComplexBindings`, which avoids setting up any unnecessary bindings for the properties that were initialized. A representation for the proxy object was added to the internal IR that is used by `qmltc` when generating code. The representation for a compiled C++ type was modified to store an instance of the proxy object. The newly stored instance is now populated as part of the general compilation of a type, by the `compilePropertyInitializer` free-function in "qmltccompiler.cpp". The component responsible for the final code-generation, `QmltcCodeWriter`, was modified to be able to generate code for the new proxy object representation. The documentation for `QmltcCodeGenerator::generate_initCode`, which sets up the body for `QML_init`, was updated to reflect the new body. A pre-existing issue in the documentation of the method, which failed to enumerate all generated steps for the body, was fixed as part of the change. The preamble that `qmltc` generates for all generated header files was modified to include "QtCore/qxpfunction.h", to have access to `qxp::function_ref`, which is used to store the new callback parameter. A pre-existing snapshot test had its snapshot file, "src/qml/doc/snippets/qmltc/special/HelloWorld.qml.cpp", updated to reflect the changes to the generated code. A new basic, non-exhaustive test case was added to the available corpus of `qmltc` tests to test the basic workflow of providing an initialization callback. The documentation for `qmltc` was modified to mention the new parameter. Task-number: QTBUG-120700 Change-Id: I246c1c3634982580d66b31fd891382559a9cc3ae Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
2024-03-11 15:33:35 +00:00
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