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

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