2022-05-13 13:12:05 +00:00
|
|
|
// Copyright (C) 2021 The Qt Company Ltd.
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
2021-09-16 07:35:33 +00:00
|
|
|
|
|
|
|
#include "qmltccompiler.h"
|
2021-09-17 15:01:56 +00:00
|
|
|
#include "qmltcoutputir.h"
|
|
|
|
#include "qmltccodewriter.h"
|
2021-10-29 09:56:25 +00:00
|
|
|
#include "qmltcpropertyutils.h"
|
2021-11-12 08:28:33 +00:00
|
|
|
#include "qmltccompilerpieces.h"
|
2021-11-01 15:18:30 +00:00
|
|
|
|
2021-11-15 13:31:42 +00:00
|
|
|
#include <QtCore/qloggingcategory.h>
|
2023-08-10 07:45:37 +00:00
|
|
|
#include <QtQml/private/qqmlsignalnames_p.h>
|
2021-12-17 14:52:17 +00:00
|
|
|
#include <private/qqmljsutils_p.h>
|
2021-09-16 07:35:33 +00:00
|
|
|
|
2021-10-29 10:43:44 +00:00
|
|
|
#include <algorithm>
|
|
|
|
|
2021-09-16 07:35:33 +00:00
|
|
|
QT_BEGIN_NAMESPACE
|
2022-03-21 09:21:18 +00:00
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
|
2022-06-29 15:21:49 +00:00
|
|
|
bool qIsReferenceTypeList(const QQmlJSMetaProperty &p)
|
|
|
|
{
|
2022-10-19 12:35:24 +00:00
|
|
|
if (QQmlJSScope::ConstPtr type = p.type())
|
|
|
|
return type->isListProperty();
|
|
|
|
return false;
|
2022-06-29 15:21:49 +00:00
|
|
|
}
|
|
|
|
|
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 ¤t,
|
|
|
|
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(
|
2025-08-25 09:00:35 +00:00
|
|
|
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);
|
|
|
|
};
|
|
|
|
|
2021-09-21 08:52:24 +00:00
|
|
|
Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg);
|
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
const QString QmltcCodeGenerator::privateEngineName = u"ePriv"_s;
|
|
|
|
const QString QmltcCodeGenerator::typeCountName = u"q_qmltc_typeCount"_s;
|
2021-11-12 08:28:33 +00:00
|
|
|
|
2021-09-21 08:52:24 +00:00
|
|
|
QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor,
|
|
|
|
QQmlJSLogger *logger)
|
|
|
|
: m_url(url), m_typeResolver(resolver), m_visitor(visitor), m_logger(logger)
|
2021-09-16 07:35:33 +00:00
|
|
|
{
|
|
|
|
Q_UNUSED(m_typeResolver);
|
2021-09-21 08:52:24 +00:00
|
|
|
Q_ASSERT(!hasErrors());
|
2021-09-16 07:35:33 +00:00
|
|
|
}
|
|
|
|
|
2022-03-10 08:37:01 +00:00
|
|
|
// 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;
|
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-06-03 11:52:47 +00:00
|
|
|
void QmltcCompiler::compile(const QmltcCompilerInfo &info)
|
2021-09-16 07:35:33 +00:00
|
|
|
{
|
|
|
|
m_info = info;
|
2021-09-21 08:52:24 +00:00
|
|
|
Q_ASSERT(!m_info.outputCppFile.isEmpty());
|
|
|
|
Q_ASSERT(!m_info.outputHFile.isEmpty());
|
|
|
|
Q_ASSERT(!m_info.resourcePath.isEmpty());
|
|
|
|
|
2022-06-03 11:52:47 +00:00
|
|
|
// Note: we only compile "pure" QML types. any component-wrapped type is
|
|
|
|
// expected to appear through a binding
|
2022-03-10 08:37:01 +00:00
|
|
|
|
|
|
|
const auto isComponent = [](const QQmlJSScope::ConstPtr &type) {
|
|
|
|
auto base = type->baseType();
|
2022-03-21 09:21:18 +00:00
|
|
|
return base && base->internalName() == u"QQmlComponent"_s;
|
2022-03-10 08:37:01 +00:00
|
|
|
};
|
|
|
|
|
2022-04-26 08:42:16 +00:00
|
|
|
QmltcCodeGenerator generator { m_url, m_visitor };
|
|
|
|
|
2021-11-12 08:28:33 +00:00
|
|
|
QmltcMethod urlMethod;
|
2022-04-26 08:42:16 +00:00
|
|
|
compileUrlMethod(urlMethod, generator.urlMethodName());
|
2022-05-12 12:39:38 +00:00
|
|
|
m_urlMethodName = urlMethod.name;
|
2022-03-10 08:37:01 +00:00
|
|
|
|
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;
|
|
|
|
|
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;
|
|
|
|
});
|
2022-03-10 08:37:01 +00:00
|
|
|
|
|
|
|
QList<QmltcType> compiledTypes;
|
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)) {
|
2022-03-10 08:37:01 +00:00
|
|
|
compiledTypes.emplaceBack(); // create empty type
|
2022-09-09 11:47:28 +00:00
|
|
|
const auto compile = [&](QmltcType ¤t, const QQmlJSScope::ConstPtr &type) {
|
|
|
|
generator.generate_initCodeForTopLevelComponent(current, type);
|
|
|
|
};
|
|
|
|
compileType(compiledTypes.back(), root, compile);
|
|
|
|
} else {
|
|
|
|
const auto compile = [this](QmltcType ¤t, const QQmlJSScope::ConstPtr &type) {
|
|
|
|
compileTypeElements(current, type);
|
|
|
|
};
|
|
|
|
|
|
|
|
for (const auto &type : pureTypes) {
|
2023-05-05 07:30:27 +00:00
|
|
|
Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope);
|
2022-09-09 11:47:28 +00:00
|
|
|
compiledTypes.emplaceBack(); // create empty type
|
|
|
|
compileType(compiledTypes.back(), type, compile);
|
|
|
|
}
|
2022-03-10 08:37:01 +00:00
|
|
|
}
|
2021-09-21 08:52:24 +00:00
|
|
|
}
|
2022-09-09 11:47:28 +00:00
|
|
|
|
2022-03-10 08:37:01 +00:00
|
|
|
if (hasErrors())
|
|
|
|
return;
|
2021-09-17 15:01:56 +00:00
|
|
|
|
|
|
|
QmltcProgram program;
|
|
|
|
program.url = m_url;
|
|
|
|
program.cppPath = m_info.outputCppFile;
|
|
|
|
program.hPath = m_info.outputHFile;
|
2021-10-20 09:14:52 +00:00
|
|
|
program.outNamespace = m_info.outputNamespace;
|
2022-12-22 16:24:16 +00:00
|
|
|
program.exportMacro = m_info.exportMacro;
|
2021-09-21 08:52:24 +00:00
|
|
|
program.compiledTypes = compiledTypes;
|
2022-06-10 07:41:27 +00:00
|
|
|
program.includes = m_visitor->cppIncludeFiles();
|
2022-12-22 16:24:16 +00:00
|
|
|
if (!m_info.exportMacro.isEmpty() && !m_info.exportInclude.isEmpty())
|
|
|
|
program.includes += (m_info.exportInclude);
|
2021-11-12 08:28:33 +00:00
|
|
|
program.urlMethod = urlMethod;
|
2021-09-17 15:01:56 +00:00
|
|
|
|
|
|
|
QmltcOutput out;
|
|
|
|
QmltcOutputWrapper code(out);
|
|
|
|
QmltcCodeWriter::write(code, program);
|
2021-09-16 07:35:33 +00:00
|
|
|
}
|
|
|
|
|
2022-04-26 08:42:16 +00:00
|
|
|
void QmltcCompiler::compileUrlMethod(QmltcMethod &urlMethod, const QString &urlMethodName)
|
2021-11-12 08:28:33 +00:00
|
|
|
{
|
2022-04-26 08:42:16 +00:00
|
|
|
urlMethod.name = urlMethodName;
|
2022-03-21 09:21:18 +00:00
|
|
|
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;
|
2021-11-12 08:28:33 +00:00
|
|
|
}
|
|
|
|
|
2021-11-19 12:04:05 +00:00
|
|
|
void QmltcCompiler::compileType(
|
|
|
|
QmltcType ¤t, const QQmlJSScope::ConstPtr &type,
|
|
|
|
std::function<void(QmltcType &, const QQmlJSScope::ConstPtr &)> compileElements)
|
2021-09-21 08:52:24 +00:00
|
|
|
{
|
2021-10-28 12:18:04 +00:00
|
|
|
Q_ASSERT(!type->internalName().isEmpty());
|
2021-09-21 08:52:24 +00:00
|
|
|
current.cppType = type->internalName();
|
2021-10-28 12:18:04 +00:00
|
|
|
Q_ASSERT(!type->baseType()->internalName().isEmpty());
|
2021-09-21 08:52:24 +00:00
|
|
|
const QString baseClass = type->baseType()->internalName();
|
|
|
|
|
2021-10-22 10:02:37 +00:00
|
|
|
const auto rootType = m_visitor->result();
|
2022-09-09 11:47:28 +00:00
|
|
|
const InlineComponentOrDocumentRootName name = type->enclosingInlineComponentName();
|
|
|
|
QQmlJSScope::ConstPtr inlineComponentType = m_visitor->inlineComponent(name);
|
|
|
|
Q_ASSERT(inlineComponentType);
|
2021-10-22 10:02:37 +00:00
|
|
|
const bool documentRoot = (type == rootType);
|
2022-09-09 11:47:28 +00:00
|
|
|
const bool inlineComponent = type->isInlineComponent();
|
2021-11-19 12:04:05 +00:00
|
|
|
const bool isAnonymous = !documentRoot || type->internalName().at(0).isLower();
|
2022-09-28 18:04:44 +00:00
|
|
|
const bool isSingleton = type->isSingleton();
|
2021-11-19 12:04:05 +00:00
|
|
|
|
2022-04-26 08:42:16 +00:00
|
|
|
QmltcCodeGenerator generator { m_url, m_visitor };
|
2021-09-21 08:52:24 +00:00
|
|
|
|
|
|
|
current.baseClasses = { baseClass };
|
|
|
|
if (!documentRoot) {
|
2022-03-03 13:03:58 +00:00
|
|
|
// make document root a friend to allow it to access init and endInit
|
2022-09-09 11:47:28 +00:00
|
|
|
const QString rootInternalName =
|
|
|
|
m_visitor->inlineComponent(type->enclosingInlineComponentName())->internalName();
|
2023-08-01 15:32:25 +00:00
|
|
|
if (rootInternalName != current.cppType) // avoid GCC13 warning on self-befriending
|
|
|
|
current.otherCode << "friend class %1;"_L1.arg(rootInternalName);
|
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
|
2021-11-19 12:04:05 +00:00
|
|
|
// 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());
|
|
|
|
}
|
2021-09-21 08:52:24 +00:00
|
|
|
}
|
2022-04-28 09:03:09 +00:00
|
|
|
|
2021-11-19 12:04:05 +00:00
|
|
|
// make QQmltcObjectCreationHelper a friend of every type since it provides
|
|
|
|
// useful helper methods for all types
|
2022-03-21 09:21:18 +00:00
|
|
|
current.otherCode << u"friend class QT_PREPEND_NAMESPACE(QQmltcObjectCreationHelper);"_s;
|
2021-09-21 08:52:24 +00:00
|
|
|
|
2021-10-28 12:18:04 +00:00
|
|
|
current.mocCode = {
|
2022-03-21 09:21:18 +00:00
|
|
|
u"Q_OBJECT"_s,
|
2021-10-28 12:18:04 +00:00
|
|
|
// Note: isAnonymous holds for non-root types in the document as well
|
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),
|
2021-10-28 12:18:04 +00:00
|
|
|
};
|
2021-09-21 08:52:24 +00:00
|
|
|
|
2021-10-22 10:02:37 +00:00
|
|
|
// add special member functions
|
2022-03-03 13:03:58 +00:00
|
|
|
current.baselineCtor.access = QQmlJSMetaMethod::Protected;
|
2022-09-28 18:04:44 +00:00
|
|
|
if (documentRoot || inlineComponent || isSingleton) {
|
2021-11-19 12:04:05 +00:00
|
|
|
current.externalCtor.access = QQmlJSMetaMethod::Public;
|
|
|
|
} else {
|
|
|
|
current.externalCtor.access = QQmlJSMetaMethod::Protected;
|
|
|
|
}
|
2021-10-22 10:02:37 +00:00
|
|
|
current.init.access = QQmlJSMetaMethod::Protected;
|
2021-11-19 12:04:05 +00:00
|
|
|
current.beginClass.access = QQmlJSMetaMethod::Protected;
|
2022-03-03 13:03:58 +00:00
|
|
|
current.endInit.access = QQmlJSMetaMethod::Protected;
|
2022-07-27 15:03:44 +00:00
|
|
|
current.setComplexBindings.access = QQmlJSMetaMethod::Protected;
|
2021-11-19 12:04:05 +00:00
|
|
|
current.completeComponent.access = QQmlJSMetaMethod::Protected;
|
|
|
|
current.finalizeComponent.access = QQmlJSMetaMethod::Protected;
|
|
|
|
current.handleOnCompleted.access = QQmlJSMetaMethod::Protected;
|
2021-10-22 10:02:37 +00:00
|
|
|
|
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;
|
|
|
|
|
2022-03-03 13:03:58 +00:00
|
|
|
current.baselineCtor.name = current.cppType;
|
|
|
|
current.externalCtor.name = current.cppType;
|
2022-03-21 09:21:18 +00:00
|
|
|
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;
|
2022-07-27 15:03:44 +00:00
|
|
|
current.setComplexBindings.name = u"QML_setComplexBindings"_s;
|
|
|
|
current.setComplexBindings.returnType = u"void"_s;
|
2022-03-21 09:21:18 +00:00
|
|
|
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
|
|
|
|
);
|
2022-03-21 09:21:18 +00:00
|
|
|
QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_s, u"parentContext"_s);
|
|
|
|
QmltcVariable finalizeFlag(u"bool"_s, u"canFinalize"_s);
|
2021-11-19 12:04:05 +00:00
|
|
|
current.baselineCtor.parameterList = { parent };
|
2022-07-27 15:28:33 +00:00
|
|
|
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 };
|
2022-07-27 15:28:33 +00:00
|
|
|
current.handleOnCompleted.parameterList = { creator };
|
2022-09-09 11:47:28 +00:00
|
|
|
|
|
|
|
if (documentRoot || inlineComponent) {
|
2024-04-10 06:34:27 +00:00
|
|
|
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 };
|
2021-11-19 12:04:05 +00:00
|
|
|
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 };
|
|
|
|
}
|
2021-09-21 08:52:24 +00:00
|
|
|
} else {
|
2022-03-03 13:03:58 +00:00
|
|
|
current.externalCtor.parameterList = { creator, engine, parent };
|
2021-10-22 10:02:37 +00:00
|
|
|
current.init.parameterList = { creator, engine, ctxtdata };
|
2021-11-19 12:04:05 +00:00
|
|
|
current.beginClass.parameterList = { creator };
|
|
|
|
current.completeComponent.parameterList = { creator };
|
|
|
|
current.finalizeComponent.parameterList = { creator };
|
2021-09-21 08:52:24 +00:00
|
|
|
}
|
|
|
|
|
2022-03-03 13:03:58 +00:00
|
|
|
current.externalCtor.initializerList = { current.baselineCtor.name + u"(" + parent.name
|
|
|
|
+ u")" };
|
2022-04-28 09:07:30 +00:00
|
|
|
if (QQmlJSUtils::hasCompositeBase(type)) {
|
2021-09-21 08:52:24 +00:00
|
|
|
// call parent's (QML type's) basic ctor from this. that one will take
|
|
|
|
// care about QObject::setParent()
|
2022-03-03 13:03:58 +00:00
|
|
|
current.baselineCtor.initializerList = { baseClass + u"(" + parent.name + u")" };
|
2021-09-21 08:52:24 +00:00
|
|
|
} else {
|
|
|
|
// default call to ctor is enough, but QQml_setParent_noEvent() is
|
|
|
|
// needed (note: faster? version of QObject::setParent())
|
2022-03-03 13:03:58 +00:00
|
|
|
current.baselineCtor.body << u"QQml_setParent_noEvent(this, " + parent.name + u");";
|
2021-09-21 08:52:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// compilation stub:
|
2022-10-05 11:37:07 +00:00
|
|
|
current.externalCtor.body << u"Q_UNUSED(engine)"_s;
|
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);
|
2021-10-22 10:02:37 +00:00
|
|
|
} else {
|
2022-03-21 09:21:18 +00:00
|
|
|
current.externalCtor.body << u"// not document root:"_s;
|
2021-10-22 10:02:37 +00:00
|
|
|
// just call init, we don't do any setup here otherwise
|
2022-03-03 13:03:58 +00:00
|
|
|
current.externalCtor.body << current.init.name
|
2021-10-22 10:02:37 +00:00
|
|
|
+ u"(creator, engine, QQmlData::get(parent)->outerContext);";
|
2021-09-21 08:52:24 +00:00
|
|
|
}
|
2021-11-12 08:28:33 +00:00
|
|
|
|
2022-09-28 18:04:44 +00:00
|
|
|
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;
|
2023-05-05 07:30:27 +00:00
|
|
|
staticCreate.type = QQmlJSMetaMethodType::StaticMethod;
|
2022-09-28 18:04:44 +00:00
|
|
|
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;
|
|
|
|
}
|
2021-11-19 12:04:05 +00:00
|
|
|
auto postponedQmlContextSetup = generator.generate_initCode(current, type);
|
2022-07-27 11:36:04 +00:00
|
|
|
generator.generate_endInitCode(current, type);
|
2022-07-27 15:03:44 +00:00
|
|
|
generator.generate_setComplexBindingsCode(current, type);
|
2021-11-19 12:04:05 +00:00
|
|
|
generator.generate_beginClassCode(current, type);
|
|
|
|
generator.generate_completeComponentCode(current, type);
|
|
|
|
generator.generate_finalizeComponentCode(current, type);
|
|
|
|
generator.generate_handleOnCompletedCode(current, type);
|
|
|
|
|
|
|
|
compileElements(current, type);
|
|
|
|
}
|
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
template<typename Iterator>
|
|
|
|
static Iterator partitionBindings(Iterator first, Iterator last)
|
2021-11-19 12:04:05 +00:00
|
|
|
{
|
2022-05-12 12:39:38 +00:00
|
|
|
// 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) {
|
2022-07-27 15:03:44 +00:00
|
|
|
// we want complex bindings to be at the end, so do the negation
|
|
|
|
return !QmltcCompiler::isComplexBinding(b);
|
2022-05-12 12:39:38 +00:00
|
|
|
});
|
|
|
|
}
|
2021-11-19 12:04:05 +00:00
|
|
|
|
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.
|
2025-07-11 13:52:57 +00:00
|
|
|
void QmltcCompiler::compilePropertyInitializer(
|
|
|
|
QmltcType ¤t, 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;
|
|
|
|
|
2025-07-11 13:52:57 +00:00
|
|
|
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;
|
2024-03-20 14:49:34 +00:00
|
|
|
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();
|
2025-07-11 13:52:57 +00:00
|
|
|
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;
|
|
|
|
|
2024-03-20 14:49:34 +00:00
|
|
|
if (qIsReferenceTypeList(property)) {
|
|
|
|
compiledSetter.parameterList.emplaceBack(
|
2025-07-11 13:52:57 +00:00
|
|
|
QQmlJSUtils::constRefify(
|
2025-08-25 09:00:35 +00:00
|
|
|
u"QList<%1*>"_s.arg(propertyType->elementType()->internalName())),
|
2024-03-20 14:49:34 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-10-15 13:20:39 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
void QmltcCompiler::compileTypeElements(QmltcType ¤t, const QQmlJSScope::ConstPtr &type)
|
|
|
|
{
|
2021-10-29 10:43:44 +00:00
|
|
|
// 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());
|
2021-11-01 15:18:30 +00:00
|
|
|
|
2021-10-29 09:56:25 +00:00
|
|
|
auto properties = type->ownProperties().values();
|
2021-11-19 12:04:05 +00:00
|
|
|
current.properties.reserve(properties.size());
|
2021-10-29 09:56:25 +00:00
|
|
|
// 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();
|
|
|
|
});
|
2022-10-06 09:30:50 +00:00
|
|
|
for (const QQmlJSMetaProperty &p : std::as_const(properties)) {
|
2021-10-29 09:56:25 +00:00
|
|
|
if (p.index() == -1) {
|
|
|
|
recordError(type->sourceLocation(),
|
2022-03-21 09:21:18 +00:00
|
|
|
u"Internal error: property '%1' has incomplete information"_s.arg(
|
2021-10-29 09:56:25 +00:00
|
|
|
p.propertyName()));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (p.isAlias()) {
|
2022-04-22 10:51:41 +00:00
|
|
|
compileAlias(current, p, type);
|
2021-10-29 09:56:25 +00:00
|
|
|
} else {
|
|
|
|
compileProperty(current, p, type);
|
|
|
|
}
|
|
|
|
}
|
2021-11-15 16:10:31 +00:00
|
|
|
|
2021-11-19 12:04:05 +00:00
|
|
|
const auto methods = type->ownMethods();
|
2022-04-19 11:35:36 +00:00
|
|
|
for (const QQmlJSMetaMethod &m : methods)
|
|
|
|
compileMethod(current, m, type);
|
2021-11-19 12:04:05 +00:00
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
auto bindings = type->ownPropertyBindingsInQmlIROrder();
|
|
|
|
partitionBindings(bindings.begin(), bindings.end());
|
2021-11-19 12:04:05 +00:00
|
|
|
|
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);
|
2022-09-20 13:11:54 +00:00
|
|
|
compileBinding(current, bindings.begin(), bindings.end(), type, { type });
|
2021-10-29 10:43:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void QmltcCompiler::compileEnum(QmltcType ¤t, 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),
|
2022-03-21 09:21:18 +00:00
|
|
|
u"Q_ENUM(%1)"_s.arg(e.name()));
|
2021-09-21 08:52:24 +00:00
|
|
|
}
|
|
|
|
|
2022-11-10 10:38:51 +00:00
|
|
|
static QList<QmltcVariable>
|
|
|
|
compileMethodParameters(const QList<QQmlJSMetaParameter> ¶meterInfos, bool allowUnnamed = false)
|
2021-11-01 15:18:30 +00:00
|
|
|
{
|
|
|
|
QList<QmltcVariable> parameters;
|
2022-11-10 10:38:51 +00:00
|
|
|
const auto size = parameterInfos.size();
|
2021-11-01 15:18:30 +00:00
|
|
|
parameters.reserve(size);
|
|
|
|
for (qsizetype i = 0; i < size; ++i) {
|
2022-11-10 10:38:51 +00:00
|
|
|
const auto &p = parameterInfos[i];
|
|
|
|
Q_ASSERT(p.type()); // assume verified
|
|
|
|
QString name = p.name();
|
2021-11-01 15:18:30 +00:00
|
|
|
Q_ASSERT(allowUnnamed || !name.isEmpty()); // assume verified
|
|
|
|
if (name.isEmpty() && allowUnnamed)
|
|
|
|
name = u"unnamed_" + QString::number(i);
|
2022-10-12 13:56:00 +00:00
|
|
|
|
|
|
|
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());
|
2021-11-01 15:18:30 +00:00
|
|
|
}
|
|
|
|
return parameters;
|
|
|
|
}
|
|
|
|
|
2022-04-19 11:35:36 +00:00
|
|
|
void QmltcCompiler::compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m,
|
|
|
|
const QQmlJSScope::ConstPtr &owner)
|
2021-11-01 15:18:30 +00:00
|
|
|
{
|
2024-06-18 14:17:16 +00:00
|
|
|
const QString returnType = m.returnType()->augmentedInternalName();
|
2022-11-10 10:38:51 +00:00
|
|
|
|
|
|
|
const QList<QmltcVariable> compiledParams = compileMethodParameters(m.parameters());
|
2023-05-05 07:30:27 +00:00
|
|
|
const auto methodType = m.methodType();
|
2021-11-01 15:18:30 +00:00
|
|
|
|
|
|
|
QStringList code;
|
2023-05-05 07:30:27 +00:00
|
|
|
if (methodType != QQmlJSMetaMethodType::Signal) {
|
2022-04-19 11:35:36 +00:00
|
|
|
QmltcCodeGenerator urlGenerator { m_url, m_visitor };
|
|
|
|
QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
|
2022-05-12 12:39:38 +00:00
|
|
|
&code, urlGenerator.urlMethodName() + u"()",
|
|
|
|
owner->ownRuntimeFunctionIndex(m.jsFunctionIndex()), u"this"_s, returnType,
|
2022-04-19 11:35:36 +00:00
|
|
|
compiledParams);
|
2021-11-01 15:18:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2023-05-05 07:30:27 +00:00
|
|
|
if (methodType != QQmlJSMetaMethodType::Signal) {
|
2022-03-21 09:21:18 +00:00
|
|
|
compiled.declarationPrefixes << u"Q_INVOKABLE"_s;
|
2022-04-19 11:35:36 +00:00
|
|
|
compiled.userVisible = m.access() == QQmlJSMetaMethod::Public;
|
|
|
|
} else {
|
|
|
|
compiled.userVisible = !m.isImplicitQmlPropertyChangeSignal();
|
|
|
|
}
|
2021-11-01 15:18:30 +00:00
|
|
|
current.functions.emplaceBack(compiled);
|
|
|
|
}
|
|
|
|
|
2022-10-17 13:27:12 +00:00
|
|
|
/*! \internal
|
|
|
|
Compiles an extra set of methods for Lists, that makes manipulating lists easier from C++
|
|
|
|
for the user.
|
|
|
|
*/
|
|
|
|
void QmltcCompiler::compileExtraListMethods(QmltcType ¤t, const QQmlJSMetaProperty &p)
|
|
|
|
{
|
|
|
|
QmltcPropertyData data(p);
|
2025-08-25 09:00:35 +00:00
|
|
|
const QString elementType = p.type()->elementType()->internalName() + u'*';
|
2022-10-17 13:27:12 +00:00
|
|
|
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);
|
2025-08-25 09:00:35 +00:00
|
|
|
append.parameterList.emplaceBack(elementType, u"toBeAppended"_s);
|
2022-10-17 13:27:12 +00:00
|
|
|
|
|
|
|
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));
|
2025-08-25 09:00:35 +00:00
|
|
|
at.returnType = elementType;
|
2022-10-17 13:27:12 +00:00
|
|
|
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());
|
2025-08-25 09:00:35 +00:00
|
|
|
replace.parameterList.emplaceBack(elementType, u"element"_s,
|
2022-10-17 13:27:12 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-29 09:56:25 +00:00
|
|
|
void QmltcCompiler::compileProperty(QmltcType ¤t, 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);
|
2022-06-29 15:21:49 +00:00
|
|
|
if (qIsReferenceTypeList(p)) {
|
2021-10-29 09:56:25 +00:00
|
|
|
const QString storageName = variableName + u"_storage";
|
2022-10-19 12:35:24 +00:00
|
|
|
current.variables.emplaceBack(
|
2025-08-25 09:00:35 +00:00
|
|
|
u"QList<" + p.type()->elementType()->internalName() + u"*>", storageName,
|
2022-10-19 12:35:24 +00:00
|
|
|
QString());
|
2022-03-03 13:03:58 +00:00
|
|
|
current.baselineCtor.initializerList.emplaceBack(variableName + u"(" + underlyingType
|
|
|
|
+ u"(this, std::addressof(" + storageName
|
|
|
|
+ u")))");
|
2022-10-17 13:27:12 +00:00
|
|
|
compileExtraListMethods(current, p);
|
2021-10-29 09:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2021-11-19 12:04:05 +00:00
|
|
|
QmltcPropertyData compilationData(p);
|
|
|
|
|
2021-10-29 09:56:25 +00:00
|
|
|
// 1. add setter and getter
|
2021-11-19 12:04:05 +00:00
|
|
|
// 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.
|
2024-11-28 14:35:18 +00:00
|
|
|
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;
|
|
|
|
|
2022-06-29 15:21:49 +00:00
|
|
|
if (p.isWritable() && !qIsReferenceTypeList(p)) {
|
2021-10-29 09:56:25 +00:00
|
|
|
QmltcMethod setter {};
|
2022-03-21 09:21:18 +00:00
|
|
|
setter.returnType = u"void"_s;
|
2021-11-19 12:04:05 +00:00
|
|
|
setter.name = compilationData.write;
|
|
|
|
// QmltcVariable
|
2021-12-17 14:52:17 +00:00
|
|
|
setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(underlyingType), name + u"_",
|
2022-03-21 09:21:18 +00:00
|
|
|
u""_s);
|
2021-10-29 09:56:25 +00:00
|
|
|
setter.body << variableName + u".setValue(" + name + u"_);";
|
2021-11-19 12:04:05 +00:00
|
|
|
setter.body << u"Q_EMIT " + compilationData.notify + u"();";
|
|
|
|
setter.userVisible = true;
|
2021-10-29 09:56:25 +00:00
|
|
|
current.functions.emplaceBack(setter);
|
2022-03-21 09:21:18 +00:00
|
|
|
mocPieces << u"WRITE"_s << setter.name;
|
2021-10-29 09:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 2. add bindable
|
2022-06-29 15:21:49 +00:00
|
|
|
if (!qIsReferenceTypeList(p)) {
|
2021-11-19 12:04:05 +00:00
|
|
|
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);
|
2022-03-21 09:21:18 +00:00
|
|
|
mocPieces << u"BINDABLE"_s << bindable.name;
|
2021-11-19 12:04:05 +00:00
|
|
|
}
|
2021-10-29 09:56:25 +00:00
|
|
|
|
|
|
|
// 3. add/check notify (actually, this is already done inside QmltcVisitor)
|
|
|
|
|
|
|
|
if (owner->isPropertyRequired(name))
|
2022-03-21 09:21:18 +00:00
|
|
|
mocPieces << u"REQUIRED"_s;
|
2021-10-29 09:56:25 +00:00
|
|
|
|
|
|
|
// 4. add moc entry
|
|
|
|
// e.g. Q_PROPERTY(QString p READ getP WRITE setP BINDABLE bindableP)
|
2022-03-21 09:21:18 +00:00
|
|
|
current.mocCode << u"Q_PROPERTY(" + mocPieces.join(u" "_s) + u")";
|
2021-10-29 09:56:25 +00:00
|
|
|
|
|
|
|
// 5. add extra moc entry if this property is marked default
|
|
|
|
if (name == owner->defaultPropertyName())
|
2022-03-21 09:21:18 +00:00
|
|
|
current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(name);
|
2021-10-29 09:56:25 +00:00
|
|
|
|
|
|
|
// structure: (C++ type name, name, C++ class name, C++ signal name)
|
2021-11-19 12:04:05 +00:00
|
|
|
current.properties.emplaceBack(underlyingType, variableName, current.cppType,
|
|
|
|
compilationData.notify);
|
2021-10-29 09:56:25 +00:00
|
|
|
}
|
|
|
|
|
2022-08-22 09:13:41 +00:00
|
|
|
/*!
|
|
|
|
* \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}.
|
|
|
|
*/
|
2022-04-22 10:51:41 +00:00
|
|
|
struct AliasResolutionFrame
|
|
|
|
{
|
2022-08-22 09:13:41 +00:00
|
|
|
/*!
|
|
|
|
* \internal
|
|
|
|
*
|
|
|
|
* Placeholder for the current resolved state. It is replaced later with
|
|
|
|
* the result from previous resolutions from the \c QStack<AliasResolutionFrame>.
|
|
|
|
*
|
|
|
|
* \sa unpackFrames()
|
|
|
|
*/
|
2022-04-22 10:51:41 +00:00
|
|
|
static QString inVar;
|
2022-08-22 09:13:41 +00:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \internal
|
|
|
|
*
|
|
|
|
* Steps to access this value as a list of C++ statements, to be used in
|
|
|
|
* conjunction with \c {epilogue}.
|
|
|
|
*/
|
2022-04-22 10:51:41 +00:00
|
|
|
QStringList prologue;
|
2022-08-22 09:13:41 +00:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \internal
|
|
|
|
*
|
|
|
|
* Steps to finish the statements of the \c prologue (e.g. closing brackets).
|
|
|
|
*/
|
2022-04-28 09:03:09 +00:00
|
|
|
QStringList epilogue;
|
2022-08-22 09:13:41 +00:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \internal
|
|
|
|
*
|
|
|
|
* Instructions on how to write the property, after it was loaded with the
|
|
|
|
* instructions from \c prologue. Has to happen before \c epilogue.
|
|
|
|
*/
|
2022-04-22 10:51:41 +00:00
|
|
|
QStringList epilogueForWrite;
|
2022-08-22 09:13:41 +00:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* \internal
|
|
|
|
*
|
|
|
|
* Name of the variable holding the result of this resolution step, to be
|
|
|
|
* used in the following resolution steps.
|
|
|
|
*/
|
2022-04-22 10:51:41 +00:00
|
|
|
QString outVar;
|
|
|
|
};
|
|
|
|
// special string replaced by outVar of the previous frame
|
|
|
|
QString AliasResolutionFrame::inVar = QStringLiteral("__QMLTC_ALIAS_FRAME_INPUT_VAR__");
|
|
|
|
|
2022-08-22 09:13:41 +00:00
|
|
|
/*!
|
|
|
|
* \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.
|
|
|
|
*/
|
2022-04-22 10:51:41 +00:00
|
|
|
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 ¤t, 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:
|
2022-04-28 09:03:09 +00:00
|
|
|
frames.push(
|
|
|
|
AliasResolutionFrame { QStringList(), QStringList(), QStringList(), u"this"_s });
|
2022-04-22 10:51:41 +00:00
|
|
|
};
|
|
|
|
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 {};
|
2022-06-08 10:33:14 +00:00
|
|
|
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());
|
2022-04-22 10:51:41 +00:00
|
|
|
|
|
|
|
// doing the above allows us to lookup id object by index (fast)
|
|
|
|
queryIdFrame.outVar = u"alias_objectById_" + aliasExprBits.front(); // unique enough
|
2022-07-26 12:32:23 +00:00
|
|
|
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"));";
|
2022-04-22 10:51:41 +00:00
|
|
|
queryIdFrame.prologue << u"Q_ASSERT(" + queryIdFrame.outVar + u");";
|
|
|
|
|
|
|
|
frames.push(queryIdFrame);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
aliasVisitor.processResolvedProperty = [&](const QQmlJSMetaProperty &p,
|
2022-04-28 09:03:09 +00:00
|
|
|
const QQmlJSScope::ConstPtr &owner) {
|
2022-04-22 10:51:41 +00:00
|
|
|
AliasResolutionFrame queryPropertyFrame {};
|
|
|
|
|
2022-04-28 09:03:09 +00:00
|
|
|
auto [extensionPrologue, extensionAccessor, extensionEpilogue] =
|
|
|
|
QmltcCodeGenerator::wrap_extensionType(
|
|
|
|
owner, p,
|
|
|
|
QmltcCodeGenerator::wrap_privateClass(AliasResolutionFrame::inVar, p));
|
|
|
|
QString inVar = extensionAccessor;
|
|
|
|
queryPropertyFrame.prologue += extensionPrologue;
|
2022-04-22 10:51:41 +00:00
|
|
|
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;
|
2022-04-28 09:03:09 +00:00
|
|
|
queryPropertyFrame.epilogue += extensionEpilogue;
|
2022-04-22 10:51:41 +00:00
|
|
|
|
|
|
|
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 {};
|
2022-04-28 09:03:09 +00:00
|
|
|
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;
|
2022-04-22 10:51:41 +00:00
|
|
|
frames.push(customFinalFrame);
|
|
|
|
}
|
|
|
|
|
|
|
|
const QString latestAccessor = frames.top().outVar;
|
|
|
|
const QStringList prologue =
|
|
|
|
joinFrames(frames, [](const AliasResolutionFrame &frame) { return frame.prologue; });
|
2022-04-28 09:03:09 +00:00
|
|
|
const QStringList epilogue =
|
|
|
|
joinFrames(frames, [](const AliasResolutionFrame &frame) { return frame.epilogue; });
|
2022-04-22 10:51:41 +00:00
|
|
|
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;
|
2022-06-23 07:48:04 +00:00
|
|
|
if (result.kind == QQmlJSUtils::AliasTarget_Property) {
|
2022-07-25 10:58:15 +00:00
|
|
|
if (QString read = result.property.read(); !read.isEmpty()
|
|
|
|
&& !QQmlJSUtils::bindablePropertyHasDefaultAccessor(
|
|
|
|
result.property, QQmlJSUtils::PropertyAccessor_Read)) {
|
2022-06-23 07:48:04 +00:00
|
|
|
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
|
2022-04-22 10:51:41 +00:00
|
|
|
getter.body << u"return " + latestAccessor + u";";
|
2022-06-23 07:48:04 +00:00
|
|
|
}
|
2022-04-28 09:03:09 +00:00
|
|
|
getter.body += epilogue;
|
2022-04-22 10:51:41 +00:00
|
|
|
getter.userVisible = true;
|
|
|
|
current.functions.emplaceBack(getter);
|
|
|
|
mocLines << u"READ"_s << getter.name;
|
|
|
|
|
2022-06-23 07:48:04 +00:00
|
|
|
if (result.property.isWritable()) {
|
2022-04-22 10:51:41 +00:00
|
|
|
Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
|
|
|
|
QmltcMethod setter {};
|
|
|
|
setter.returnType = u"void"_s;
|
|
|
|
setter.name = compilationData.write;
|
|
|
|
|
2022-06-23 07:48:04 +00:00
|
|
|
const QString setName = result.property.write();
|
2022-04-22 10:51:41 +00:00
|
|
|
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 {
|
2022-11-10 10:38:51 +00:00
|
|
|
setter.parameterList = compileMethodParameters(methods.at(0).parameters(),
|
2022-04-22 10:51:41 +00:00
|
|
|
/* 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);
|
2022-07-25 10:58:15 +00:00
|
|
|
if (!setName.isEmpty()
|
|
|
|
&& !QQmlJSUtils::bindablePropertyHasDefaultAccessor(
|
|
|
|
result.property, QQmlJSUtils::PropertyAccessor_Write)) {
|
2022-06-23 07:48:04 +00:00
|
|
|
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);
|
|
|
|
}
|
2022-04-22 10:51:41 +00:00
|
|
|
setter.body += joinFrames(
|
|
|
|
frames, [](const AliasResolutionFrame &frame) { return frame.epilogueForWrite; });
|
2022-04-28 09:03:09 +00:00
|
|
|
setter.body += epilogue; // NB: *after* epilogueForWrite - see prologue construction
|
2022-04-22 10:51:41 +00:00
|
|
|
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";";
|
2022-04-28 09:03:09 +00:00
|
|
|
bindable.body += epilogue;
|
2022-04-22 10:51:41 +00:00
|
|
|
bindable.userVisible = true;
|
|
|
|
current.functions.emplaceBack(bindable);
|
|
|
|
mocLines << u"BINDABLE"_s << bindable.name;
|
|
|
|
}
|
2022-08-22 09:13:03 +00:00
|
|
|
|
2022-04-22 10:51:41 +00:00
|
|
|
// 3. add notify - which is pretty special
|
2022-08-22 09:13:03 +00:00
|
|
|
// 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()) {
|
|
|
|
|
2022-04-22 10:51:41 +00:00
|
|
|
Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
|
|
|
|
|
2022-08-22 09:13:03 +00:00
|
|
|
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()) {
|
2022-04-28 09:03:09 +00:00
|
|
|
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; });
|
|
|
|
|
2022-04-22 10:51:41 +00:00
|
|
|
// notify is very special
|
|
|
|
current.endInit.body << u"{ // alias notify connection:"_s;
|
2022-04-28 09:03:09 +00:00
|
|
|
current.endInit.body += notifyPrologue;
|
2022-04-22 10:51:41 +00:00
|
|
|
// 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)
|
2022-07-26 12:32:23 +00:00
|
|
|
const QString cppType = (m_visitor->qmlComponentIndex(result.owner) == -1)
|
|
|
|
? result.owner->internalName()
|
|
|
|
: u"QQmlComponent"_s;
|
2022-04-28 09:03:09 +00:00
|
|
|
const QString latestAccessorNonPrivate = notifyFrames.top().outVar;
|
2022-07-26 12:32:23 +00:00
|
|
|
current.endInit.body << u"QObject::connect(" + latestAccessorNonPrivate + u", &" + cppType
|
|
|
|
+ u"::" + notifyName + u", this, &" + current.cppType + u"::"
|
|
|
|
+ compilationData.notify + u");";
|
2022-04-28 09:03:09 +00:00
|
|
|
current.endInit.body += notifyEpilogue;
|
2022-04-22 10:51:41 +00:00
|
|
|
current.endInit.body << u"}"_s;
|
|
|
|
}
|
2022-08-22 09:13:03 +00:00
|
|
|
|
2022-07-29 08:27:50 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2022-08-22 09:13:03 +00:00
|
|
|
// 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;
|
2022-04-22 10:51:41 +00:00
|
|
|
|
|
|
|
// 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
static QString generate_callCompilationUnit(const QString &urlMethodName)
|
|
|
|
{
|
|
|
|
return u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(%1())"_s.arg(urlMethodName);
|
|
|
|
}
|
|
|
|
|
2022-08-02 14:20:07 +00:00
|
|
|
static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope,
|
|
|
|
const QString &propertyName);
|
|
|
|
|
2022-09-20 09:30:58 +00:00
|
|
|
/*!
|
|
|
|
* \internal
|
|
|
|
* Helper method used to keep compileBindingByType() readable.
|
|
|
|
*/
|
|
|
|
void QmltcCompiler::compileObjectBinding(QmltcType ¤t,
|
|
|
|
const QQmlJSMetaPropertyBinding &binding,
|
|
|
|
const QQmlJSScope::ConstPtr &type,
|
|
|
|
const BindingAccessorData &accessor)
|
|
|
|
{
|
2023-05-05 07:30:27 +00:00
|
|
|
Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Object);
|
2022-09-20 09:30:58 +00:00
|
|
|
|
|
|
|
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)) {
|
2022-09-20 13:11:54 +00:00
|
|
|
Q_ASSERT(unprocessedListProperty == property || unprocessedListBindings.empty());
|
|
|
|
unprocessedListBindings.append(value);
|
|
|
|
unprocessedListProperty = property;
|
2022-09-20 09:30:58 +00:00
|
|
|
} else {
|
|
|
|
QmltcCodeGenerator::generate_assignToProperty(¤t.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) ? ¤t.endInit.body : ¤t.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) {
|
2023-02-03 13:58:26 +00:00
|
|
|
QString idString = m_visitor->addressableScopes().id(object, object);
|
2022-09-20 09:30:58 +00:00
|
|
|
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 == ¤t.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 ¤t,
|
|
|
|
const QQmlJSMetaPropertyBinding &binding,
|
|
|
|
const QQmlJSScope::ConstPtr &type,
|
|
|
|
const BindingAccessorData &accessor)
|
|
|
|
{
|
2023-05-05 07:30:27 +00:00
|
|
|
Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::ValueSource
|
|
|
|
|| binding.bindingType() == QQmlSA::BindingType::Interceptor);
|
2022-09-20 09:30:58 +00:00
|
|
|
|
|
|
|
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;
|
2023-05-05 07:30:27 +00:00
|
|
|
if (binding.bindingType() == QQmlSA::BindingType::Interceptor)
|
2022-09-20 09:30:58 +00:00
|
|
|
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 ¤t,
|
|
|
|
const QQmlJSMetaPropertyBinding &binding,
|
|
|
|
const QQmlJSScope::ConstPtr &type,
|
|
|
|
const BindingAccessorData &accessor)
|
|
|
|
{
|
2023-05-05 07:30:27 +00:00
|
|
|
Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::AttachedProperty);
|
2022-09-20 09:30:58 +00:00
|
|
|
|
|
|
|
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
|
2024-11-07 12:32:29 +00:00
|
|
|
const auto attachedType = binding.attachedType();
|
2022-09-20 09:30:58 +00:00
|
|
|
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()) {
|
2024-11-07 13:18:01 +00:00
|
|
|
attachedMemberName = uniqueVariableName(attachingTypeName);
|
|
|
|
|
2022-09-20 09:30:58 +00:00
|
|
|
// 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());
|
2022-09-20 13:11:54 +00:00
|
|
|
compileBinding(current, subbindings.begin(), subbindings.end(), attachedType,
|
|
|
|
{ type, attachedMemberName, propertyName, false });
|
2022-09-20 09:30:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \internal
|
|
|
|
* Helper method used to keep compileBindingByType() readable.
|
|
|
|
*/
|
|
|
|
void QmltcCompiler::compileGroupPropertyBinding(QmltcType ¤t,
|
|
|
|
const QQmlJSMetaPropertyBinding &binding,
|
|
|
|
const QQmlJSScope::ConstPtr &type,
|
|
|
|
const BindingAccessorData &accessor)
|
|
|
|
{
|
2023-05-05 07:30:27 +00:00
|
|
|
Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::GroupProperty);
|
2022-09-20 09:30:58 +00:00
|
|
|
|
|
|
|
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
|
2022-09-20 13:11:54 +00:00
|
|
|
const auto compile = [&](const auto &bStart, const auto &bEnd) {
|
|
|
|
compileBinding(current, bStart, bEnd, groupType,
|
|
|
|
{ type, groupAccessor, propertyName, isValueType });
|
2022-09-20 09:30:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
auto it = subbindings.begin();
|
2022-09-20 13:11:54 +00:00
|
|
|
Q_ASSERT(std::all_of(it, firstScript, [](const auto &x) {
|
2023-05-05 07:30:27 +00:00
|
|
|
return x.bindingType() != QQmlSA::BindingType::Script;
|
2022-09-20 13:11:54 +00:00
|
|
|
}));
|
|
|
|
compile(it, firstScript);
|
|
|
|
it = firstScript;
|
2022-09-20 09:30:58 +00:00
|
|
|
|
|
|
|
// 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
|
2022-09-20 13:11:54 +00:00
|
|
|
Q_ASSERT(std::all_of(it, subbindings.end(), [](const auto &x) {
|
2023-05-05 07:30:27 +00:00
|
|
|
return x.bindingType() == QQmlSA::BindingType::Script;
|
2022-09-20 13:11:54 +00:00
|
|
|
}));
|
|
|
|
compile(it, subbindings.end());
|
2022-09-20 09:30:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
|
|
|
* \internal
|
|
|
|
* Helper method used to keep compileBindingByType() readable.
|
|
|
|
*/
|
|
|
|
void QmltcCompiler::compileTranslationBinding(QmltcType ¤t,
|
|
|
|
const QQmlJSMetaPropertyBinding &binding,
|
|
|
|
const QQmlJSScope::ConstPtr &type,
|
|
|
|
const BindingAccessorData &accessor)
|
|
|
|
{
|
2023-05-05 07:30:27 +00:00
|
|
|
Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Translation
|
|
|
|
|| binding.bindingType() == QQmlSA::BindingType::TranslationById);
|
2022-09-20 09:30:58 +00:00
|
|
|
|
|
|
|
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(¤t.endInit.body, info);
|
|
|
|
}
|
|
|
|
|
2022-09-20 13:11:54 +00:00
|
|
|
void QmltcCompiler::processLastListBindings(QmltcType ¤t, const QQmlJSScope::ConstPtr &type,
|
|
|
|
const BindingAccessorData &accessor)
|
|
|
|
{
|
|
|
|
if (unprocessedListBindings.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
QmltcCodeGenerator::generate_assignToListProperty(
|
|
|
|
¤t.endInit.body, type, unprocessedListProperty, unprocessedListBindings,
|
|
|
|
accessor.name,
|
|
|
|
m_uniques[UniqueStringId(current, unprocessedListProperty.propertyName())]
|
|
|
|
.qmlListVariableName);
|
|
|
|
|
|
|
|
unprocessedListBindings.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
void QmltcCompiler::compileBinding(QmltcType ¤t,
|
|
|
|
QList<QQmlJSMetaPropertyBinding>::iterator bindingStart,
|
|
|
|
QList<QQmlJSMetaPropertyBinding>::iterator bindingEnd,
|
2021-11-15 16:10:31 +00:00
|
|
|
const QQmlJSScope::ConstPtr &type,
|
|
|
|
const BindingAccessorData &accessor)
|
|
|
|
{
|
2022-09-20 13:11:54 +00:00
|
|
|
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.
|
2023-05-05 07:30:27 +00:00
|
|
|
if (binding.bindingType() == QQmlSA::BindingType::GroupProperty
|
2022-09-20 13:11:54 +00:00
|
|
|
&& 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;
|
|
|
|
}
|
2021-11-15 16:10:31 +00:00
|
|
|
}
|
2022-05-12 12:39:38 +00:00
|
|
|
|
2022-09-20 13:11:54 +00:00
|
|
|
const QQmlJSMetaProperty metaProperty = type->property(propertyName);
|
|
|
|
const QQmlJSScope::ConstPtr propertyType = metaProperty.type();
|
2021-11-15 16:10:31 +00:00
|
|
|
|
2022-09-20 13:11:54 +00:00
|
|
|
if (!(qIsReferenceTypeList(metaProperty) && unprocessedListProperty == metaProperty)) {
|
|
|
|
processLastListBindings(current, type, accessor);
|
2022-05-12 12:39:38 +00:00
|
|
|
}
|
2022-09-20 13:11:54 +00:00
|
|
|
|
|
|
|
compileBindingByType(current, binding, type, accessor);
|
2022-05-12 12:39:38 +00:00
|
|
|
}
|
2021-11-15 16:10:31 +00:00
|
|
|
|
2022-09-20 13:11:54 +00:00
|
|
|
processLastListBindings(current, type, accessor);
|
2022-09-20 09:30:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void QmltcCompiler::compileBindingByType(QmltcType ¤t,
|
|
|
|
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(¤t.endInit.body, type, p, value,
|
|
|
|
accessor.name, constructFromQObject);
|
|
|
|
};
|
2021-11-15 16:10:31 +00:00
|
|
|
switch (binding.bindingType()) {
|
2023-05-05 07:30:27 +00:00
|
|
|
case QQmlSA::BindingType::BoolLiteral: {
|
Refactor QQmlJSMetaPropertyBinding
- Store the "binding content", i.e. the literal, object, interceptor or
value source in a std::variant. This saves space compared to the
previous approach (where we had individual fields), and also helps
with type-safety. We also get rid of m_bindingType, which can now be
obtained via BindingType(m_bindingContent.index()), as long as we keep
the types in the variant in the correct order.
- Remove a few methods that were not used anywhere. Those can be brought
back once we actually need them.
- Removed the setLiteral method in lieu of type-safe setX methods where
X is one of the literal types. QQmlJSImportVisitor has been refactored
to make use of this, and its parseLiteralBinding method has been
changed into a parseLiteralOrScriptBinding method. This simplifies the
control flow, and ensures that we always add the parsed binding.
- Literals no longer store the literal type (as in, the actual
QQmlJSScope pointer) themselves. Instead, literalType takes a pointer
to a QQmlJSTypeResolver, and we use that one to resolve the type on
demand.
Change-Id: I0612d49f2f46fec0fa90f0f5047d8c9f831214ef
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
2022-02-24 10:58:38 +00:00
|
|
|
const bool value = binding.boolValue();
|
2022-09-20 09:30:58 +00:00
|
|
|
assignToProperty(metaProperty, value ? u"true"_s : u"false"_s);
|
2021-11-15 16:10:31 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-05-05 07:30:27 +00:00
|
|
|
case QQmlSA::BindingType::NumberLiteral: {
|
2022-09-20 09:30:58 +00:00
|
|
|
assignToProperty(metaProperty, QString::number(binding.numberValue()));
|
2021-11-15 16:10:31 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-05-05 07:30:27 +00:00
|
|
|
case QQmlSA::BindingType::StringLiteral: {
|
2023-06-07 14:46:01 +00:00
|
|
|
QString value = QQmlJSUtils::toLiteral(binding.stringValue());
|
|
|
|
if (auto type = metaProperty.type()) {
|
|
|
|
if (type->internalName() == u"QUrl"_s) {
|
2023-06-12 06:28:11 +00:00
|
|
|
value = u"QUrl(%1)"_s.arg(value);
|
2023-06-07 14:46:01 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
assignToProperty(metaProperty, value);
|
2021-11-15 16:10:31 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-05-05 07:30:27 +00:00
|
|
|
case QQmlSA::BindingType::RegExpLiteral: {
|
2022-07-26 14:29:02 +00:00
|
|
|
const QString value =
|
|
|
|
u"QRegularExpression(%1)"_s.arg(QQmlJSUtils::toLiteral(binding.regExpValue()));
|
2022-09-20 09:30:58 +00:00
|
|
|
assignToProperty(metaProperty, value);
|
2022-07-26 14:29:02 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-05-05 07:30:27 +00:00
|
|
|
case QQmlSA::BindingType::Null: {
|
2021-11-15 16:10:31 +00:00
|
|
|
// poor check: null bindings are only supported for var and objects
|
2022-05-12 12:39:38 +00:00
|
|
|
Q_ASSERT(propertyType->isSameType(m_typeResolver->varType())
|
|
|
|
|| propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference);
|
|
|
|
if (propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
|
2022-09-20 09:30:58 +00:00
|
|
|
assignToProperty(metaProperty, u"nullptr"_s);
|
2022-05-12 12:39:38 +00:00
|
|
|
else
|
2022-09-20 09:30:58 +00:00
|
|
|
assignToProperty(metaProperty, u"QVariant::fromValue(nullptr)"_s);
|
2022-05-12 12:39:38 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-05-05 07:30:27 +00:00
|
|
|
case QQmlSA::BindingType::Script: {
|
2024-11-07 13:18:01 +00:00
|
|
|
QString bindingSymbolName
|
|
|
|
= uniqueVariableName(type->internalName() + u'_' + propertyName + u"_binding");
|
2022-05-12 12:39:38 +00:00
|
|
|
compileScriptBinding(current, binding, bindingSymbolName, type, propertyName, propertyType,
|
|
|
|
accessor);
|
|
|
|
break;
|
|
|
|
}
|
2023-05-05 07:30:27 +00:00
|
|
|
case QQmlSA::BindingType::Object: {
|
2022-09-20 09:30:58 +00:00
|
|
|
compileObjectBinding(current, binding, type, accessor);
|
2022-05-12 12:39:38 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-05-05 07:30:27 +00:00
|
|
|
case QQmlSA::BindingType::Interceptor:
|
2022-05-12 12:39:38 +00:00
|
|
|
Q_FALLTHROUGH();
|
2023-05-05 07:30:27 +00:00
|
|
|
case QQmlSA::BindingType::ValueSource: {
|
2022-09-20 09:30:58 +00:00
|
|
|
compileValueSourceOrInterceptorBinding(current, binding, type, accessor);
|
2022-05-12 12:39:38 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-05-05 07:30:27 +00:00
|
|
|
case QQmlSA::BindingType::AttachedProperty: {
|
2022-09-20 09:30:58 +00:00
|
|
|
compileAttachedPropertyBinding(current, binding, type, accessor);
|
2022-05-12 12:39:38 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-05-05 07:30:27 +00:00
|
|
|
case QQmlSA::BindingType::GroupProperty: {
|
2022-09-20 09:30:58 +00:00
|
|
|
compileGroupPropertyBinding(current, binding, type, accessor);
|
2021-11-15 16:10:31 +00:00
|
|
|
break;
|
|
|
|
}
|
2022-08-02 14:20:07 +00:00
|
|
|
|
2023-05-05 07:30:27 +00:00
|
|
|
case QQmlSA::BindingType::TranslationById:
|
|
|
|
case QQmlSA::BindingType::Translation: {
|
2022-09-20 09:30:58 +00:00
|
|
|
compileTranslationBinding(current, binding, type, accessor);
|
2022-08-02 14:20:07 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-05-05 07:30:27 +00:00
|
|
|
case QQmlSA::BindingType::Invalid: {
|
2022-05-12 12:39:38 +00:00
|
|
|
recordError(binding.sourceLocation(), u"This binding is invalid"_s);
|
2021-11-15 16:10:31 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
2022-05-12 12:39:38 +00:00
|
|
|
recordError(binding.sourceLocation(), u"Binding is not supported"_s);
|
2021-11-15 16:10:31 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
// 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);
|
2022-07-01 12:06:35 +00:00
|
|
|
const QQmlJSMetaProperty p = owner->ownProperty(propertyName);
|
2022-05-12 12:39:38 +00:00
|
|
|
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 };
|
|
|
|
|
2022-07-01 12:06:35 +00:00
|
|
|
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;
|
2023-11-09 14:58:38 +00:00
|
|
|
|
|
|
|
// extension namespace and JavaScript properties are ignored
|
|
|
|
if (m == QQmlJSScope::ExtensionNamespace || m == QQmlJSScope::ExtensionJavaScript)
|
2022-07-01 12:06:35 +00:00
|
|
|
return;
|
2023-11-09 14:58:38 +00:00
|
|
|
|
2022-07-01 12:06:35 +00:00
|
|
|
index += int(type->ownProperties().size());
|
|
|
|
};
|
|
|
|
QQmlJSUtils::traverseFollowingMetaObjectHierarchy(scope, owner, increment);
|
2022-05-12 12:39:38 +00:00
|
|
|
return { p, index };
|
|
|
|
}
|
|
|
|
|
|
|
|
void QmltcCompiler::compileScriptBinding(QmltcType ¤t,
|
|
|
|
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());
|
|
|
|
|
2023-05-05 07:30:27 +00:00
|
|
|
const auto signalMethods = objectType->methods(name, QQmlJSMetaMethodType::Signal);
|
2022-05-12 12:39:38 +00:00
|
|
|
Q_ASSERT(!signalMethods.isEmpty()); // an error somewhere else
|
|
|
|
QQmlJSMetaMethod signal = signalMethods.at(0);
|
2023-05-05 07:30:27 +00:00
|
|
|
Q_ASSERT(signal.methodType() == QQmlJSMetaMethodType::Signal);
|
2022-05-12 12:39:38 +00:00
|
|
|
|
|
|
|
const QString signalName = signal.methodName();
|
|
|
|
const QString slotName = newSymbol(signalName + u"_slot");
|
|
|
|
|
2024-06-18 14:17:16 +00:00
|
|
|
const QString signalReturnType = signal.returnType()->augmentedInternalName();
|
2022-11-10 10:38:51 +00:00
|
|
|
const QList<QmltcVariable> slotParameters =
|
|
|
|
compileMethodParameters(signal.parameters(), /* allow unnamed = */ true);
|
2022-05-12 12:39:38 +00:00
|
|
|
|
|
|
|
// 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);
|
2023-05-05 07:30:27 +00:00
|
|
|
slotMethod.type = QQmlJSMetaMethodType::Slot;
|
2022-05-12 12:39:38 +00:00
|
|
|
|
|
|
|
current.functions << std::move(slotMethod);
|
2022-10-12 13:56:00 +00:00
|
|
|
current.setComplexBindings.body << u"QObject::connect(" + This_signal + u", " + u"&"
|
|
|
|
+ objectClassName_signal + u"::" + signalName + u", " + This_slot + u", &"
|
|
|
|
+ objectClassName_slot + u"::" + slotName + u");";
|
2022-05-12 12:39:38 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
switch (binding.scriptKind()) {
|
2023-08-30 08:35:00 +00:00
|
|
|
case QQmlSA::ScriptBindingKind::PropertyBinding: {
|
2022-05-12 12:39:38 +00:00
|
|
|
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(
|
2022-07-27 15:03:44 +00:00
|
|
|
¤t.setComplexBindings.body, generate_callCompilationUnit(m_urlMethodName),
|
2022-05-12 12:39:38 +00:00
|
|
|
u"this"_s, // NB: always using enclosing object as a scope for the binding
|
|
|
|
static_cast<qsizetype>(objectType->ownRuntimeFunctionIndex(binding.scriptIndex())),
|
|
|
|
bindingTarget, // binding target
|
2022-07-01 12:06:35 +00:00
|
|
|
// value types are special and are bound through valueTypeIndex
|
|
|
|
accessor.isValueType ? QQmlJSScope::ConstPtr() : objectType, absoluteIndex,
|
|
|
|
property, valueTypeIndex, accessor.name);
|
2022-05-12 12:39:38 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-08-30 08:35:00 +00:00
|
|
|
case QQmlSA::ScriptBindingKind::SignalHandler: {
|
2023-08-10 07:45:37 +00:00
|
|
|
const auto name = QQmlSignalNames::handlerNameToSignalName(propertyName);
|
2022-05-12 12:39:38 +00:00
|
|
|
Q_ASSERT(name.has_value());
|
|
|
|
compileScriptSignal(*name);
|
|
|
|
break;
|
|
|
|
}
|
2023-08-30 08:35:00 +00:00
|
|
|
case QQmlSA ::ScriptBindingKind::ChangeHandler: {
|
2022-05-12 12:39:38 +00:00
|
|
|
const QString objectClassName = objectType->internalName();
|
|
|
|
const QString bindingFunctorName = newSymbol(bindingSymbolName + u"Functor");
|
|
|
|
|
2023-08-10 07:45:37 +00:00
|
|
|
const auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName);
|
2022-05-12 12:39:38 +00:00
|
|
|
Q_ASSERT(signalName.has_value()); // an error somewhere else
|
2023-08-10 07:45:37 +00:00
|
|
|
const auto actualProperty =
|
|
|
|
QQmlJSUtils::propertyFromChangedHandler(objectType, propertyName);
|
2022-05-12 12:39:38 +00:00
|
|
|
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);
|
|
|
|
|
2024-10-16 08:38:30 +00:00
|
|
|
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);
|
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
// 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<"
|
2022-05-12 12:39:38 +00:00
|
|
|
+ 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-16 07:35:33 +00:00
|
|
|
QT_END_NAMESPACE
|