1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "qmltccompiler.h"
5#include "qmltcoutputir.h"
6#include "qmltccodewriter.h"
7#include "qmltcpropertyutils.h"
8#include "qmltccompilerpieces.h"
9
10#include <QtCore/qloggingcategory.h>
11#include <private/qqmljsutils_p.h>
12
13#include <algorithm>
14
15QT_BEGIN_NAMESPACE
16using namespace Qt::StringLiterals;
17
18bool qIsReferenceTypeList(const QQmlJSMetaProperty &p)
19{
20 if (QQmlJSScope::ConstPtr type = p.type())
21 return type->isListProperty();
22 return false;
23}
24
25Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg);
26
27const QString QmltcCodeGenerator::privateEngineName = u"ePriv"_s;
28const QString QmltcCodeGenerator::typeCountName = u"q_qmltc_typeCount"_s;
29
30QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor,
31 QQmlJSLogger *logger)
32 : m_url(url), m_typeResolver(resolver), m_visitor(visitor), m_logger(logger)
33{
34 Q_UNUSED(m_typeResolver);
35 Q_ASSERT(!hasErrors());
36}
37
38// needed due to std::unique_ptr<CodeGenerator> with CodeGenerator being
39// incomplete type in the header (~std::unique_ptr<> fails with a static_assert)
40QmltcCompiler::~QmltcCompiler() = default;
41
42QString QmltcCompiler::newSymbol(const QString &base)
43{
44 QString symbol = base;
45 symbol.replace(before: QLatin1String("."), after: QLatin1String("_"));
46 while (symbol.startsWith(c: QLatin1Char('_')) && symbol.size() >= 2
47 && (symbol[1].isUpper() || symbol[1] == QLatin1Char('_'))) {
48 symbol.remove(i: 0, len: 1);
49 }
50 if (!m_symbols.contains(key: symbol)) {
51 m_symbols.insert(key: symbol, value: 1);
52 } else {
53 symbol += u"_" + QString::number(m_symbols[symbol]++);
54 }
55 return symbol;
56}
57
58void QmltcCompiler::compile(const QmltcCompilerInfo &info)
59{
60 m_info = info;
61 Q_ASSERT(!m_info.outputCppFile.isEmpty());
62 Q_ASSERT(!m_info.outputHFile.isEmpty());
63 Q_ASSERT(!m_info.resourcePath.isEmpty());
64
65 // Note: we only compile "pure" QML types. any component-wrapped type is
66 // expected to appear through a binding
67
68 const auto isComponent = [](const QQmlJSScope::ConstPtr &type) {
69 auto base = type->baseType();
70 return base && base->internalName() == u"QQmlComponent"_s;
71 };
72
73 QmltcCodeGenerator generator { .documentUrl: m_url, .visitor: m_visitor };
74
75 QmltcMethod urlMethod;
76 compileUrlMethod(urlMethod, urlMethodName: generator.urlMethodName());
77 m_urlMethodName = urlMethod.name;
78
79 // sort inline components to compile them in the right order
80 // a inherits b => b needs to be defined in the cpp file before a!
81 // r is the root => r needs to be compiled at the end!
82 // otherwise => sort them by inline component names to have consistent output
83 auto sortedInlineComponentNames = m_visitor->inlineComponentNames();
84 std::sort(first: sortedInlineComponentNames.begin(), last: sortedInlineComponentNames.end(),
85 comp: [&](const InlineComponentOrDocumentRootName &a,
86 const InlineComponentOrDocumentRootName &b) {
87 const auto *inlineComponentAName = std::get_if<InlineComponentNameType>(ptr: &a);
88 const auto *inlineComponentBName = std::get_if<InlineComponentNameType>(ptr: &b);
89
90 // the root comes at last, so (a < b) == true when b is the root and a is not
91 if (inlineComponentAName && !inlineComponentBName)
92 return true;
93
94 // b requires a to be declared before b when b inherits from a, therefore (a < b)
95 // == true
96 if (inlineComponentAName && inlineComponentBName) {
97 QQmlJSScope::ConstPtr inlineComponentA = m_visitor->inlineComponent(inlineComponentName: a);
98 QQmlJSScope::ConstPtr inlineComponentB = m_visitor->inlineComponent(inlineComponentName: b);
99 if (inlineComponentB->inherits(base: inlineComponentA)) {
100 return true;
101 } else if (inlineComponentA->inherits(base: inlineComponentB)) {
102 return false;
103 } else {
104 // fallback to default sorting based on names
105 return *inlineComponentAName < *inlineComponentBName;
106 }
107 }
108 Q_ASSERT(!inlineComponentAName || !inlineComponentBName);
109 // a is the root or both a and b are the root
110 return false;
111 });
112
113 QList<QmltcType> compiledTypes;
114 for (const auto &inlineComponent : sortedInlineComponentNames) {
115 const QList<QQmlJSScope::ConstPtr> &pureTypes = m_visitor->pureQmlTypes(inlineComponent);
116 Q_ASSERT(!pureTypes.empty());
117 const QQmlJSScope::ConstPtr &root = pureTypes.front();
118 if (isComponent(root)) {
119 compiledTypes.emplaceBack(); // create empty type
120 const auto compile = [&](QmltcType &current, const QQmlJSScope::ConstPtr &type) {
121 generator.generate_initCodeForTopLevelComponent(current, type);
122 };
123 compileType(current&: compiledTypes.back(), type: root, compileElements: compile);
124 } else {
125 const auto compile = [this](QmltcType &current, const QQmlJSScope::ConstPtr &type) {
126 compileTypeElements(current, type);
127 };
128
129 for (const auto &type : pureTypes) {
130 Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope);
131 compiledTypes.emplaceBack(); // create empty type
132 compileType(current&: compiledTypes.back(), type, compileElements: compile);
133 }
134 }
135 }
136
137 if (hasErrors())
138 return;
139
140 QmltcProgram program;
141 program.url = m_url;
142 program.cppPath = m_info.outputCppFile;
143 program.hPath = m_info.outputHFile;
144 program.outNamespace = m_info.outputNamespace;
145 program.exportMacro = m_info.exportMacro;
146 program.compiledTypes = compiledTypes;
147 program.includes = m_visitor->cppIncludeFiles();
148 if (!m_info.exportMacro.isEmpty() && !m_info.exportInclude.isEmpty())
149 program.includes += (m_info.exportInclude);
150 program.urlMethod = urlMethod;
151
152 QmltcOutput out;
153 QmltcOutputWrapper code(out);
154 QmltcCodeWriter::write(code, program);
155}
156
157void QmltcCompiler::compileUrlMethod(QmltcMethod &urlMethod, const QString &urlMethodName)
158{
159 urlMethod.name = urlMethodName;
160 urlMethod.returnType = u"const QUrl&"_s;
161 urlMethod.body << u"static QUrl url {QStringLiteral(\"qrc:%1\")};"_s.arg(a: m_info.resourcePath);
162 urlMethod.body << u"return url;"_s;
163 urlMethod.declarationPrefixes << u"static"_s;
164 urlMethod.modifiers << u"noexcept"_s;
165}
166
167void QmltcCompiler::compileType(
168 QmltcType &current, const QQmlJSScope::ConstPtr &type,
169 std::function<void(QmltcType &, const QQmlJSScope::ConstPtr &)> compileElements)
170{
171 Q_ASSERT(!type->internalName().isEmpty());
172 current.cppType = type->internalName();
173 Q_ASSERT(!type->baseType()->internalName().isEmpty());
174 const QString baseClass = type->baseType()->internalName();
175
176 const auto rootType = m_visitor->result();
177 const InlineComponentOrDocumentRootName name = type->enclosingInlineComponentName();
178 QQmlJSScope::ConstPtr inlineComponentType = m_visitor->inlineComponent(inlineComponentName: name);
179 Q_ASSERT(inlineComponentType);
180 const bool documentRoot = (type == rootType);
181 const bool inlineComponent = type->isInlineComponent();
182 const bool isAnonymous = !documentRoot || type->internalName().at(i: 0).isLower();
183 const bool isSingleton = type->isSingleton();
184
185 QmltcCodeGenerator generator { .documentUrl: m_url, .visitor: m_visitor };
186
187 current.baseClasses = { baseClass };
188 if (!documentRoot) {
189 // make document root a friend to allow it to access init and endInit
190 const QString rootInternalName =
191 m_visitor->inlineComponent(inlineComponentName: type->enclosingInlineComponentName())->internalName();
192 if (rootInternalName != current.cppType) // avoid GCC13 warning on self-befriending
193 current.otherCode << "friend class %1;"_L1.arg(args: rootInternalName);
194 }
195 if (documentRoot || inlineComponent) {
196 auto name = type->inlineComponentName()
197 ? InlineComponentOrDocumentRootName(*type->inlineComponentName())
198 : InlineComponentOrDocumentRootName(RootDocumentNameType());
199 // make QQmltcObjectCreationBase<DocumentRoot> a friend to allow it to
200 // be created for the root object
201 current.otherCode << u"friend class QQmltcObjectCreationBase<%1>;"_s.arg(
202 a: inlineComponentType->internalName());
203 // generate typeCount for all components (root + inlineComponents)
204 QmltcMethod typeCountMethod;
205 typeCountMethod.name = QmltcCodeGenerator::typeCountName;
206 typeCountMethod.returnType = u"uint"_s;
207 typeCountMethod.body << u"return " + generator.generate_typeCount(inlinedComponent: name) + u";";
208 current.typeCount = typeCountMethod;
209 } else {
210 // make an immediate parent a friend since that parent
211 // would create the object through a non-public constructor
212 const auto realQmlScope = [](const QQmlJSScope::ConstPtr &scope) {
213 if (scope->isArrayScope())
214 return scope->parentScope();
215 return scope;
216 };
217 current.otherCode << u"friend class %1;"_s.arg(
218 a: realQmlScope(type->parentScope())->internalName());
219 }
220
221 // make QQmltcObjectCreationHelper a friend of every type since it provides
222 // useful helper methods for all types
223 current.otherCode << u"friend class QT_PREPEND_NAMESPACE(QQmltcObjectCreationHelper);"_s;
224
225 current.mocCode = {
226 u"Q_OBJECT"_s,
227 // Note: isAnonymous holds for non-root types in the document as well
228 type->isInlineComponent() ? (u"QML_NAMED_ELEMENT(%1)"_s.arg(a: *type->inlineComponentName()))
229 : (isAnonymous ? u"QML_ANONYMOUS"_s : u"QML_ELEMENT"_s),
230 };
231
232 // add special member functions
233 current.baselineCtor.access = QQmlJSMetaMethod::Protected;
234 if (documentRoot || inlineComponent || isSingleton) {
235 current.externalCtor.access = QQmlJSMetaMethod::Public;
236 } else {
237 current.externalCtor.access = QQmlJSMetaMethod::Protected;
238 }
239 current.init.access = QQmlJSMetaMethod::Protected;
240 current.beginClass.access = QQmlJSMetaMethod::Protected;
241 current.endInit.access = QQmlJSMetaMethod::Protected;
242 current.setComplexBindings.access = QQmlJSMetaMethod::Protected;
243 current.completeComponent.access = QQmlJSMetaMethod::Protected;
244 current.finalizeComponent.access = QQmlJSMetaMethod::Protected;
245 current.handleOnCompleted.access = QQmlJSMetaMethod::Protected;
246
247 current.baselineCtor.name = current.cppType;
248 current.externalCtor.name = current.cppType;
249 current.init.name = u"QML_init"_s;
250 current.init.returnType = u"QQmlRefPointer<QQmlContextData>"_s;
251 current.beginClass.name = u"QML_beginClass"_s;
252 current.beginClass.returnType = u"void"_s;
253 current.endInit.name = u"QML_endInit"_s;
254 current.endInit.returnType = u"void"_s;
255 current.setComplexBindings.name = u"QML_setComplexBindings"_s;
256 current.setComplexBindings.returnType = u"void"_s;
257 current.completeComponent.name = u"QML_completeComponent"_s;
258 current.completeComponent.returnType = u"void"_s;
259 current.finalizeComponent.name = u"QML_finalizeComponent"_s;
260 current.finalizeComponent.returnType = u"void"_s;
261 current.handleOnCompleted.name = u"QML_handleOnCompleted"_s;
262 current.handleOnCompleted.returnType = u"void"_s;
263 QmltcVariable creator(u"QQmltcObjectCreationHelper*"_s, u"creator"_s);
264 QmltcVariable engine(u"QQmlEngine*"_s, u"engine"_s);
265 QmltcVariable parent(u"QObject*"_s, u"parent"_s, u"nullptr"_s);
266 QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_s, u"parentContext"_s);
267 QmltcVariable finalizeFlag(u"bool"_s, u"canFinalize"_s);
268 current.baselineCtor.parameterList = { parent };
269 current.endInit.parameterList = { creator, engine };
270 current.setComplexBindings.parameterList = { creator, engine };
271 current.handleOnCompleted.parameterList = { creator };
272
273 if (documentRoot || inlineComponent) {
274 current.externalCtor.parameterList = { engine, parent };
275 current.init.parameterList = { creator, engine, ctxtdata, finalizeFlag };
276 current.beginClass.parameterList = { creator, finalizeFlag };
277 current.completeComponent.parameterList = { creator, finalizeFlag };
278 current.finalizeComponent.parameterList = { creator, finalizeFlag };
279 } else {
280 current.externalCtor.parameterList = { creator, engine, parent };
281 current.init.parameterList = { creator, engine, ctxtdata };
282 current.beginClass.parameterList = { creator };
283 current.completeComponent.parameterList = { creator };
284 current.finalizeComponent.parameterList = { creator };
285 }
286
287 current.externalCtor.initializerList = { current.baselineCtor.name + u"(" + parent.name
288 + u")" };
289 if (QQmlJSUtils::hasCompositeBase(scope: type)) {
290 // call parent's (QML type's) basic ctor from this. that one will take
291 // care about QObject::setParent()
292 current.baselineCtor.initializerList = { baseClass + u"(" + parent.name + u")" };
293 } else {
294 // default call to ctor is enough, but QQml_setParent_noEvent() is
295 // needed (note: faster? version of QObject::setParent())
296 current.baselineCtor.body << u"QQml_setParent_noEvent(this, " + parent.name + u");";
297 }
298
299 // compilation stub:
300 current.externalCtor.body << u"Q_UNUSED(engine)"_s;
301 if (documentRoot || inlineComponent) {
302 current.externalCtor.body << u"// document root:"_s;
303 // if it's document root, we want to create our QQmltcObjectCreationBase
304 // that would store all the created objects
305 current.externalCtor.body << u"QQmltcObjectCreationBase<%1> objectHolder;"_s.arg(
306 a: type->internalName());
307 current.externalCtor.body
308 << u"QQmltcObjectCreationHelper creator = objectHolder.view();"_s;
309 current.externalCtor.body << u"creator.set(0, this);"_s; // special case
310 // now call init
311 current.externalCtor.body << current.init.name
312 + u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* "
313 u"endInit */ true);";
314 } else {
315 current.externalCtor.body << u"// not document root:"_s;
316 // just call init, we don't do any setup here otherwise
317 current.externalCtor.body << current.init.name
318 + u"(creator, engine, QQmlData::get(parent)->outerContext);";
319 }
320
321 if (isSingleton) {
322 // see https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON for context
323 current.mocCode.append(t: u"QML_SINGLETON"_s);
324 auto &staticCreate = current.staticCreate.emplace();
325 staticCreate.comments
326 << u"Used by the engine for singleton creation."_s
327 << u"See also \\l {https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON}."_s;
328 staticCreate.type = QQmlJSMetaMethodType::StaticMethod;
329 staticCreate.access = QQmlJSMetaMethod::Public;
330 staticCreate.name = u"create"_s;
331 staticCreate.returnType = u"%1 *"_s.arg(a: current.cppType);
332 QmltcVariable jsEngine(u"QJSEngine*"_s, u"jsEngine"_s);
333 staticCreate.parameterList = { engine, jsEngine };
334 staticCreate.body << u"Q_UNUSED(jsEngine);"_s
335 << u"%1 *result = new %1(engine, nullptr);"_s.arg(a: current.cppType)
336 << u"return result;"_s;
337 }
338 auto postponedQmlContextSetup = generator.generate_initCode(current, type);
339 generator.generate_endInitCode(current, type);
340 generator.generate_setComplexBindingsCode(current, type);
341 generator.generate_beginClassCode(current, type);
342 generator.generate_completeComponentCode(current, type);
343 generator.generate_finalizeComponentCode(current, type);
344 generator.generate_handleOnCompletedCode(current, type);
345
346 compileElements(current, type);
347}
348
349template<typename Iterator>
350static Iterator partitionBindings(Iterator first, Iterator last)
351{
352 // NB: the code generator cares about script bindings being processed at a
353 // later point, so we should sort or partition the range. we do a stable
354 // partition since the relative order of binding evaluation affects the UI
355 return std::stable_partition(first, last, [](const QQmlJSMetaPropertyBinding &b) {
356 // we want complex bindings to be at the end, so do the negation
357 return !QmltcCompiler::isComplexBinding(binding: b);
358 });
359}
360
361void QmltcCompiler::compileTypeElements(QmltcType &current, const QQmlJSScope::ConstPtr &type)
362{
363 // compile components of a type:
364 // - enums
365 // - properties
366 // - methods
367 // - bindings
368
369 const auto enums = type->ownEnumerations();
370 current.enums.reserve(asize: enums.size());
371 for (auto it = enums.begin(); it != enums.end(); ++it)
372 compileEnum(current, e: it.value());
373
374 auto properties = type->ownProperties().values();
375 current.properties.reserve(asize: properties.size());
376 // Note: index() is the (future) meta property index, so make sure given
377 // properties are ordered by that index before compiling
378 std::sort(first: properties.begin(), last: properties.end(),
379 comp: [](const QQmlJSMetaProperty &x, const QQmlJSMetaProperty &y) {
380 return x.index() < y.index();
381 });
382 for (const QQmlJSMetaProperty &p : std::as_const(t&: properties)) {
383 if (p.index() == -1) {
384 recordError(location: type->sourceLocation(),
385 message: u"Internal error: property '%1' has incomplete information"_s.arg(
386 a: p.propertyName()));
387 continue;
388 }
389 if (p.isAlias()) {
390 compileAlias(current, alias: p, owner: type);
391 } else {
392 compileProperty(current, p, owner: type);
393 }
394 }
395
396 const auto methods = type->ownMethods();
397 for (const QQmlJSMetaMethod &m : methods)
398 compileMethod(current, m, owner: type);
399
400 auto bindings = type->ownPropertyBindingsInQmlIROrder();
401 partitionBindings(first: bindings.begin(), last: bindings.end());
402
403 compileBinding(current, bindingStart: bindings.begin(), bindingEnd: bindings.end(), type, accessor: { .scope: type });
404}
405
406void QmltcCompiler::compileEnum(QmltcType &current, const QQmlJSMetaEnum &e)
407{
408 const auto intValues = e.values();
409 QStringList values;
410 values.reserve(asize: intValues.size());
411 std::transform(first: intValues.cbegin(), last: intValues.cend(), result: std::back_inserter(x&: values),
412 unary_op: [](int x) { return QString::number(x); });
413
414 // structure: (C++ type name, enum keys, enum values, MOC line)
415 current.enums.emplaceBack(args: e.name(), args: e.keys(), args: std::move(values),
416 args: u"Q_ENUM(%1)"_s.arg(a: e.name()));
417}
418
419static QList<QmltcVariable>
420compileMethodParameters(const QList<QQmlJSMetaParameter> &parameterInfos, bool allowUnnamed = false)
421{
422 QList<QmltcVariable> parameters;
423 const auto size = parameterInfos.size();
424 parameters.reserve(asize: size);
425 for (qsizetype i = 0; i < size; ++i) {
426 const auto &p = parameterInfos[i];
427 Q_ASSERT(p.type()); // assume verified
428 QString name = p.name();
429 Q_ASSERT(allowUnnamed || !name.isEmpty()); // assume verified
430 if (name.isEmpty() && allowUnnamed)
431 name = u"unnamed_" + QString::number(i);
432
433 QString internalName;
434 const QQmlJSScope::AccessSemantics semantics = p.type()->accessSemantics();
435
436 switch (semantics) {
437 case QQmlJSScope::AccessSemantics::Reference:
438 if (p.typeQualifier() == QQmlJSMetaParameter::Const)
439 internalName = u"const "_s;
440 internalName += u"%1*"_s.arg(a: p.type()->internalName());
441 break;
442 case QQmlJSScope::AccessSemantics::Value:
443 case QQmlJSScope::AccessSemantics::Sequence:
444 internalName = u"passByConstRefOrValue<%1>"_s.arg(a: p.type()->internalName());
445 break;
446 case QQmlJSScope::AccessSemantics::None:
447 Q_ASSERT(false); // or maybe print an error message
448 }
449 parameters.emplaceBack(args&: internalName, args&: name, args: QString());
450 }
451 return parameters;
452}
453
454static QString figureReturnType(const QQmlJSMetaMethod &m)
455{
456 const bool isVoidMethod =
457 m.returnTypeName() == u"void" || m.methodType() == QQmlJSMetaMethodType::Signal;
458 Q_ASSERT(isVoidMethod || m.returnType());
459 QString type;
460 if (isVoidMethod) {
461 type = u"void"_s;
462 } else {
463 type = m.returnType()->augmentedInternalName();
464 }
465 return type;
466}
467
468void QmltcCompiler::compileMethod(QmltcType &current, const QQmlJSMetaMethod &m,
469 const QQmlJSScope::ConstPtr &owner)
470{
471 const auto returnType = figureReturnType(m);
472
473 const QList<QmltcVariable> compiledParams = compileMethodParameters(parameterInfos: m.parameters());
474 const auto methodType = m.methodType();
475
476 QStringList code;
477 if (methodType != QQmlJSMetaMethodType::Signal) {
478 QmltcCodeGenerator urlGenerator { .documentUrl: m_url, .visitor: m_visitor };
479 QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
480 block: &code, url: urlGenerator.urlMethodName() + u"()",
481 index: owner->ownRuntimeFunctionIndex(index: m.jsFunctionIndex()), accessor: u"this"_s, returnType,
482 parameters: compiledParams);
483 }
484
485 QmltcMethod compiled {};
486 compiled.returnType = returnType;
487 compiled.name = m.methodName();
488 compiled.parameterList = std::move(compiledParams);
489 compiled.body = std::move(code);
490 compiled.type = methodType;
491 compiled.access = m.access();
492 if (methodType != QQmlJSMetaMethodType::Signal) {
493 compiled.declarationPrefixes << u"Q_INVOKABLE"_s;
494 compiled.userVisible = m.access() == QQmlJSMetaMethod::Public;
495 } else {
496 compiled.userVisible = !m.isImplicitQmlPropertyChangeSignal();
497 }
498 current.functions.emplaceBack(args&: compiled);
499}
500
501/*! \internal
502 Compiles an extra set of methods for Lists, that makes manipulating lists easier from C++
503 for the user.
504*/
505void QmltcCompiler::compileExtraListMethods(QmltcType &current, const QQmlJSMetaProperty &p)
506{
507 QmltcPropertyData data(p);
508 const QString valueType = p.type()->valueType()->internalName() + u'*';
509 const QString variableName = data.read + u"()"_s;
510 const QStringList ownershipWarning = {
511 u"\\note {This method does not change the ownership of its argument."_s,
512 u"The caller is responsible for setting the argument's \\c {QObject::parent} or"_s,
513 u"for ensuring that the argument lives long enough."_s,
514 u"For example, an argument created with \\c {createObject()} that has no parent"_s,
515 u"will eventually be garbage-collected, leaving a dangling pointer.}"_s
516 };
517
518 // generate append() sugar for users
519 {
520 QmltcMethod append{};
521 append.comments.emplaceBack(args: u"\\brief Append an element to %1."_s.arg(a: data.read));
522 append.comments << ownershipWarning;
523 append.returnType = u"void"_s;
524 append.name = u"%1Append"_s.arg(a: data.read);
525 append.parameterList.emplaceBack(args: valueType, args: u"toBeAppended"_s);
526
527 append.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName);
528 append.body
529 << u"q_qmltc_localList.append(std::addressof(q_qmltc_localList), toBeAppended);"_s;
530 // append.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 is
531 // resolved
532 append.userVisible = true;
533 current.functions.emplaceBack(args&: append);
534 }
535
536 // generate count() sugar for users
537 {
538 QmltcMethod count{};
539 count.comments.emplaceBack(args: u"\\brief Number of elements in %1."_s.arg(a: data.read));
540 count.returnType = u"int"_s;
541 count.name = u"%1Count"_s.arg(a: data.read);
542
543 count.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName);
544 count.body << u"int result = q_qmltc_localList.count(std::addressof(q_qmltc_localList));"_s;
545 count.body << u"return result;"_s;
546 count.userVisible = true;
547 current.functions.emplaceBack(args&: count);
548 }
549
550 // generate at() sugar for users
551 {
552 QmltcMethod at{};
553 at.comments.emplaceBack(args: u"\\brief Access an element in %1."_s.arg(a: data.read));
554 at.returnType = valueType;
555 at.name = u"%1At"_s.arg(a: data.read);
556 at.parameterList.emplaceBack(args: u"qsizetype"_s, args: u"position"_s, args: QString());
557
558 at.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName);
559 at.body << u"auto result = q_qmltc_localList.at(std::addressof(q_qmltc_localList), position);"_s;
560 at.body << u"return result;"_s;
561 at.userVisible = true;
562 current.functions.emplaceBack(args&: at);
563 }
564
565 // generate clear() sugar for users
566 {
567 QmltcMethod clear{};
568 clear.comments.emplaceBack(args: u"\\brief Clear %1."_s.arg(a: data.read));
569 clear.returnType = u"void"_s;
570 clear.name = u"%1Clear"_s.arg(a: data.read);
571
572 clear.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName);
573 clear.body << u"q_qmltc_localList.clear(std::addressof(q_qmltc_localList));"_s;
574 // clear.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 is
575 // resolved
576 clear.userVisible = true;
577 current.functions.emplaceBack(args&: clear);
578 }
579
580 // generate replace() sugar for users
581 {
582 QmltcMethod replace{};
583 replace.comments.emplaceBack(args: u"\\brief Replace an element in %1."_s.arg(a: data.read));
584 replace.comments << ownershipWarning;
585 replace.returnType = u"void"_s;
586 replace.name = u"%1Replace"_s.arg(a: data.read);
587 replace.parameterList.emplaceBack(args: u"qsizetype"_s, args: u"position"_s, args: QString());
588 replace.parameterList.emplaceBack(args: valueType, args: u"element"_s,
589 args: QString());
590
591 replace.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName);
592 replace.body
593 << u"q_qmltc_localList.replace(std::addressof(q_qmltc_localList), position, element);"_s;
594 // replace.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587
595 // is resolved
596 replace.userVisible = true;
597 current.functions.emplaceBack(args&: replace);
598 }
599
600 // generate removeLast() sugar for users
601 {
602 QmltcMethod removeLast{};
603 removeLast.comments.emplaceBack(args: u"\\brief Remove the last element in %1."_s.arg(a: data.read));
604 removeLast.returnType = u"void"_s;
605 removeLast.name = u"%1RemoveLast"_s.arg(a: data.read);
606
607 removeLast.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName);
608 removeLast.body << u"q_qmltc_localList.removeLast(std::addressof(q_qmltc_localList));"_s;
609 // removeLast.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when
610 // QTBUG-106587 is resolved
611
612 removeLast.userVisible = true;
613 current.functions.emplaceBack(args&: removeLast);
614 }
615}
616
617void QmltcCompiler::compileProperty(QmltcType &current, const QQmlJSMetaProperty &p,
618 const QQmlJSScope::ConstPtr &owner)
619{
620 Q_ASSERT(!p.isAlias()); // will be handled separately
621 Q_ASSERT(p.type());
622
623 const QString name = p.propertyName();
624 const QString variableName = u"m_" + name;
625 const QString underlyingType = getUnderlyingType(p);
626 if (qIsReferenceTypeList(p)) {
627 const QString storageName = variableName + u"_storage";
628 current.variables.emplaceBack(
629 args: u"QList<" + p.type()->valueType()->internalName() + u"*>", args: storageName,
630 args: QString());
631 current.baselineCtor.initializerList.emplaceBack(args: variableName + u"(" + underlyingType
632 + u"(this, std::addressof(" + storageName
633 + u")))");
634 compileExtraListMethods(current, p);
635 }
636
637 // along with property, also add relevant moc code, so that we can use the
638 // property in Qt/QML contexts
639 QStringList mocPieces;
640 mocPieces.reserve(asize: 10);
641 mocPieces << underlyingType << name;
642
643 QmltcPropertyData compilationData(p);
644
645 // 1. add setter and getter
646 // If p.isList(), it's a QQmlListProperty. Then you can write the underlying list through
647 // the QQmlListProperty object retrieved with the getter. Setting it would make no sense.
648 if (p.isWritable() && !qIsReferenceTypeList(p)) {
649 QmltcMethod setter {};
650 setter.returnType = u"void"_s;
651 setter.name = compilationData.write;
652 // QmltcVariable
653 setter.parameterList.emplaceBack(args: QQmlJSUtils::constRefify(type: underlyingType), args: name + u"_",
654 args: u""_s);
655 setter.body << variableName + u".setValue(" + name + u"_);";
656 setter.body << u"Q_EMIT " + compilationData.notify + u"();";
657 setter.userVisible = true;
658 current.functions.emplaceBack(args&: setter);
659 mocPieces << u"WRITE"_s << setter.name;
660 }
661
662 QmltcMethod getter {};
663 getter.returnType = underlyingType;
664 getter.name = compilationData.read;
665 getter.body << u"return " + variableName + u".value();";
666 getter.userVisible = true;
667 current.functions.emplaceBack(args&: getter);
668 mocPieces << u"READ"_s << getter.name;
669
670 // 2. add bindable
671 if (!qIsReferenceTypeList(p)) {
672 QmltcMethod bindable {};
673 bindable.returnType = u"QBindable<" + underlyingType + u">";
674 bindable.name = compilationData.bindable;
675 bindable.body << u"return QBindable<" + underlyingType + u">(std::addressof(" + variableName
676 + u"));";
677 bindable.userVisible = true;
678 current.functions.emplaceBack(args&: bindable);
679 mocPieces << u"BINDABLE"_s << bindable.name;
680 }
681
682 // 3. add/check notify (actually, this is already done inside QmltcVisitor)
683
684 if (owner->isPropertyRequired(name))
685 mocPieces << u"REQUIRED"_s;
686
687 // 4. add moc entry
688 // e.g. Q_PROPERTY(QString p READ getP WRITE setP BINDABLE bindableP)
689 current.mocCode << u"Q_PROPERTY(" + mocPieces.join(sep: u" "_s) + u")";
690
691 // 5. add extra moc entry if this property is marked default
692 if (name == owner->defaultPropertyName())
693 current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(a: name);
694
695 // structure: (C++ type name, name, C++ class name, C++ signal name)
696 current.properties.emplaceBack(args: underlyingType, args: variableName, args&: current.cppType,
697 args&: compilationData.notify);
698}
699
700/*!
701 * \internal
702 *
703 * Models one step of the alias resolution. If the current alias to be resolved
704 * points to \c {x.y.z} and that \c {x.y} is already resolved, then this struct
705 * contains the information on how to obtain the \c {z} part from \c {x.y}.
706 */
707struct AliasResolutionFrame
708{
709 /*!
710 * \internal
711 *
712 * Placeholder for the current resolved state. It is replaced later with
713 * the result from previous resolutions from the \c QStack<AliasResolutionFrame>.
714 *
715 * \sa unpackFrames()
716 */
717 static QString inVar;
718
719 /*!
720 * \internal
721 *
722 * Steps to access this value as a list of C++ statements, to be used in
723 * conjunction with \c {epilogue}.
724 */
725 QStringList prologue;
726
727 /*!
728 * \internal
729 *
730 * Steps to finish the statements of the \c prologue (e.g. closing brackets).
731 */
732 QStringList epilogue;
733
734 /*!
735 * \internal
736 *
737 * Instructions on how to write the property, after it was loaded with the
738 * instructions from \c prologue. Has to happen before \c epilogue.
739 */
740 QStringList epilogueForWrite;
741
742 /*!
743 * \internal
744 *
745 * Name of the variable holding the result of this resolution step, to be
746 * used in the following resolution steps.
747 */
748 QString outVar;
749};
750// special string replaced by outVar of the previous frame
751QString AliasResolutionFrame::inVar = QStringLiteral("__QMLTC_ALIAS_FRAME_INPUT_VAR__");
752
753/*!
754 * \internal
755 *
756 * Process the frames by replacing the placeholder \c invar
757 * used in \c epilogueForWrite and \c prologue with the result
758 * obtained from the previous frame.
759 */
760static void unpackFrames(QStack<AliasResolutionFrame> &frames)
761{
762 if (frames.size() < 2)
763 return;
764
765 // assume first frame is fine
766 auto prev = frames.begin();
767 for (auto it = std::next(x: prev); it != frames.end(); ++it, ++prev) {
768 for (QString &line : it->prologue)
769 line.replace(before: AliasResolutionFrame::inVar, after: prev->outVar);
770 for (QString &line : it->epilogueForWrite)
771 line.replace(before: AliasResolutionFrame::inVar, after: prev->outVar);
772 it->outVar.replace(before: AliasResolutionFrame::inVar, after: prev->outVar);
773 }
774}
775
776template<typename Projection>
777static QStringList joinFrames(const QStack<AliasResolutionFrame> &frames, Projection project)
778{
779 QStringList joined;
780 for (const AliasResolutionFrame &frame : frames)
781 joined += project(frame);
782 return joined;
783}
784
785void QmltcCompiler::compileAlias(QmltcType &current, const QQmlJSMetaProperty &alias,
786 const QQmlJSScope::ConstPtr &owner)
787{
788 const QString aliasName = alias.propertyName();
789 Q_ASSERT(!aliasName.isEmpty());
790
791 QStringList aliasExprBits = alias.aliasExpression().split(sep: u'.');
792 Q_ASSERT(!aliasExprBits.isEmpty());
793
794 QStack<AliasResolutionFrame> frames;
795
796 QQmlJSUtils::AliasResolutionVisitor aliasVisitor;
797 qsizetype i = 0;
798 aliasVisitor.reset = [&]() {
799 frames.clear();
800 i = 0; // we use it in property processing
801
802 // first frame is a dummy one:
803 frames.push(
804 t: AliasResolutionFrame { .prologue: QStringList(), .epilogue: QStringList(), .epilogueForWrite: QStringList(), .outVar: u"this"_s });
805 };
806 aliasVisitor.processResolvedId = [&](const QQmlJSScope::ConstPtr &type) {
807 Q_ASSERT(type);
808 if (owner != type) { // cannot start at `this`, need to fetch object through context
809 const int id = m_visitor->runtimeId(type);
810 Q_ASSERT(id >= 0); // since the type is found by id, it must have an id
811
812 AliasResolutionFrame queryIdFrame {};
813 Q_ASSERT(frames.top().outVar == u"this"_s); // so inVar would be "this" as well
814 queryIdFrame.prologue << u"auto context = %1::q_qmltc_thisContext;"_s.arg(
815 a: owner->internalName());
816
817 // doing the above allows us to lookup id object by index (fast)
818 queryIdFrame.outVar = u"alias_objectById_" + aliasExprBits.front(); // unique enough
819 const QString cppType = (m_visitor->qmlComponentIndex(type) == -1)
820 ? type->internalName()
821 : u"QQmlComponent"_s;
822 queryIdFrame.prologue << u"auto " + queryIdFrame.outVar + u" = static_cast<" + cppType
823 + u"*>(context->idValue(" + QString::number(id) + u"));";
824 queryIdFrame.prologue << u"Q_ASSERT(" + queryIdFrame.outVar + u");";
825
826 frames.push(t: queryIdFrame);
827 }
828 };
829 aliasVisitor.processResolvedProperty = [&](const QQmlJSMetaProperty &p,
830 const QQmlJSScope::ConstPtr &owner) {
831 AliasResolutionFrame queryPropertyFrame {};
832
833 auto [extensionPrologue, extensionAccessor, extensionEpilogue] =
834 QmltcCodeGenerator::wrap_extensionType(
835 type: owner, p,
836 accessor: QmltcCodeGenerator::wrap_privateClass(accessor: AliasResolutionFrame::inVar, p));
837 QString inVar = extensionAccessor;
838 queryPropertyFrame.prologue += extensionPrologue;
839 if (p.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Value) {
840 // we need to read the property to a local variable and then
841 // write the updated value once the actual operation is done
842 const QString aliasVar = u"alias_" + QString::number(i); // should be fairly unique
843 ++i;
844 queryPropertyFrame.prologue
845 << u"auto " + aliasVar + u" = " + inVar + u"->" + p.read() + u"();";
846 queryPropertyFrame.epilogueForWrite
847 << inVar + u"->" + p.write() + u"(" + aliasVar + u");";
848 // NB: since accessor becomes a value type, wrap it into an
849 // addressof operator so that we could access it as a pointer
850 inVar = QmltcCodeGenerator::wrap_addressof(addressed: aliasVar); // reset
851 } else {
852 inVar += u"->" + p.read() + u"()"; // update
853 }
854 queryPropertyFrame.outVar = inVar;
855 queryPropertyFrame.epilogue += extensionEpilogue;
856
857 frames.push(t: queryPropertyFrame);
858 };
859
860 QQmlJSUtils::ResolvedAlias result =
861 QQmlJSUtils::resolveAlias(typeResolver: m_typeResolver, property: alias, owner, visitor: aliasVisitor);
862 Q_ASSERT(result.kind != QQmlJSUtils::AliasTarget_Invalid);
863
864 unpackFrames(frames);
865
866 if (result.kind == QQmlJSUtils::AliasTarget_Property) {
867 // we don't need the last frame here
868 frames.pop();
869
870 // instead, add a custom frame
871 AliasResolutionFrame customFinalFrame {};
872 auto [extensionPrologue, extensionAccessor, extensionEpilogue] =
873 QmltcCodeGenerator::wrap_extensionType(
874 type: result.owner, p: result.property,
875 accessor: QmltcCodeGenerator::wrap_privateClass(accessor: frames.top().outVar,
876 p: result.property));
877 customFinalFrame.prologue = extensionPrologue;
878 customFinalFrame.outVar = extensionAccessor;
879 customFinalFrame.epilogue = extensionEpilogue;
880 frames.push(t: customFinalFrame);
881 }
882
883 const QString latestAccessor = frames.top().outVar;
884 const QStringList prologue =
885 joinFrames(frames, project: [](const AliasResolutionFrame &frame) { return frame.prologue; });
886 const QStringList epilogue =
887 joinFrames(frames, project: [](const AliasResolutionFrame &frame) { return frame.epilogue; });
888 const QString underlyingType = (result.kind == QQmlJSUtils::AliasTarget_Property)
889 ? getUnderlyingType(p: result.property)
890 : result.owner->internalName() + u" *";
891
892 QStringList mocLines;
893 mocLines.reserve(asize: 10);
894 mocLines << underlyingType << aliasName;
895
896 QmltcPropertyData compilationData(aliasName);
897 // 1. add setter and getter
898 QmltcMethod getter {};
899 getter.returnType = underlyingType;
900 getter.name = compilationData.read;
901 getter.body += prologue;
902 if (result.kind == QQmlJSUtils::AliasTarget_Property) {
903 if (QString read = result.property.read(); !read.isEmpty()
904 && !QQmlJSUtils::bindablePropertyHasDefaultAccessor(
905 p: result.property, accessor: QQmlJSUtils::PropertyAccessor_Read)) {
906 getter.body << u"return %1->%2();"_s.arg(args: latestAccessor, args&: read);
907 } else { // use QObject::property() as a fallback when read method is unknown
908 getter.body << u"return qvariant_cast<%1>(%2->property(\"%3\"));"_s.arg(
909 args: underlyingType, args: latestAccessor, args: result.property.propertyName());
910 }
911 } else { // AliasTarget_Object
912 getter.body << u"return " + latestAccessor + u";";
913 }
914 getter.body += epilogue;
915 getter.userVisible = true;
916 current.functions.emplaceBack(args&: getter);
917 mocLines << u"READ"_s << getter.name;
918
919 if (result.property.isWritable()) {
920 Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
921 QmltcMethod setter {};
922 setter.returnType = u"void"_s;
923 setter.name = compilationData.write;
924
925 const QString setName = result.property.write();
926 QList<QQmlJSMetaMethod> methods = result.owner->methods(name: setName);
927 if (methods.isEmpty()) { // when we are compiling the property as well
928 // QmltcVariable
929 setter.parameterList.emplaceBack(args: QQmlJSUtils::constRefify(type: underlyingType),
930 args: aliasName + u"_", args: u""_s);
931 } else {
932 setter.parameterList = compileMethodParameters(parameterInfos: methods.at(i: 0).parameters(),
933 /* allow unnamed = */ allowUnnamed: true);
934 }
935
936 setter.body += prologue;
937 QStringList parameterNames;
938 parameterNames.reserve(asize: setter.parameterList.size());
939 std::transform(first: setter.parameterList.cbegin(), last: setter.parameterList.cend(),
940 result: std::back_inserter(x&: parameterNames),
941 unary_op: [](const QmltcVariable &x) { return x.name; });
942 QString commaSeparatedParameterNames = parameterNames.join(sep: u", "_s);
943 if (!setName.isEmpty()
944 && !QQmlJSUtils::bindablePropertyHasDefaultAccessor(
945 p: result.property, accessor: QQmlJSUtils::PropertyAccessor_Write)) {
946 setter.body << u"%1->%2(%3);"_s.arg(args: latestAccessor, args: setName,
947 args&: commaSeparatedParameterNames);
948 } else { // use QObject::setProperty() as fallback when write method is unknown
949 Q_ASSERT(parameterNames.size() == 1);
950 const QString variantName = u"var_" + aliasName; // fairly unique
951 setter.body << u"QVariant %1;"_s.arg(a: variantName);
952 setter.body << u"%1.setValue(%2);"_s.arg(args: variantName, args&: commaSeparatedParameterNames);
953 setter.body << u"%1->setProperty(\"%2\", std::move(%3));"_s.arg(
954 args: latestAccessor, args: result.property.propertyName(), args: variantName);
955 }
956 setter.body += joinFrames(
957 frames, project: [](const AliasResolutionFrame &frame) { return frame.epilogueForWrite; });
958 setter.body += epilogue; // NB: *after* epilogueForWrite - see prologue construction
959 setter.userVisible = true;
960 current.functions.emplaceBack(args&: setter);
961 mocLines << u"WRITE"_s << setter.name;
962 }
963 // 2. add bindable
964 if (QString bindableName = result.property.bindable(); !bindableName.isEmpty()) {
965 Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
966 QmltcMethod bindable {};
967 bindable.returnType = u"QBindable<" + underlyingType + u">";
968 bindable.name = compilationData.bindable;
969 bindable.body += prologue;
970 bindable.body << u"return " + latestAccessor + u"->" + bindableName + u"()" + u";";
971 bindable.body += epilogue;
972 bindable.userVisible = true;
973 current.functions.emplaceBack(args&: bindable);
974 mocLines << u"BINDABLE"_s << bindable.name;
975 }
976
977 // 3. add notify - which is pretty special
978 // step 1: generate the moc instructions
979 // mimic the engines behavior: do it even if the notify will never be emitted
980 if (const QString aliasNotifyName = alias.notify(); !aliasNotifyName.isEmpty()) {
981
982 Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
983
984 mocLines << u"NOTIFY"_s << aliasNotifyName;
985 }
986
987 // step 2: connect the notifier to the aliased property notifier, if this latter exists
988 // otherwise, mimic the engines behavior and generate a useless notify
989 if (const QString notifyName = result.property.notify(); !notifyName.isEmpty()) {
990 auto notifyFrames = frames;
991 notifyFrames.pop(); // we don't need the last frame at all in this case
992
993 const QStringList notifyPrologue = joinFrames(
994 frames, project: [](const AliasResolutionFrame &frame) { return frame.prologue; });
995 const QStringList notifyEpilogue = joinFrames(
996 frames, project: [](const AliasResolutionFrame &frame) { return frame.epilogue; });
997
998 // notify is very special
999 current.endInit.body << u"{ // alias notify connection:"_s;
1000 current.endInit.body += notifyPrologue;
1001 // TODO: use non-private accessor since signals must exist on the public
1002 // type, not on the private one -- otherwise, you can't connect to a
1003 // private property signal in C++ and so it is useless (hence, use
1004 // public type)
1005 const QString cppType = (m_visitor->qmlComponentIndex(type: result.owner) == -1)
1006 ? result.owner->internalName()
1007 : u"QQmlComponent"_s;
1008 const QString latestAccessorNonPrivate = notifyFrames.top().outVar;
1009 current.endInit.body << u"QObject::connect(" + latestAccessorNonPrivate + u", &" + cppType
1010 + u"::" + notifyName + u", this, &" + current.cppType + u"::"
1011 + compilationData.notify + u");";
1012 current.endInit.body += notifyEpilogue;
1013 current.endInit.body << u"}"_s;
1014 }
1015
1016 if (QString resetName = result.property.reset(); !resetName.isEmpty()) {
1017 Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
1018 QmltcMethod reset {};
1019 reset.returnType = u"void"_s;
1020 reset.name = compilationData.reset;
1021 reset.body += prologue;
1022 reset.body << latestAccessor + u"->" + resetName + u"()" + u";";
1023 reset.body += epilogue;
1024 reset.userVisible = true;
1025 current.functions.emplaceBack(args&: reset);
1026 mocLines << u"RESET"_s << reset.name;
1027 }
1028
1029 // mimic the engines behavior: aliases are never constants
1030 // mocLines << u"CONSTANT"_s;
1031 // mimic the engines behavior: aliases are never stored
1032 mocLines << u"STORED"_s << u"false"_s;
1033 // mimic the engines behavior: aliases are never designable
1034 mocLines << u"DESIGNABLE"_s << u"false"_s;
1035
1036 // 4. add moc entry
1037 // Q_PROPERTY(QString text READ text WRITE setText BINDABLE bindableText NOTIFY textChanged)
1038 current.mocCode << u"Q_PROPERTY(" + mocLines.join(sep: u" "_s) + u")";
1039
1040 // 5. add extra moc entry if this alias is default one
1041 if (aliasName == owner->defaultPropertyName()) {
1042 // Q_CLASSINFO("DefaultProperty", propertyName)
1043 current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(a: aliasName);
1044 }
1045}
1046
1047static QString generate_callCompilationUnit(const QString &urlMethodName)
1048{
1049 return u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(%1())"_s.arg(a: urlMethodName);
1050}
1051
1052static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope,
1053 const QString &propertyName);
1054
1055/*!
1056 * \internal
1057 * Helper method used to keep compileBindingByType() readable.
1058 */
1059void QmltcCompiler::compileObjectBinding(QmltcType &current,
1060 const QQmlJSMetaPropertyBinding &binding,
1061 const QQmlJSScope::ConstPtr &type,
1062 const BindingAccessorData &accessor)
1063{
1064 Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Object);
1065
1066 const QString &propertyName = binding.propertyName();
1067 const QQmlJSMetaProperty property = type->property(name: propertyName);
1068 QQmlJSScope::ConstPtr propertyType = property.type();
1069
1070 // NB: object is compiled with compileType(), here just need to use it
1071 auto object = binding.objectType();
1072
1073 // Note: despite a binding being set for `accessor`, we use "this" as a
1074 // parent of a created object. Both attached and grouped properties are
1075 // parented by "this", so lifetime-wise we should be fine
1076 const QString qobjectParent = u"this"_s;
1077
1078 if (!propertyType) {
1079 recordError(location: binding.sourceLocation(),
1080 message: u"Binding on property '" + propertyName + u"' of unknown type");
1081 return;
1082 }
1083
1084 const auto addObjectBinding = [&](const QString &value) {
1085 if (qIsReferenceTypeList(p: property)) {
1086 Q_ASSERT(unprocessedListProperty == property || unprocessedListBindings.empty());
1087 unprocessedListBindings.append(t: value);
1088 unprocessedListProperty = property;
1089 } else {
1090 QmltcCodeGenerator::generate_assignToProperty(block: &current.endInit.body, type, p: property,
1091 value, accessor: accessor.name, constructFromQObject: true);
1092 }
1093 };
1094
1095 // special case of implicit or explicit component:
1096 if (qsizetype index = m_visitor->qmlComponentIndex(type: object); index >= 0) {
1097 const QString objectName = newSymbol(base: u"sc"_s);
1098
1099 const qsizetype creationIndex = m_visitor->creationIndex(type: object);
1100
1101 QStringList *block = (creationIndex == -1) ? &current.endInit.body : &current.init.body;
1102 *block << u"{"_s;
1103 *block << QStringLiteral("auto thisContext = QQmlData::get(%1)->outerContext;")
1104 .arg(a: qobjectParent);
1105 *block << QStringLiteral("auto %1 = QQmlObjectCreator::createComponent(engine, "
1106 "%2, %3, %4, thisContext);")
1107 .arg(args: objectName, args: generate_callCompilationUnit(urlMethodName: m_urlMethodName),
1108 args: QString::number(index), args: qobjectParent);
1109 *block << QStringLiteral("thisContext->installContext(QQmlData::get(%1), "
1110 "QQmlContextData::OrdinaryObject);")
1111 .arg(a: objectName);
1112
1113 // objects wrapped in implicit components do not have visible ids,
1114 // however, explicit components can have an id and that one is going
1115 // to be visible in the common document context
1116 if (creationIndex != -1) {
1117 // explicit component
1118 Q_ASSERT(object->isComposite());
1119 Q_ASSERT(object->baseType()->internalName() == u"QQmlComponent"_s);
1120
1121 if (int id = m_visitor->runtimeId(type: object); id >= 0) {
1122 QString idString = m_visitor->addressableScopes().id(scope: object, referrer: object);
1123 if (idString.isEmpty())
1124 idString = u"<unknown>"_s;
1125 QmltcCodeGenerator::generate_setIdValue(block, context: u"thisContext"_s, index: id, accessor: objectName,
1126 idString);
1127 }
1128
1129 const QString creationIndexStr = QString::number(creationIndex);
1130 *block << QStringLiteral("creator->set(%1, %2);").arg(args: creationIndexStr, args: objectName);
1131 Q_ASSERT(block == &current.init.body);
1132 current.endInit.body << QStringLiteral("auto %1 = creator->get<%2>(%3);")
1133 .arg(args: objectName, args: u"QQmlComponent"_s, args: creationIndexStr);
1134 }
1135 addObjectBinding(objectName);
1136 *block << u"}"_s;
1137 return;
1138 }
1139
1140 const QString objectName = newSymbol(base: u"o"_s);
1141 current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg(
1142 args: objectName, args: object->internalName(), args: qobjectParent);
1143 current.init.body << u"creator->set(%1, %2);"_s.arg(
1144 args: QString::number(m_visitor->creationIndex(type: object)), args: objectName);
1145
1146 // refetch the same object during endInit to set the bindings
1147 current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg(
1148 args: objectName, args: object->internalName(), args: QString::number(m_visitor->creationIndex(type: object)));
1149 addObjectBinding(objectName);
1150}
1151
1152/*!
1153 * \internal
1154 * Helper method used to keep compileBindingByType() readable.
1155 */
1156void QmltcCompiler::compileValueSourceOrInterceptorBinding(QmltcType &current,
1157 const QQmlJSMetaPropertyBinding &binding,
1158 const QQmlJSScope::ConstPtr &type,
1159 const BindingAccessorData &accessor)
1160{
1161 Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::ValueSource
1162 || binding.bindingType() == QQmlSA::BindingType::Interceptor);
1163
1164 const QString &propertyName = binding.propertyName();
1165 const QQmlJSMetaProperty property = type->property(name: propertyName);
1166 QQmlJSScope::ConstPtr propertyType = property.type();
1167
1168 // NB: object is compiled with compileType(), here just need to use it
1169 QSharedPointer<const QQmlJSScope> object;
1170 if (binding.bindingType() == QQmlSA::BindingType::Interceptor)
1171 object = binding.interceptorType();
1172 else
1173 object = binding.valueSourceType();
1174
1175 // Note: despite a binding being set for `accessor`, we use "this" as a
1176 // parent of a created object. Both attached and grouped properties are
1177 // parented by "this", so lifetime-wise we should be fine
1178 const QString qobjectParent = u"this"_s;
1179
1180 if (!propertyType) {
1181 recordError(location: binding.sourceLocation(),
1182 message: u"Binding on property '" + propertyName + u"' of unknown type");
1183 return;
1184 }
1185
1186 auto &objectName = m_uniques[UniqueStringId(current, propertyName)].onAssignmentObjectName;
1187 if (objectName.isEmpty()) {
1188 objectName = u"onAssign_" + propertyName;
1189
1190 current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg(
1191 args&: objectName, args: object->internalName(), args: qobjectParent);
1192 current.init.body << u"creator->set(%1, %2);"_s.arg(
1193 args: QString::number(m_visitor->creationIndex(type: object)), args&: objectName);
1194
1195 current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg(
1196 args&: objectName, args: object->internalName(),
1197 args: QString::number(m_visitor->creationIndex(type: object)));
1198 }
1199
1200 // NB: we expect one "on" assignment per property, so creating
1201 // QQmlProperty each time should be fine (unlike QQmlListReference)
1202 current.endInit.body << u"{"_s;
1203 current.endInit.body << u"QQmlProperty qmlprop(%1, %2);"_s.arg(
1204 args: accessor.name, args: QQmlJSUtils::toLiteral(s: propertyName));
1205 current.endInit.body << u"QT_PREPEND_NAMESPACE(QQmlCppOnAssignmentHelper)::set(%1, qmlprop);"_s
1206 .arg(a: objectName);
1207 current.endInit.body << u"}"_s;
1208}
1209
1210/*!
1211 * \internal
1212 * Helper method used to keep compileBindingByType() readable.
1213 */
1214void QmltcCompiler::compileAttachedPropertyBinding(QmltcType &current,
1215 const QQmlJSMetaPropertyBinding &binding,
1216 const QQmlJSScope::ConstPtr &type,
1217 const BindingAccessorData &accessor)
1218{
1219 Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::AttachedProperty);
1220
1221 const QString &propertyName = binding.propertyName();
1222 const QQmlJSMetaProperty property = type->property(name: propertyName);
1223 QQmlJSScope::ConstPtr propertyType = property.type();
1224
1225 Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact
1226 const auto attachedType = binding.attachingType();
1227 Q_ASSERT(attachedType);
1228
1229 const QString attachingTypeName = propertyName; // acts as an identifier
1230 auto attachingType = m_typeResolver->typeForName(name: attachingTypeName);
1231
1232 QString attachedTypeName = attachedType->baseTypeName();
1233 Q_ASSERT(!attachedTypeName.isEmpty());
1234
1235 auto &attachedMemberName =
1236 m_uniques[UniqueStringId(current, propertyName)].attachedVariableName;
1237 if (attachedMemberName.isEmpty()) {
1238 attachedMemberName = u"m_" + attachingTypeName;
1239 // add attached type as a member variable to allow noop lookup
1240 current.variables.emplaceBack(args: attachedTypeName + u" *", args&: attachedMemberName, args: u"nullptr"_s);
1241
1242 if (propertyName == u"Component"_s) { // Component attached type is special
1243 current.endInit.body << u"Q_ASSERT(qmlEngine(this));"_s;
1244 current.endInit.body
1245 << u"// attached Component must be added to the object's QQmlData"_s;
1246 current.endInit.body
1247 << u"Q_ASSERT(!QQmlEnginePrivate::get(qmlEngine(this))->activeObjectCreator);"_s;
1248 }
1249
1250 // Note: getting attached property is fairly expensive
1251 const QString getAttachedPropertyLine = u"qobject_cast<" + attachedTypeName
1252 + u" *>(qmlAttachedPropertiesObject<" + attachingType->internalName()
1253 + u">(this, /* create = */ true))";
1254 current.endInit.body << attachedMemberName + u" = " + getAttachedPropertyLine + u";";
1255
1256 if (propertyName == u"Component"_s) {
1257 // call completed/destruction signals appropriately
1258 current.handleOnCompleted.body << u"Q_EMIT " + attachedMemberName + u"->completed();";
1259 if (!current.dtor) {
1260 current.dtor = QmltcDtor{};
1261 current.dtor->name = u"~" + current.cppType;
1262 }
1263 current.dtor->body << u"Q_EMIT " + attachedMemberName + u"->destruction();";
1264 }
1265 }
1266
1267 auto subbindings = attachedType->ownPropertyBindingsInQmlIROrder();
1268 // compile bindings of the attached property
1269 partitionBindings(first: subbindings.begin(), last: subbindings.end());
1270 compileBinding(current, bindingStart: subbindings.begin(), bindingEnd: subbindings.end(), type: attachedType,
1271 accessor: { .scope: type, .name: attachedMemberName, .propertyName: propertyName, .isValueType: false });
1272}
1273
1274/*!
1275 * \internal
1276 * Helper method used to keep compileBindingByType() readable.
1277 */
1278void QmltcCompiler::compileGroupPropertyBinding(QmltcType &current,
1279 const QQmlJSMetaPropertyBinding &binding,
1280 const QQmlJSScope::ConstPtr &type,
1281 const BindingAccessorData &accessor)
1282{
1283 Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::GroupProperty);
1284
1285 const QString &propertyName = binding.propertyName();
1286 const QQmlJSMetaProperty property = type->property(name: propertyName);
1287 QQmlJSScope::ConstPtr propertyType = property.type();
1288
1289 Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact
1290 if (property.read().isEmpty()) {
1291 recordError(location: binding.sourceLocation(),
1292 message: u"READ function of group property '" + propertyName + u"' is unknown");
1293 return;
1294 }
1295
1296 auto groupType = binding.groupType();
1297 Q_ASSERT(groupType);
1298
1299 const bool isValueType = propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Value;
1300 if (!isValueType
1301 && propertyType->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
1302 recordError(location: binding.sourceLocation(),
1303 message: u"Group property '" + propertyName + u"' has unsupported access semantics");
1304 return;
1305 }
1306
1307 auto subbindings = groupType->ownPropertyBindingsInQmlIROrder();
1308 auto firstScript = partitionBindings(first: subbindings.begin(), last: subbindings.end());
1309
1310 // if we have no non-script bindings, we have no bindings that affect
1311 // the value type group, so no reason to generate the wrapping code
1312 const bool generateValueTypeCode = isValueType && (subbindings.begin() != firstScript);
1313
1314 QString groupAccessor = QmltcCodeGenerator::wrap_privateClass(accessor: accessor.name, p: property) + u"->"
1315 + property.read() + u"()";
1316 // NB: used when isValueType == true
1317 const QString groupPropertyVarName = accessor.name + u"_group_" + propertyName;
1318 // value types are special
1319 if (generateValueTypeCode) {
1320 if (property.write().isEmpty()) { // just reject this
1321 recordError(location: binding.sourceLocation(),
1322 message: u"Group property '" + propertyName + u"' is a value type without a setter");
1323 return;
1324 }
1325
1326 current.endInit.body << u"auto " + groupPropertyVarName + u" = " + groupAccessor + u";";
1327 // addressof operator is to make the binding logic work, which
1328 // expects that `accessor.name` is a pointer type
1329 groupAccessor = QmltcCodeGenerator::wrap_addressof(addressed: groupPropertyVarName);
1330 }
1331
1332 // compile bindings of the grouped property
1333 const auto compile = [&](const auto &bStart, const auto &bEnd) {
1334 compileBinding(current, bindingStart: bStart, bindingEnd: bEnd, type: groupType,
1335 accessor: { type, groupAccessor, propertyName, isValueType });
1336 };
1337
1338 auto it = subbindings.begin();
1339 Q_ASSERT(std::all_of(it, firstScript, [](const auto &x) {
1340 return x.bindingType() != QQmlSA::BindingType::Script;
1341 }));
1342 compile(it, firstScript);
1343 it = firstScript;
1344
1345 // NB: script bindings are special on group properties. if our group is
1346 // a value type, the binding would be installed on the *object* that
1347 // holds the value type and not on the value type itself. this may cause
1348 // subtle side issues (esp. when script binding is actually a simple
1349 // enum value assignment - which is not recognized specially):
1350 //
1351 // auto valueTypeGroupProperty = getCopy();
1352 // installBinding(valueTypeGroupProperty, "subproperty1"); // changes subproperty1 value
1353 // setCopy(valueTypeGroupProperty); // oops, subproperty1 value changed to old again
1354 if (generateValueTypeCode) { // write the value type back
1355 current.endInit.body << QmltcCodeGenerator::wrap_privateClass(accessor: accessor.name, p: property)
1356 + u"->" + property.write() + u"(" + groupPropertyVarName + u");";
1357 }
1358
1359 // once the value is written back, process the script bindings
1360 Q_ASSERT(std::all_of(it, subbindings.end(), [](const auto &x) {
1361 return x.bindingType() == QQmlSA::BindingType::Script;
1362 }));
1363 compile(it, subbindings.end());
1364}
1365
1366/*!
1367 * \internal
1368 * Helper method used to keep compileBindingByType() readable.
1369 */
1370void QmltcCompiler::compileTranslationBinding(QmltcType &current,
1371 const QQmlJSMetaPropertyBinding &binding,
1372 const QQmlJSScope::ConstPtr &type,
1373 const BindingAccessorData &accessor)
1374{
1375 Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Translation
1376 || binding.bindingType() == QQmlSA::BindingType::TranslationById);
1377
1378 const QString &propertyName = binding.propertyName();
1379
1380 auto [property, absoluteIndex] = getMetaPropertyIndex(scope: type, propertyName);
1381
1382 if (absoluteIndex < 0) {
1383 recordError(location: binding.sourceLocation(),
1384 message: u"Binding on unknown property '" + propertyName + u"'");
1385 return;
1386 }
1387
1388 QString bindingTarget = accessor.name;
1389
1390 int valueTypeIndex = -1;
1391 if (accessor.isValueType) {
1392 Q_ASSERT(accessor.scope != type);
1393 bindingTarget = u"this"_s; // TODO: not necessarily "this"?
1394 auto [groupProperty, groupPropertyIndex] =
1395 getMetaPropertyIndex(scope: accessor.scope, propertyName: accessor.propertyName);
1396 if (groupPropertyIndex < 0) {
1397 recordError(location: binding.sourceLocation(),
1398 message: u"Binding on group property '" + accessor.propertyName
1399 + u"' of unknown type");
1400 return;
1401 }
1402 valueTypeIndex = absoluteIndex;
1403 absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name
1404 }
1405
1406 QmltcCodeGenerator::TranslationBindingInfo info;
1407 info.unitVarName = generate_callCompilationUnit(urlMethodName: m_urlMethodName);
1408 info.scope = u"this"_s;
1409 info.target = u"this"_s;
1410 info.propertyIndex = absoluteIndex;
1411 info.property = property;
1412 info.data = binding.translationDataValue(qmlFileNameForContext: m_url);
1413 info.valueTypeIndex = valueTypeIndex;
1414 info.line = binding.sourceLocation().startLine;
1415 info.column = binding.sourceLocation().startColumn;
1416
1417 QmltcCodeGenerator::generate_createTranslationBindingOnProperty(block: &current.endInit.body, info);
1418}
1419
1420void QmltcCompiler::processLastListBindings(QmltcType &current, const QQmlJSScope::ConstPtr &type,
1421 const BindingAccessorData &accessor)
1422{
1423 if (unprocessedListBindings.empty())
1424 return;
1425
1426 QmltcCodeGenerator::generate_assignToListProperty(
1427 block: &current.endInit.body, type, p: unprocessedListProperty, value: unprocessedListBindings,
1428 accessor: accessor.name,
1429 qmlListVarName&: m_uniques[UniqueStringId(current, unprocessedListProperty.propertyName())]
1430 .qmlListVariableName);
1431
1432 unprocessedListBindings.clear();
1433}
1434
1435void QmltcCompiler::compileBinding(QmltcType &current,
1436 QList<QQmlJSMetaPropertyBinding>::iterator bindingStart,
1437 QList<QQmlJSMetaPropertyBinding>::iterator bindingEnd,
1438 const QQmlJSScope::ConstPtr &type,
1439 const BindingAccessorData &accessor)
1440{
1441 for (auto it = bindingStart; it != bindingEnd; it++) {
1442 const QQmlJSMetaPropertyBinding &binding = *it;
1443 const QString &propertyName = binding.propertyName();
1444 Q_ASSERT(!propertyName.isEmpty());
1445
1446 // Note: unlike QQmlObjectCreator, we don't have to do a complicated
1447 // deferral logic for bindings: if a binding is deferred, it is not compiled
1448 // (potentially, with all the bindings inside of it), period.
1449 if (type->isNameDeferred(name: propertyName)) {
1450 const auto location = binding.sourceLocation();
1451 // make sure group property is not generalized by checking if type really has a property
1452 // called propertyName. If not, it is probably an id.
1453 if (binding.bindingType() == QQmlSA::BindingType::GroupProperty
1454 && type->hasProperty(name: propertyName)) {
1455 qCWarning(lcQmltcCompiler)
1456 << QStringLiteral("Binding at line %1 column %2 is not deferred as it is a "
1457 "binding on a group property.")
1458 .arg(args: QString::number(location.startLine),
1459 args: QString::number(location.startColumn));
1460 // we do not support PropertyChanges and other types with similar
1461 // behavior yet, so this binding is compiled
1462 } else {
1463 qCDebug(lcQmltcCompiler)
1464 << QStringLiteral(
1465 "Binding at line %1 column %2 is deferred and thus not compiled")
1466 .arg(args: QString::number(location.startLine),
1467 args: QString::number(location.startColumn));
1468 continue;
1469 }
1470 }
1471
1472 const QQmlJSMetaProperty metaProperty = type->property(name: propertyName);
1473 const QQmlJSScope::ConstPtr propertyType = metaProperty.type();
1474
1475 if (!(qIsReferenceTypeList(p: metaProperty) && unprocessedListProperty == metaProperty)) {
1476 processLastListBindings(current, type, accessor);
1477 }
1478
1479 compileBindingByType(current, binding, type, accessor);
1480 }
1481
1482 processLastListBindings(current, type, accessor);
1483}
1484
1485void QmltcCompiler::compileBindingByType(QmltcType &current,
1486 const QQmlJSMetaPropertyBinding &binding,
1487 const QQmlJSScope::ConstPtr &type,
1488 const BindingAccessorData &accessor)
1489{
1490 const QString &propertyName = binding.propertyName();
1491 const QQmlJSMetaProperty metaProperty = type->property(name: propertyName);
1492 const QQmlJSScope::ConstPtr propertyType = metaProperty.type();
1493
1494 const auto assignToProperty = [&](const QQmlJSMetaProperty &p, const QString &value,
1495 bool constructFromQObject = false) {
1496 QmltcCodeGenerator::generate_assignToProperty(block: &current.endInit.body, type, p, value,
1497 accessor: accessor.name, constructFromQObject);
1498 };
1499 switch (binding.bindingType()) {
1500 case QQmlSA::BindingType::BoolLiteral: {
1501 const bool value = binding.boolValue();
1502 assignToProperty(metaProperty, value ? u"true"_s : u"false"_s);
1503 break;
1504 }
1505 case QQmlSA::BindingType::NumberLiteral: {
1506 assignToProperty(metaProperty, QString::number(binding.numberValue()));
1507 break;
1508 }
1509 case QQmlSA::BindingType::StringLiteral: {
1510 assignToProperty(metaProperty, QQmlJSUtils::toLiteral(s: binding.stringValue()));
1511 break;
1512 }
1513 case QQmlSA::BindingType::RegExpLiteral: {
1514 const QString value =
1515 u"QRegularExpression(%1)"_s.arg(a: QQmlJSUtils::toLiteral(s: binding.regExpValue()));
1516 assignToProperty(metaProperty, value);
1517 break;
1518 }
1519 case QQmlSA::BindingType::Null: {
1520 // poor check: null bindings are only supported for var and objects
1521 Q_ASSERT(propertyType->isSameType(m_typeResolver->varType())
1522 || propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference);
1523 if (propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
1524 assignToProperty(metaProperty, u"nullptr"_s);
1525 else
1526 assignToProperty(metaProperty, u"QVariant::fromValue(nullptr)"_s);
1527 break;
1528 }
1529 case QQmlSA::BindingType::Script: {
1530 QString bindingSymbolName = type->internalName() + u'_' + propertyName + u"_binding";
1531 bindingSymbolName.replace(before: u'.', after: u'_'); // can happen with group properties
1532 compileScriptBinding(current, binding, bindingSymbolName, type, propertyName, propertyType,
1533 accessor);
1534 break;
1535 }
1536 case QQmlSA::BindingType::Object: {
1537 compileObjectBinding(current, binding, type, accessor);
1538 break;
1539 }
1540 case QQmlSA::BindingType::Interceptor:
1541 Q_FALLTHROUGH();
1542 case QQmlSA::BindingType::ValueSource: {
1543 compileValueSourceOrInterceptorBinding(current, binding, type, accessor);
1544 break;
1545 }
1546 case QQmlSA::BindingType::AttachedProperty: {
1547 compileAttachedPropertyBinding(current, binding, type, accessor);
1548 break;
1549 }
1550 case QQmlSA::BindingType::GroupProperty: {
1551 compileGroupPropertyBinding(current, binding, type, accessor);
1552 break;
1553 }
1554
1555 case QQmlSA::BindingType::TranslationById:
1556 case QQmlSA::BindingType::Translation: {
1557 compileTranslationBinding(current, binding, type, accessor);
1558 break;
1559 }
1560 case QQmlSA::BindingType::Invalid: {
1561 recordError(location: binding.sourceLocation(), message: u"This binding is invalid"_s);
1562 break;
1563 }
1564 default: {
1565 recordError(location: binding.sourceLocation(), message: u"Binding is not supported"_s);
1566 break;
1567 }
1568 }
1569}
1570
1571// returns compiled script binding for "property changed" handler in a form of object type
1572static QmltcType compileScriptBindingPropertyChangeHandler(const QQmlJSMetaPropertyBinding &binding,
1573 const QQmlJSScope::ConstPtr &objectType,
1574 const QString &urlMethodName,
1575 const QString &functorCppType,
1576 const QString &objectCppType)
1577{
1578 QmltcType bindingFunctor {};
1579 bindingFunctor.cppType = functorCppType;
1580 bindingFunctor.ignoreInit = true;
1581
1582 // default member variable and ctor:
1583 const QString pointerToObject = objectCppType + u" *";
1584 bindingFunctor.variables.emplaceBack(
1585 args: QmltcVariable { pointerToObject, u"m_self"_s, u"nullptr"_s });
1586 bindingFunctor.baselineCtor.name = functorCppType;
1587 bindingFunctor.baselineCtor.parameterList.emplaceBack(
1588 args: QmltcVariable { pointerToObject, u"self"_s, QString() });
1589 bindingFunctor.baselineCtor.initializerList.emplaceBack(args: u"m_self(self)"_s);
1590
1591 // call operator:
1592 QmltcMethod callOperator {};
1593 callOperator.returnType = u"void"_s;
1594 callOperator.name = u"operator()"_s;
1595 callOperator.modifiers << u"const"_s;
1596 QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
1597 block: &callOperator.body, url: urlMethodName + u"()",
1598 index: objectType->ownRuntimeFunctionIndex(index: binding.scriptIndex()), accessor: u"m_self"_s, returnType: u"void"_s, parameters: {});
1599
1600 bindingFunctor.functions.emplaceBack(args: std::move(callOperator));
1601
1602 return bindingFunctor;
1603}
1604
1605// finds property for given scope and returns it together with the absolute
1606// property index in the property array of the corresponding QMetaObject
1607static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope,
1608 const QString &propertyName)
1609{
1610 auto owner = QQmlJSScope::ownerOfProperty(self: scope, name: propertyName).scope;
1611 Q_ASSERT(owner);
1612 const QQmlJSMetaProperty p = owner->ownProperty(name: propertyName);
1613 if (!p.isValid())
1614 return { p, -1 };
1615 int index = p.index();
1616 if (index < 0) // this property doesn't have index - comes from QML
1617 return { p, -1 };
1618
1619 const auto increment = [&](const QQmlJSScope::ConstPtr &type, QQmlJSScope::ExtensionKind m) {
1620 // owner of property is not included in the offset calculation (relative
1621 // index is already added as p.index())
1622 if (type->isSameType(otherScope: owner))
1623 return;
1624 if (m == QQmlJSScope::ExtensionNamespace) // extension namespace properties are ignored
1625 return;
1626 index += int(type->ownProperties().size());
1627 };
1628 QQmlJSUtils::traverseFollowingMetaObjectHierarchy(scope, start: owner, act: increment);
1629 return { p, index };
1630}
1631
1632void QmltcCompiler::compileScriptBinding(QmltcType &current,
1633 const QQmlJSMetaPropertyBinding &binding,
1634 const QString &bindingSymbolName,
1635 const QQmlJSScope::ConstPtr &objectType,
1636 const QString &propertyName,
1637 const QQmlJSScope::ConstPtr &propertyType,
1638 const QmltcCompiler::BindingAccessorData &accessor)
1639{
1640 const auto compileScriptSignal = [&](const QString &name) {
1641 QString This_signal = u"this"_s;
1642 QString This_slot = u"this"_s;
1643 QString objectClassName_signal = objectType->internalName();
1644 QString objectClassName_slot = objectType->internalName();
1645
1646 // TODO: ugly crutch to make stuff work
1647 if (accessor.name != u"this"_s) { // e.g. if attached property
1648 This_signal = accessor.name;
1649 This_slot = u"this"_s; // still
1650 objectClassName_signal = objectType->baseTypeName();
1651 objectClassName_slot = current.cppType; // real base class where slot would go
1652 }
1653 Q_ASSERT(!objectClassName_signal.isEmpty());
1654 Q_ASSERT(!objectClassName_slot.isEmpty());
1655
1656 const auto signalMethods = objectType->methods(name, type: QQmlJSMetaMethodType::Signal);
1657 Q_ASSERT(!signalMethods.isEmpty()); // an error somewhere else
1658 QQmlJSMetaMethod signal = signalMethods.at(i: 0);
1659 Q_ASSERT(signal.methodType() == QQmlJSMetaMethodType::Signal);
1660
1661 const QString signalName = signal.methodName();
1662 const QString slotName = newSymbol(base: signalName + u"_slot");
1663
1664 const QString signalReturnType = figureReturnType(m: signal);
1665 const QList<QmltcVariable> slotParameters =
1666 compileMethodParameters(parameterInfos: signal.parameters(), /* allow unnamed = */ allowUnnamed: true);
1667
1668 // SignalHander specific:
1669 QmltcMethod slotMethod {};
1670 slotMethod.returnType = signalReturnType;
1671 slotMethod.name = slotName;
1672 slotMethod.parameterList = slotParameters;
1673
1674 QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
1675 block: &slotMethod.body, url: m_urlMethodName + u"()",
1676 index: objectType->ownRuntimeFunctionIndex(index: binding.scriptIndex()),
1677 accessor: u"this"_s, // Note: because script bindings always use current QML object scope
1678 returnType: signalReturnType, parameters: slotParameters);
1679 slotMethod.type = QQmlJSMetaMethodType::Slot;
1680
1681 current.functions << std::move(slotMethod);
1682 current.setComplexBindings.body << u"QObject::connect(" + This_signal + u", " + u"&"
1683 + objectClassName_signal + u"::" + signalName + u", " + This_slot + u", &"
1684 + objectClassName_slot + u"::" + slotName + u");";
1685 };
1686
1687 switch (binding.scriptKind()) {
1688 case QQmlSA::ScriptBindingKind::PropertyBinding: {
1689 if (!propertyType) {
1690 recordError(location: binding.sourceLocation(),
1691 message: u"Binding on property '" + propertyName + u"' of unknown type");
1692 return;
1693 }
1694
1695 auto [property, absoluteIndex] = getMetaPropertyIndex(scope: objectType, propertyName);
1696 if (absoluteIndex < 0) {
1697 recordError(location: binding.sourceLocation(),
1698 message: u"Binding on unknown property '" + propertyName + u"'");
1699 return;
1700 }
1701
1702 QString bindingTarget = accessor.name;
1703
1704 int valueTypeIndex = -1;
1705 if (accessor.isValueType) {
1706 Q_ASSERT(accessor.scope != objectType);
1707 bindingTarget = u"this"_s; // TODO: not necessarily "this"?
1708 auto [groupProperty, groupPropertyIndex] =
1709 getMetaPropertyIndex(scope: accessor.scope, propertyName: accessor.propertyName);
1710 if (groupPropertyIndex < 0) {
1711 recordError(location: binding.sourceLocation(),
1712 message: u"Binding on group property '" + accessor.propertyName
1713 + u"' of unknown type");
1714 return;
1715 }
1716 valueTypeIndex = absoluteIndex;
1717 absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name
1718 }
1719
1720 QmltcCodeGenerator::generate_createBindingOnProperty(
1721 block: &current.setComplexBindings.body, unitVarName: generate_callCompilationUnit(urlMethodName: m_urlMethodName),
1722 scope: u"this"_s, // NB: always using enclosing object as a scope for the binding
1723 functionIndex: static_cast<qsizetype>(objectType->ownRuntimeFunctionIndex(index: binding.scriptIndex())),
1724 target: bindingTarget, // binding target
1725 // value types are special and are bound through valueTypeIndex
1726 targetType: accessor.isValueType ? QQmlJSScope::ConstPtr() : objectType, propertyIndex: absoluteIndex,
1727 p: property, valueTypeIndex, subTarget: accessor.name);
1728 break;
1729 }
1730 case QQmlSA::ScriptBindingKind::SignalHandler: {
1731 const auto name = QQmlJSUtils::signalName(handlerName: propertyName);
1732 Q_ASSERT(name.has_value());
1733 compileScriptSignal(*name);
1734 break;
1735 }
1736 case QQmlSA ::ScriptBindingKind::ChangeHandler: {
1737 const QString objectClassName = objectType->internalName();
1738 const QString bindingFunctorName = newSymbol(base: bindingSymbolName + u"Functor");
1739
1740 const auto signalName = QQmlJSUtils::signalName(handlerName: propertyName);
1741 Q_ASSERT(signalName.has_value()); // an error somewhere else
1742 const auto actualProperty = QQmlJSUtils::changeHandlerProperty(scope: objectType, signalName: *signalName);
1743 Q_ASSERT(actualProperty.has_value()); // an error somewhere else
1744 const auto actualPropertyType = actualProperty->type();
1745 if (!actualPropertyType) {
1746 recordError(location: binding.sourceLocation(),
1747 message: u"Binding on property '" + actualProperty->propertyName()
1748 + u"' of unknown type");
1749 return;
1750 }
1751
1752 // due to historical reasons (QQmlObjectCreator), prefer NOTIFY over
1753 // BINDABLE when both are available. thus, test for notify first
1754 const QString notifyString = actualProperty->notify();
1755 if (!notifyString.isEmpty()) {
1756 compileScriptSignal(notifyString);
1757 break;
1758 }
1759 const QString bindableString = actualProperty->bindable();
1760 QString typeOfQmlBinding =
1761 u"std::unique_ptr<QPropertyChangeHandler<" + bindingFunctorName + u">>";
1762
1763 current.children << compileScriptBindingPropertyChangeHandler(
1764 binding, objectType, urlMethodName: m_urlMethodName, functorCppType: bindingFunctorName, objectCppType: objectClassName);
1765
1766 // TODO: this could be dropped if QQmlEngine::setContextForObject() is
1767 // done before currently generated C++ object is constructed
1768 current.setComplexBindings.body << bindingSymbolName + u".reset(new QPropertyChangeHandler<"
1769 + bindingFunctorName + u">("
1770 + QmltcCodeGenerator::wrap_privateClass(accessor: accessor.name, p: *actualProperty)
1771 + u"->" + bindableString + u"().onValueChanged(" + bindingFunctorName + u"("
1772 + accessor.name + u"))));";
1773
1774 current.variables.emplaceBack(
1775 args: QmltcVariable { typeOfQmlBinding, bindingSymbolName, QString() });
1776 break;
1777 }
1778 default:
1779 recordError(location: binding.sourceLocation(), message: u"Invalid script binding found"_s);
1780 break;
1781 }
1782}
1783
1784QT_END_NAMESPACE
1785

source code of qtdeclarative/tools/qmltc/qmltccompiler.cpp