qtdeclarative/tools/qmltc/qmltccodewriter.cpp

570 lines
22 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 "qmltccodewriter.h"
#include <QtCore/qfileinfo.h>
#include <QtCore/qstringbuilder.h>
#include <QtCore/qstring.h>
#include <QtCore/qmap.h>
#include <QtCore/qlist.h>
#include <utility>
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
static QString urlToMacro(const QString &url)
{
QFileInfo fi(url);
return u"Q_QMLTC_" + fi.baseName().toUpper();
}
static QString getFunctionCategory(const QmltcMethodBase &method)
{
QString category;
switch (method.access) {
case QQmlJSMetaMethod::Private:
category = u"private"_s;
break;
case QQmlJSMetaMethod::Protected:
category = u"protected"_s;
break;
case QQmlJSMetaMethod::Public:
category = u"public"_s;
break;
}
return category;
}
static QString getFunctionCategory(const QmltcMethod &method)
{
QString category = getFunctionCategory(static_cast<const QmltcMethodBase &>(method));
switch (method.type) {
case QQmlJSMetaMethodType::Signal:
category = u"Q_SIGNALS"_s;
break;
case QQmlJSMetaMethodType::Slot:
category += u" Q_SLOTS"_s;
break;
case QQmlJSMetaMethodType::Method:
case QQmlJSMetaMethodType::StaticMethod:
break;
}
return category;
}
static QString appendSpace(const QString &s)
{
if (s.isEmpty())
return s;
return s + u" ";
}
static QString prependSpace(const QString &s)
{
if (s.isEmpty())
return s;
return u" " + s;
}
static std::pair<QString, QString> functionSignatures(const QmltcMethodBase &method)
{
const QString name = method.name;
const QList<QmltcVariable> &parameterList = method.parameterList;
QStringList headerParamList;
QStringList cppParamList;
for (const QmltcVariable &variable : parameterList) {
const QString commonPart = variable.cppType + u" " + variable.name;
cppParamList << commonPart;
headerParamList << commonPart;
if (!variable.defaultValue.isEmpty())
headerParamList.back() += u" = " + variable.defaultValue;
}
const QString headerSignature = name + u"(" + headerParamList.join(u", "_s) + u")"
+ prependSpace(method.modifiers.join(u" "));
const QString cppSignature = name + u"(" + cppParamList.join(u", "_s) + u")"
+ prependSpace(method.modifiers.join(u" "));
return { headerSignature, cppSignature };
}
static QString functionReturnType(const QmltcMethod &m)
{
return appendSpace(m.declarationPrefixes.join(u" "_s)) + m.returnType;
}
void QmltcCodeWriter::writeGlobalHeader(QmltcOutputWrapper &code, const QString &sourcePath,
const QString &hPath, const QString &cppPath,
const QString &outNamespace,
const QSet<QString> &requiredCppIncludes)
{
Q_UNUSED(cppPath);
const QString preamble = u"// This code is auto-generated by the qmltc tool from the file '"
+ sourcePath + u"'\n// WARNING! All changes made in this file will be lost!\n";
code.rawAppendToHeader(preamble);
code.rawAppendToCpp(preamble);
code.rawAppendToHeader(
u"// NOTE: This generated API is to be considered implementation detail.");
code.rawAppendToHeader(
u"// It may change from version to version and should not be relied upon.");
const QString headerMacro = urlToMacro(sourcePath);
code.rawAppendToHeader(u"#ifndef %1_H"_s.arg(headerMacro));
code.rawAppendToHeader(u"#define %1_H"_s.arg(headerMacro));
code.rawAppendToHeader(u"#include <QtCore/qproperty.h>");
code.rawAppendToHeader(u"#include <QtCore/qobject.h>");
code.rawAppendToHeader(u"#include <QtCore/qcoreapplication.h>");
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
code.rawAppendToHeader(u"#include <QtCore/qxpfunctional.h>");
code.rawAppendToHeader(u"#include <QtQml/qqmlengine.h>");
code.rawAppendToHeader(u"#include <QtCore/qurl.h>"); // used in engine execution
code.rawAppendToHeader(u"#include <QtQml/qqml.h>"); // used for attached properties
code.rawAppendToHeader(u"#include <private/qqmlengine_p.h>"); // executeRuntimeFunction(), etc.
code.rawAppendToHeader(u"#include <private/qqmltcobjectcreationhelper_p.h>"); // QmltcSupportLib
code.rawAppendToHeader(u"#include <QtQml/qqmllist.h>"); // QQmlListProperty
// include custom C++ includes required by used types
code.rawAppendToHeader(u"// BEGIN(custom_cpp_includes)");
for (const auto &requiredInclude : requiredCppIncludes)
code.rawAppendToHeader(u"#include \"" + requiredInclude + u"\"");
code.rawAppendToHeader(u"// END(custom_cpp_includes)");
code.rawAppendToCpp(u"#include \"" + hPath + u"\""); // include own .h file
code.rawAppendToCpp(u"// qmltc support library:");
code.rawAppendToCpp(u"#include <private/qqmlcppbinding_p.h>"); // QmltcSupportLib
code.rawAppendToCpp(u"#include <private/qqmlcpponassignment_p.h>"); // QmltcSupportLib
code.rawAppendToHeader(u"#include <private/qqmlcpptypehelpers_p.h> "); // QmltcSupportLib
code.rawAppendToCpp(u"#include <private/qqmlobjectcreator_p.h>"); // createComponent()
code.rawAppendToCpp(u"#include <private/qqmlcomponent_p.h>"); // QQmlComponentPrivate::get()
code.rawAppendToCpp(u"");
code.rawAppendToCpp(u"#include <private/qobject_p.h>"); // NB: for private properties
code.rawAppendToCpp(u"#include <private/qqmlobjectcreator_p.h>"); // for finalize callbacks
code.rawAppendToCpp(u"#include <QtQml/qqmlprivate.h>"); // QQmlPrivate::qmlExtendedObject()
code.rawAppendToCpp(u""); // blank line
code.rawAppendToCpp(u"QT_USE_NAMESPACE // avoid issues with QT_NAMESPACE");
code.rawAppendToHeader(u""); // blank line
const QStringList namespaces = outNamespace.split(u"::"_s);
for (const QString &currentNamespace : namespaces) {
code.rawAppendToHeader(u"namespace %1 {"_s.arg(currentNamespace));
code.rawAppendToCpp(u"namespace %1 {"_s.arg(currentNamespace));
}
}
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
void QmltcCodeWriter::write(QmltcOutputWrapper &code,
const QmltcPropertyInitializer &propertyInitializer,
const QmltcType &wrappedType)
{
code.rawAppendToHeader(u"class " + propertyInitializer.name + u" {");
{
{
[[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code);
code.rawAppendToHeader(u"friend class " + wrappedType.cppType + u";");
}
code.rawAppendToHeader(u"public:"_s);
[[maybe_unused]] QmltcOutputWrapper::MemberNameScope typeScope(&code, propertyInitializer.name);
{
[[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code);
write(code, propertyInitializer.constructor);
code.rawAppendToHeader(u""); // blank line
for (const auto &propertySetter : propertyInitializer.propertySetters) {
write(code, propertySetter);
}
}
code.rawAppendToHeader(u""); // blank line
code.rawAppendToHeader(u"private:"_s);
{
[[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code);
write(code, propertyInitializer.component);
write(code, propertyInitializer.initializedCache);
}
}
code.rawAppendToHeader(u"};"_s);
code.rawAppendToHeader(u""); // blank line
}
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
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcRequiredPropertiesBundle &requiredPropertiesBundle)
{
code.rawAppendToHeader(u"struct " + requiredPropertiesBundle.name + u" {");
{
[[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code);
for (const auto &member : requiredPropertiesBundle.members) {
write(code, member);
}
}
code.rawAppendToHeader(u"};"_s);
code.rawAppendToHeader(u""); // blank line
}
void QmltcCodeWriter::writeGlobalFooter(QmltcOutputWrapper &code, const QString &sourcePath,
const QString &outNamespace)
{
const QStringList namespaces = outNamespace.split(u"::"_s);
for (auto it = namespaces.crbegin(), end = namespaces.crend(); it != end; it++) {
code.rawAppendToCpp(u"} // namespace %1"_s.arg(*it));
code.rawAppendToHeader(u"} // namespace %1"_s.arg(*it));
}
code.rawAppendToHeader(u""); // blank line
code.rawAppendToHeader(u"#endif // %1_H"_s.arg(urlToMacro(sourcePath)));
code.rawAppendToHeader(u""); // blank line
}
static void writeToFile(const QString &path, const QByteArray &data)
{
// When not using dependency files, changing a single qml invalidates all
// qml files and would force the recompilation of everything. To avoid that,
// we check if the data is equal to the existing file, if yes, don't touch
// it so the build system will not recompile unnecessary things.
//
// If the build system use dependency file, we should anyway touch the file
// so qmltc is not re-run
QFileInfo fi(path);
if (fi.exists() && fi.size() == data.size()) {
QFile oldFile(path);
if (oldFile.open(QIODevice::ReadOnly)) {
if (oldFile.readAll() == data)
return;
}
}
QFile file(path);
if (!file.open(QIODevice::WriteOnly))
qFatal("Could not open file %s", qPrintable(path));
file.write(data);
}
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProgram &program)
{
writeGlobalHeader(code, program.url, program.hPath, program.cppPath, program.outNamespace,
program.includes);
// url method comes first
writeUrl(code, program.urlMethod);
// forward declare all the types first
for (const QmltcType &type : std::as_const(program.compiledTypes))
code.rawAppendToHeader(u"class " + type.cppType + u";");
// write all the types and their content
for (const QmltcType &type : std::as_const(program.compiledTypes))
write(code, type, program.exportMacro);
// add typeCount definitions. after all types have been written down (so
// they are now complete types as per C++). practically, this only concerns
// document root type
for (const QmltcType &type : std::as_const(program.compiledTypes)) {
if (!type.typeCount)
continue;
code.rawAppendToHeader(u""); // blank line
code.rawAppendToHeader(u"constexpr %1 %2::%3()"_s.arg(type.typeCount->returnType,
type.cppType, type.typeCount->name));
code.rawAppendToHeader(u"{");
for (const QString &line : std::as_const(type.typeCount->body))
code.rawAppendToHeader(line, 1);
code.rawAppendToHeader(u"}");
}
writeGlobalFooter(code, program.url, program.outNamespace);
writeToFile(program.hPath, code.code().header.toUtf8());
writeToFile(program.cppPath, code.code().cpp.toUtf8());
}
template<typename Predicate>
static void dumpFunctions(QmltcOutputWrapper &code, const QList<QmltcMethod> &functions,
Predicate pred)
{
// functions are _ordered_ by access and kind. ordering is important to
// provide consistent output
QMap<QString, QList<const QmltcMethod *>> orderedFunctions;
for (const auto &function : functions) {
if (pred(function))
orderedFunctions[getFunctionCategory(function)].append(std::addressof(function));
}
for (auto it = orderedFunctions.cbegin(); it != orderedFunctions.cend(); ++it) {
code.rawAppendToHeader(it.key() + u":", -1);
for (const QmltcMethod *function : std::as_const(it.value()))
QmltcCodeWriter::write(code, *function);
}
}
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type,
const QString &exportMacro)
{
const auto constructClassString = [&]() {
QString str = u"class "_s;
if (!exportMacro.isEmpty())
str.append(exportMacro).append(u" "_s);
str.append(type.cppType);
QStringList nonEmptyBaseClasses;
nonEmptyBaseClasses.reserve(type.baseClasses.size());
std::copy_if(type.baseClasses.cbegin(), type.baseClasses.cend(),
std::back_inserter(nonEmptyBaseClasses),
[](const QString &entry) { return !entry.isEmpty(); });
if (!nonEmptyBaseClasses.isEmpty())
str += u" : public " + nonEmptyBaseClasses.join(u", public "_s);
return str;
};
code.rawAppendToHeader(u""); // blank line
code.rawAppendToCpp(u""); // blank line
code.rawAppendToHeader(constructClassString());
code.rawAppendToHeader(u"{");
for (const QString &mocLine : std::as_const(type.mocCode))
code.rawAppendToHeader(mocLine, 1);
QmltcOutputWrapper::MemberNameScope typeScope(&code, type.cppType);
Q_UNUSED(typeScope);
{
QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code);
Q_UNUSED(headerIndent);
// first, write user-visible code, then everything else. someone might
// want to look at the generated code, so let's make an effort when
// writing it down
code.rawAppendToHeader(u"/* ----------------- */");
code.rawAppendToHeader(u"/* External C++ API */");
code.rawAppendToHeader(u"public:", -1);
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 (!type.propertyInitializer.name.isEmpty())
write(code, type.propertyInitializer, type);
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
if (type.requiredPropertiesBundle)
write(code, *type.requiredPropertiesBundle);
// NB: when non-document root, the externalCtor won't be public - but we
// really don't care about the output format of such types
if (!type.ignoreInit && type.externalCtor.access == QQmlJSMetaMethod::Public) {
// TODO: ignoreInit must be eliminated
QmltcCodeWriter::write(code, type.externalCtor);
if (type.staticCreate)
QmltcCodeWriter::write(code, *type.staticCreate);
}
// dtor
if (type.dtor)
QmltcCodeWriter::write(code, *type.dtor);
// enums
for (const auto &enumeration : std::as_const(type.enums))
QmltcCodeWriter::write(code, enumeration);
// visible functions
const auto isUserVisibleFunction = [](const QmltcMethod &function) {
return function.userVisible;
};
dumpFunctions(code, type.functions, isUserVisibleFunction);
code.rawAppendToHeader(u"/* ----------------- */");
code.rawAppendToHeader(u""); // blank line
code.rawAppendToHeader(u"/* Internal functionality (do NOT use it!) */");
// below are the hidden parts of the type
// (rest of the) ctors
if (type.ignoreInit) { // TODO: this branch should be eliminated
Q_ASSERT(type.baselineCtor.access == QQmlJSMetaMethod::Public);
code.rawAppendToHeader(u"public:", -1);
QmltcCodeWriter::write(code, type.baselineCtor);
} else {
code.rawAppendToHeader(u"protected:", -1);
if (type.externalCtor.access != QQmlJSMetaMethod::Public) {
Q_ASSERT(type.externalCtor.access == QQmlJSMetaMethod::Protected);
QmltcCodeWriter::write(code, type.externalCtor);
}
QmltcCodeWriter::write(code, type.baselineCtor);
QmltcCodeWriter::write(code, type.init);
QmltcCodeWriter::write(code, type.endInit);
QmltcCodeWriter::write(code, type.setComplexBindings);
QmltcCodeWriter::write(code, type.beginClass);
QmltcCodeWriter::write(code, type.completeComponent);
QmltcCodeWriter::write(code, type.finalizeComponent);
QmltcCodeWriter::write(code, type.handleOnCompleted);
}
// children
for (const auto &child : std::as_const(type.children))
QmltcCodeWriter::write(code, child, exportMacro);
// (non-visible) functions
dumpFunctions(code, type.functions, std::not_fn(isUserVisibleFunction));
// variables and properties
if (!type.variables.isEmpty() || !type.properties.isEmpty()) {
code.rawAppendToHeader(u""); // blank line
code.rawAppendToHeader(u"protected:", -1);
}
for (const auto &property : std::as_const(type.properties))
write(code, property);
for (const auto &variable : std::as_const(type.variables))
write(code, variable);
}
code.rawAppendToHeader(u"private:", -1);
for (const QString &otherLine : std::as_const(type.otherCode))
code.rawAppendToHeader(otherLine, 1);
if (type.typeCount) {
// add typeCount declaration, definition is added later
code.rawAppendToHeader(u""); // blank line
code.rawAppendToHeader(u"protected:");
code.rawAppendToHeader(u"constexpr static %1 %2();"_s.arg(type.typeCount->returnType,
type.typeCount->name),
1);
}
code.rawAppendToHeader(u"};");
}
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcEnum &enumeration)
{
code.rawAppendToHeader(u"enum " + enumeration.cppType + u" {");
for (qsizetype i = 0; i < enumeration.keys.size(); ++i) {
QString str;
if (enumeration.values.isEmpty()) {
str += enumeration.keys.at(i) + u",";
} else {
str += enumeration.keys.at(i) + u" = " + enumeration.values.at(i) + u",";
}
code.rawAppendToHeader(str, 1);
}
code.rawAppendToHeader(u"};");
code.rawAppendToHeader(enumeration.ownMocLine);
}
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcMethod &method)
{
const auto [hSignature, cppSignature] = functionSignatures(method);
// Note: augment return type with preambles in declaration
code.rawAppendToHeader((method.type == QQmlJSMetaMethodType::StaticMethod
? u"static " + functionReturnType(method)
: functionReturnType(method))
+ u" " + hSignature + u";");
// do not generate method implementation if it is a signal
const auto methodType = method.type;
if (methodType != QQmlJSMetaMethodType::Signal) {
code.rawAppendToCpp(u""_s); // blank line
if (method.comments.size() > 0) {
code.rawAppendToCpp(u"/*! \\internal"_s);
for (const auto &comment : method.comments)
code.rawAppendToCpp(comment, 1);
code.rawAppendToCpp(u"*/"_s);
}
code.rawAppendToCpp(method.returnType);
code.rawAppendSignatureToCpp(cppSignature);
code.rawAppendToCpp(u"{");
{
QmltcOutputWrapper::CppIndentationScope cppIndent(&code);
Q_UNUSED(cppIndent);
for (const QString &line : std::as_const(method.body))
code.rawAppendToCpp(line);
}
code.rawAppendToCpp(u"}");
}
}
template<typename WriteInitialization>
static void writeSpecialMethod(QmltcOutputWrapper &code, const QmltcMethodBase &specialMethod,
WriteInitialization writeInit)
{
const auto [hSignature, cppSignature] = functionSignatures(specialMethod);
code.rawAppendToHeader(hSignature + u";");
code.rawAppendToCpp(u""); // blank line
code.rawAppendSignatureToCpp(cppSignature);
writeInit(specialMethod);
code.rawAppendToCpp(u"{");
{
QmltcOutputWrapper::CppIndentationScope cppIndent(&code);
Q_UNUSED(cppIndent);
for (const QString &line : std::as_const(specialMethod.body))
code.rawAppendToCpp(line);
}
code.rawAppendToCpp(u"}");
}
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcCtor &ctor)
{
const auto writeInitializerList = [&](const QmltcMethodBase &ctorBase) {
auto ctor = static_cast<const QmltcCtor &>(ctorBase);
if (!ctor.initializerList.isEmpty()) {
code.rawAppendToCpp(u":", 1);
// double \n to make separate initializer list lines stand out more
code.rawAppendToCpp(
ctor.initializerList.join(u",\n\n" + u" "_s.repeated(code.cppIndent + 1)),
1);
}
};
writeSpecialMethod(code, ctor, writeInitializerList);
}
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcDtor &dtor)
{
const auto noop = [](const QmltcMethodBase &) {};
writeSpecialMethod(code, dtor, noop);
}
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcVariable &var)
{
const QString optionalPart = var.defaultValue.isEmpty() ? u""_s : u" = " + var.defaultValue;
code.rawAppendToHeader(var.cppType + u" " + var.name + optionalPart + u";");
}
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProperty &prop)
{
Q_ASSERT(prop.defaultValue.isEmpty()); // we don't support it yet (or at all?)
code.rawAppendToHeader(u"Q_OBJECT_BINDABLE_PROPERTY(%1, %2, %3, &%1::%4)"_s.arg(
prop.containingClass, prop.cppType, prop.name, prop.signalName));
}
void QmltcCodeWriter::writeUrl(QmltcOutputWrapper &code, const QmltcMethod &urlMethod)
{
// unlike ordinary methods, url function only exists in .cpp
Q_ASSERT(!urlMethod.returnType.isEmpty());
const auto [hSignature, _] = functionSignatures(urlMethod);
Q_UNUSED(_);
// Note: augment return type with preambles in declaration
code.rawAppendToCpp(functionReturnType(urlMethod) + u" " + hSignature);
code.rawAppendToCpp(u"{");
{
QmltcOutputWrapper::CppIndentationScope cppIndent(&code);
Q_UNUSED(cppIndent);
for (const QString &line : std::as_const(urlMethod.body))
code.rawAppendToCpp(line);
}
code.rawAppendToCpp(u"}");
}
QT_END_NAMESPACE