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.
539static void compilePropertyInitializer(QmltcType &current, const QQmlJSScope::ConstPtr &type) {
540 static auto isFromExtension = [](const QQmlJSMetaProperty &property, const QQmlJSScope::ConstPtr &scope) {
541 return scope->ownerOfProperty(self: scope, name: property.propertyName()).extensionSpecifier != QQmlJSScope::NotExtension;
542 };
543
544 current.propertyInitializer.constructor.initializerList << u"component{component}"_s;
545
546 auto properties = type->properties().values();
547 for (auto& property: properties) {
548 if (property.index() == -1) continue;
549 if (property.isPrivate()) continue;
550 if (!property.isWritable() && !qIsReferenceTypeList(p: property)) continue;
551
552 const QString name = property.propertyName();
553
554 current.propertyInitializer.propertySetters.emplace_back();
555 auto& compiledSetter = current.propertyInitializer.propertySetters.back();
556
557 compiledSetter.userVisible = true;
558 compiledSetter.returnType = u"void"_s;
559 compiledSetter.name = QmltcPropertyData(property).write;
560
561 if (qIsReferenceTypeList(p: property)) {
562 compiledSetter.parameterList.emplaceBack(
563 args: QQmlJSUtils::constRefify(type: u"QList<%1*>"_s.arg(a: property.type()->valueType()->internalName())),
564 args: name + u"_", args: QString()
565 );
566 } else {
567 compiledSetter.parameterList.emplaceBack(
568 args: QQmlJSUtils::constRefify(type: getUnderlyingType(p: property)), args: name + u"_", args: QString()
569 );
570 }
571
572 if (qIsReferenceTypeList(p: property)) {
573 compiledSetter.body << u"QQmlListReference list_ref_(&%1, \"%2\");"_s.arg(
574 args&: current.propertyInitializer.component.name, args: name
575 );
576 compiledSetter.body << u"list_ref_.clear();"_s;
577 compiledSetter.body << u"for (const auto& list_item_ : %1_)"_s.arg(a: name);
578 compiledSetter.body << u" list_ref_.append(list_item_);"_s;
579 } else if (
580 QQmlJSUtils::bindablePropertyHasDefaultAccessor(p: property, accessor: QQmlJSUtils::PropertyAccessor_Write)
581 ) {
582 compiledSetter.body << u"%1.%2().setValue(%3_);"_s.arg(
583 args&: current.propertyInitializer.component.name, args: property.bindable(), args: name);
584 } else if (type->hasOwnProperty(name)) {
585 compiledSetter.body << u"%1.%2(%3_);"_s.arg(
586 args&: current.propertyInitializer.component.name, args: QmltcPropertyData(property).write, args: name);
587 } else if (property.write().isEmpty() || isFromExtension(property, type)) {
588 // We can end here if a WRITE method is not available or
589 // if the method is available but not in this scope, so
590 // that we fallback to the string-based setters..
591 //
592 // For example, types that makes use of QML_EXTENDED
593 // types, will have the extension types properties
594 // available and with a WRITE method, but the WRITE method
595 // will not be available to the extended type, from C++,
596 // as the type does not directly inherit from the
597 // extension type.
598 //
599 // We specifically scope `setProperty` to `QObject` as
600 // certain types might have shadowed the method.
601 // For example, in QtQuick, some types have a property
602 // called `property` with a `setProperty` WRITE method
603 // that will produce the shadowing.
604 compiledSetter.body << u"%1.QObject::setProperty(\"%2\", QVariant::fromValue(%2_));"_s.arg(
605 args&: current.propertyInitializer.component.name, args: name);
606 } else {
607 compiledSetter.body << u"%1.%2(%3_);"_s.arg(
608 args&: current.propertyInitializer.component.name, args: property.write(), args: name);
609 }
610
611 compiledSetter.body << u"%1.insert(QStringLiteral(\"%2\"));"_s.arg(
612 args&: current.propertyInitializer.initializedCache.name, args: name);
613 }
614}
615
616void QmltcCompiler::compileTypeElements(QmltcType &current, const QQmlJSScope::ConstPtr &type)
617{
618 // compile components of a type:
619 // - enums
620 // - properties
621 // - methods
622 // - bindings
623
624 const auto enums = type->ownEnumerations();
625 current.enums.reserve(asize: enums.size());
626 for (auto it = enums.begin(); it != enums.end(); ++it)
627 compileEnum(current, e: it.value());
628
629 auto properties = type->ownProperties().values();
630 current.properties.reserve(asize: properties.size());
631 // Note: index() is the (future) meta property index, so make sure given
632 // properties are ordered by that index before compiling
633 std::sort(first: properties.begin(), last: properties.end(),
634 comp: [](const QQmlJSMetaProperty &x, const QQmlJSMetaProperty &y) {
635 return x.index() < y.index();
636 });
637 for (const QQmlJSMetaProperty &p : std::as_const(t&: properties)) {
638 if (p.index() == -1) {
639 recordError(location: type->sourceLocation(),
640 message: u"Internal error: property '%1' has incomplete information"_s.arg(
641 a: p.propertyName()));
642 continue;
643 }
644 if (p.isAlias()) {
645 compileAlias(current, alias: p, owner: type);
646 } else {
647 compileProperty(current, p, owner: type);
648 }
649 }
650
651 const auto methods = type->ownMethods();
652 for (const QQmlJSMetaMethod &m : methods)
653 compileMethod(current, m, owner: type);
654
655 auto bindings = type->ownPropertyBindingsInQmlIROrder();
656 partitionBindings(first: bindings.begin(), last: bindings.end());
657
658 compilePropertyInitializer(current, type);
659 compileBinding(current, bindingStart: bindings.begin(), bindingEnd: bindings.end(), type, accessor: { .scope: type });
660}
661
662void QmltcCompiler::compileEnum(QmltcType &current, const QQmlJSMetaEnum &e)
663{
664 const auto intValues = e.values();
665 QStringList values;
666 values.reserve(asize: intValues.size());
667 std::transform(first: intValues.cbegin(), last: intValues.cend(), result: std::back_inserter(x&: values),
668 unary_op: [](int x) { return QString::number(x); });
669
670 // structure: (C++ type name, enum keys, enum values, MOC line)
671 current.enums.emplaceBack(args: e.name(), args: e.keys(), args: std::move(values),
672 args: u"Q_ENUM(%1)"_s.arg(a: e.name()));
673}
674
675static QList<QmltcVariable>
676compileMethodParameters(const QList<QQmlJSMetaParameter> &parameterInfos, bool allowUnnamed = false)
677{
678 QList<QmltcVariable> parameters;
679 const auto size = parameterInfos.size();
680 parameters.reserve(asize: size);
681 for (qsizetype i = 0; i < size; ++i) {
682 const auto &p = parameterInfos[i];
683 Q_ASSERT(p.type()); // assume verified
684 QString name = p.name();
685 Q_ASSERT(allowUnnamed || !name.isEmpty()); // assume verified
686 if (name.isEmpty() && allowUnnamed)
687 name = u"unnamed_" + QString::number(i);
688
689 QString internalName;
690 const QQmlJSScope::AccessSemantics semantics = p.type()->accessSemantics();
691
692 switch (semantics) {
693 case QQmlJSScope::AccessSemantics::Reference:
694 if (p.typeQualifier() == QQmlJSMetaParameter::Const)
695 internalName = u"const "_s;
696 internalName += u"%1*"_s.arg(a: p.type()->internalName());
697 break;
698 case QQmlJSScope::AccessSemantics::Value:
699 case QQmlJSScope::AccessSemantics::Sequence:
700 internalName = u"passByConstRefOrValue<%1>"_s.arg(a: p.type()->internalName());
701 break;
702 case QQmlJSScope::AccessSemantics::None:
703 Q_ASSERT(false); // or maybe print an error message
704 }
705 parameters.emplaceBack(args&: internalName, args&: name, args: QString());
706 }
707 return parameters;
708}
709
710static QString figureReturnType(const QQmlJSMetaMethod &m)
711{
712 const bool isVoidMethod =
713 m.returnTypeName() == u"void" || m.methodType() == QQmlJSMetaMethodType::Signal;
714 Q_ASSERT(isVoidMethod || m.returnType());
715 QString type;
716 if (isVoidMethod) {
717 type = u"void"_s;
718 } else {
719 type = m.returnType()->augmentedInternalName();
720 }
721 return type;
722}
723
724void QmltcCompiler::compileMethod(QmltcType &current, const QQmlJSMetaMethod &m,
725 const QQmlJSScope::ConstPtr &owner)
726{
727 const auto returnType = figureReturnType(m);
728
729 const QList<QmltcVariable> compiledParams = compileMethodParameters(parameterInfos: m.parameters());
730 const auto methodType = m.methodType();
731
732 QStringList code;
733 if (methodType != QQmlJSMetaMethodType::Signal) {
734 QmltcCodeGenerator urlGenerator { .documentUrl: m_url, .visitor: m_visitor };
735 QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
736 block: &code, url: urlGenerator.urlMethodName() + u"()",
737 index: owner->ownRuntimeFunctionIndex(index: m.jsFunctionIndex()), accessor: u"this"_s, returnType,
738 parameters: compiledParams);
739 }
740
741 QmltcMethod compiled {};
742 compiled.returnType = returnType;
743 compiled.name = m.methodName();
744 compiled.parameterList = std::move(compiledParams);
745 compiled.body = std::move(code);
746 compiled.type = methodType;
747 compiled.access = m.access();
748 if (methodType != QQmlJSMetaMethodType::Signal) {
749 compiled.declarationPrefixes << u"Q_INVOKABLE"_s;
750 compiled.userVisible = m.access() == QQmlJSMetaMethod::Public;
751 } else {
752 compiled.userVisible = !m.isImplicitQmlPropertyChangeSignal();
753 }
754 current.functions.emplaceBack(args&: compiled);
755}
756
757/*! \internal
758 Compiles an extra set of methods for Lists, that makes manipulating lists easier from C++
759 for the user.
760*/
761void QmltcCompiler::compileExtraListMethods(QmltcType &current, const QQmlJSMetaProperty &p)
762{
763 QmltcPropertyData data(p);
764 const QString valueType = p.type()->valueType()->internalName() + u'*';
765 const QString variableName = data.read + u"()"_s;
766 const QStringList ownershipWarning = {
767 u"\\note {This method does not change the ownership of its argument."_s,
768 u"The caller is responsible for setting the argument's \\c {QObject::parent} or"_s,
769 u"for ensuring that the argument lives long enough."_s,
770 u"For example, an argument created with \\c {createObject()} that has no parent"_s,
771 u"will eventually be garbage-collected, leaving a dangling pointer.}"_s
772 };
773
774 // generate append() sugar for users
775 {
776 QmltcMethod append{};
777 append.comments.emplaceBack(args: u"\\brief Append an element to %1."_s.arg(a: data.read));
778 append.comments << ownershipWarning;
779 append.returnType = u"void"_s;
780 append.name = u"%1Append"_s.arg(a: data.read);
781 append.parameterList.emplaceBack(args: valueType, args: u"toBeAppended"_s);
782
783 append.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName);
784 append.body
785 << u"q_qmltc_localList.append(std::addressof(q_qmltc_localList), toBeAppended);"_s;
786 // append.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 is
787 // resolved
788 append.userVisible = true;
789 current.functions.emplaceBack(args&: append);
790 }
791
792 // generate count() sugar for users
793 {
794 QmltcMethod count{};
795 count.comments.emplaceBack(args: u"\\brief Number of elements in %1."_s.arg(a: data.read));
796 count.returnType = u"int"_s;
797 count.name = u"%1Count"_s.arg(a: data.read);
798
799 count.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName);
800 count.body << u"int result = q_qmltc_localList.count(std::addressof(q_qmltc_localList));"_s;
801 count.body << u"return result;"_s;
802 count.userVisible = true;
803 current.functions.emplaceBack(args&: count);
804 }
805
806 // generate at() sugar for users
807 {
808 QmltcMethod at{};
809 at.comments.emplaceBack(args: u"\\brief Access an element in %1."_s.arg(a: data.read));
810 at.returnType = valueType;
811 at.name = u"%1At"_s.arg(a: data.read);
812 at.parameterList.emplaceBack(args: u"qsizetype"_s, args: u"position"_s, args: QString());
813
814 at.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName);
815 at.body << u"auto result = q_qmltc_localList.at(std::addressof(q_qmltc_localList), position);"_s;
816 at.body << u"return result;"_s;
817 at.userVisible = true;
818 current.functions.emplaceBack(args&: at);
819 }
820
821 // generate clear() sugar for users
822 {
823 QmltcMethod clear{};
824 clear.comments.emplaceBack(args: u"\\brief Clear %1."_s.arg(a: data.read));
825 clear.returnType = u"void"_s;
826 clear.name = u"%1Clear"_s.arg(a: data.read);
827
828 clear.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName);
829 clear.body << u"q_qmltc_localList.clear(std::addressof(q_qmltc_localList));"_s;
830 // clear.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587 is
831 // resolved
832 clear.userVisible = true;
833 current.functions.emplaceBack(args&: clear);
834 }
835
836 // generate replace() sugar for users
837 {
838 QmltcMethod replace{};
839 replace.comments.emplaceBack(args: u"\\brief Replace an element in %1."_s.arg(a: data.read));
840 replace.comments << ownershipWarning;
841 replace.returnType = u"void"_s;
842 replace.name = u"%1Replace"_s.arg(a: data.read);
843 replace.parameterList.emplaceBack(args: u"qsizetype"_s, args: u"position"_s, args: QString());
844 replace.parameterList.emplaceBack(args: valueType, args: u"element"_s,
845 args: QString());
846
847 replace.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName);
848 replace.body
849 << u"q_qmltc_localList.replace(std::addressof(q_qmltc_localList), position, element);"_s;
850 // replace.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when QTBUG-106587
851 // is resolved
852 replace.userVisible = true;
853 current.functions.emplaceBack(args&: replace);
854 }
855
856 // generate removeLast() sugar for users
857 {
858 QmltcMethod removeLast{};
859 removeLast.comments.emplaceBack(args: u"\\brief Remove the last element in %1."_s.arg(a: data.read));
860 removeLast.returnType = u"void"_s;
861 removeLast.name = u"%1RemoveLast"_s.arg(a: data.read);
862
863 removeLast.body << u"auto q_qmltc_localList = %1;"_s.arg(a: variableName);
864 removeLast.body << u"q_qmltc_localList.removeLast(std::addressof(q_qmltc_localList));"_s;
865 // removeLast.body << u"Q_EMIT %1();"_s.arg(data.notify); // uncomment this when
866 // QTBUG-106587 is resolved
867
868 removeLast.userVisible = true;
869 current.functions.emplaceBack(args&: removeLast);
870 }
871}
872
873void QmltcCompiler::compileProperty(QmltcType &current, const QQmlJSMetaProperty &p,
874 const QQmlJSScope::ConstPtr &owner)
875{
876 Q_ASSERT(!p.isAlias()); // will be handled separately
877 Q_ASSERT(p.type());
878
879 const QString name = p.propertyName();
880 const QString variableName = u"m_" + name;
881 const QString underlyingType = getUnderlyingType(p);
882 if (qIsReferenceTypeList(p)) {
883 const QString storageName = variableName + u"_storage";
884 current.variables.emplaceBack(
885 args: u"QList<" + p.type()->valueType()->internalName() + u"*>", args: storageName,
886 args: QString());
887 current.baselineCtor.initializerList.emplaceBack(args: variableName + u"(" + underlyingType
888 + u"(this, std::addressof(" + storageName
889 + u")))");
890 compileExtraListMethods(current, p);
891 }
892
893 // along with property, also add relevant moc code, so that we can use the
894 // property in Qt/QML contexts
895 QStringList mocPieces;
896 mocPieces.reserve(asize: 10);
897 mocPieces << underlyingType << name;
898
899 QmltcPropertyData compilationData(p);
900
901 // 1. add setter and getter
902 // If p.isList(), it's a QQmlListProperty. Then you can write the underlying list through
903 // the QQmlListProperty object retrieved with the getter. Setting it would make no sense.
904 if (p.isWritable() && !qIsReferenceTypeList(p)) {
905 QmltcMethod setter {};
906 setter.returnType = u"void"_s;
907 setter.name = compilationData.write;
908 // QmltcVariable
909 setter.parameterList.emplaceBack(args: QQmlJSUtils::constRefify(type: underlyingType), args: name + u"_",
910 args: u""_s);
911 setter.body << variableName + u".setValue(" + name + u"_);";
912 setter.body << u"Q_EMIT " + compilationData.notify + u"();";
913 setter.userVisible = true;
914 current.functions.emplaceBack(args&: setter);
915 mocPieces << u"WRITE"_s << setter.name;
916 }
917
918 QmltcMethod getter {};
919 getter.returnType = underlyingType;
920 getter.name = compilationData.read;
921 getter.body << u"return " + variableName + u".value();";
922 getter.userVisible = true;
923 current.functions.emplaceBack(args&: getter);
924 mocPieces << u"READ"_s << getter.name;
925
926 // 2. add bindable
927 if (!qIsReferenceTypeList(p)) {
928 QmltcMethod bindable {};
929 bindable.returnType = u"QBindable<" + underlyingType + u">";
930 bindable.name = compilationData.bindable;
931 bindable.body << u"return QBindable<" + underlyingType + u">(std::addressof(" + variableName
932 + u"));";
933 bindable.userVisible = true;
934 current.functions.emplaceBack(args&: bindable);
935 mocPieces << u"BINDABLE"_s << bindable.name;
936 }
937
938 // 3. add/check notify (actually, this is already done inside QmltcVisitor)
939
940 if (owner->isPropertyRequired(name))
941 mocPieces << u"REQUIRED"_s;
942
943 // 4. add moc entry
944 // e.g. Q_PROPERTY(QString p READ getP WRITE setP BINDABLE bindableP)
945 current.mocCode << u"Q_PROPERTY(" + mocPieces.join(sep: u" "_s) + u")";
946
947 // 5. add extra moc entry if this property is marked default
948 if (name == owner->defaultPropertyName())
949 current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(a: name);
950
951 // structure: (C++ type name, name, C++ class name, C++ signal name)
952 current.properties.emplaceBack(args: underlyingType, args: variableName, args&: current.cppType,
953 args&: compilationData.notify);
954}
955
956/*!
957 * \internal
958 *
959 * Models one step of the alias resolution. If the current alias to be resolved
960 * points to \c {x.y.z} and that \c {x.y} is already resolved, then this struct
961 * contains the information on how to obtain the \c {z} part from \c {x.y}.
962 */
963struct AliasResolutionFrame
964{
965 /*!
966 * \internal
967 *
968 * Placeholder for the current resolved state. It is replaced later with
969 * the result from previous resolutions from the \c QStack<AliasResolutionFrame>.
970 *
971 * \sa unpackFrames()
972 */
973 static QString inVar;
974
975 /*!
976 * \internal
977 *
978 * Steps to access this value as a list of C++ statements, to be used in
979 * conjunction with \c {epilogue}.
980 */
981 QStringList prologue;
982
983 /*!
984 * \internal
985 *
986 * Steps to finish the statements of the \c prologue (e.g. closing brackets).
987 */
988 QStringList epilogue;
989
990 /*!
991 * \internal
992 *
993 * Instructions on how to write the property, after it was loaded with the
994 * instructions from \c prologue. Has to happen before \c epilogue.
995 */
996 QStringList epilogueForWrite;
997
998 /*!
999 * \internal
1000 *
1001 * Name of the variable holding the result of this resolution step, to be
1002 * used in the following resolution steps.
1003 */
1004 QString outVar;
1005};
1006// special string replaced by outVar of the previous frame
1007QString AliasResolutionFrame::inVar = QStringLiteral("__QMLTC_ALIAS_FRAME_INPUT_VAR__");
1008
1009/*!
1010 * \internal
1011 *
1012 * Process the frames by replacing the placeholder \c invar
1013 * used in \c epilogueForWrite and \c prologue with the result
1014 * obtained from the previous frame.
1015 */
1016static void unpackFrames(QStack<AliasResolutionFrame> &frames)
1017{
1018 if (frames.size() < 2)
1019 return;
1020
1021 // assume first frame is fine
1022 auto prev = frames.begin();
1023 for (auto it = std::next(x: prev); it != frames.end(); ++it, ++prev) {
1024 for (QString &line : it->prologue)
1025 line.replace(before: AliasResolutionFrame::inVar, after: prev->outVar);
1026 for (QString &line : it->epilogueForWrite)
1027 line.replace(before: AliasResolutionFrame::inVar, after: prev->outVar);
1028 it->outVar.replace(before: AliasResolutionFrame::inVar, after: prev->outVar);
1029 }
1030}
1031
1032template<typename Projection>
1033static QStringList joinFrames(const QStack<AliasResolutionFrame> &frames, Projection project)
1034{
1035 QStringList joined;
1036 for (const AliasResolutionFrame &frame : frames)
1037 joined += project(frame);
1038 return joined;
1039}
1040
1041void QmltcCompiler::compileAlias(QmltcType &current, const QQmlJSMetaProperty &alias,
1042 const QQmlJSScope::ConstPtr &owner)
1043{
1044 const QString aliasName = alias.propertyName();
1045 Q_ASSERT(!aliasName.isEmpty());
1046
1047 QStringList aliasExprBits = alias.aliasExpression().split(sep: u'.');
1048 Q_ASSERT(!aliasExprBits.isEmpty());
1049
1050 QStack<AliasResolutionFrame> frames;
1051
1052 QQmlJSUtils::AliasResolutionVisitor aliasVisitor;
1053 qsizetype i = 0;
1054 aliasVisitor.reset = [&]() {
1055 frames.clear();
1056 i = 0; // we use it in property processing
1057
1058 // first frame is a dummy one:
1059 frames.push(
1060 t: AliasResolutionFrame { .prologue: QStringList(), .epilogue: QStringList(), .epilogueForWrite: QStringList(), .outVar: u"this"_s });
1061 };
1062 aliasVisitor.processResolvedId = [&](const QQmlJSScope::ConstPtr &type) {
1063 Q_ASSERT(type);
1064 if (owner != type) { // cannot start at `this`, need to fetch object through context
1065 const int id = m_visitor->runtimeId(type);
1066 Q_ASSERT(id >= 0); // since the type is found by id, it must have an id
1067
1068 AliasResolutionFrame queryIdFrame {};
1069 Q_ASSERT(frames.top().outVar == u"this"_s); // so inVar would be "this" as well
1070 queryIdFrame.prologue << u"auto context = %1::q_qmltc_thisContext;"_s.arg(
1071 a: owner->internalName());
1072
1073 // doing the above allows us to lookup id object by index (fast)
1074 queryIdFrame.outVar = u"alias_objectById_" + aliasExprBits.front(); // unique enough
1075 const QString cppType = (m_visitor->qmlComponentIndex(type) == -1)
1076 ? type->internalName()
1077 : u"QQmlComponent"_s;
1078 queryIdFrame.prologue << u"auto " + queryIdFrame.outVar + u" = static_cast<" + cppType
1079 + u"*>(context->idValue(" + QString::number(id) + u"));";
1080 queryIdFrame.prologue << u"Q_ASSERT(" + queryIdFrame.outVar + u");";
1081
1082 frames.push(t: queryIdFrame);
1083 }
1084 };
1085 aliasVisitor.processResolvedProperty = [&](const QQmlJSMetaProperty &p,
1086 const QQmlJSScope::ConstPtr &owner) {
1087 AliasResolutionFrame queryPropertyFrame {};
1088
1089 auto [extensionPrologue, extensionAccessor, extensionEpilogue] =
1090 QmltcCodeGenerator::wrap_extensionType(
1091 type: owner, p,
1092 accessor: QmltcCodeGenerator::wrap_privateClass(accessor: AliasResolutionFrame::inVar, p));
1093 QString inVar = extensionAccessor;
1094 queryPropertyFrame.prologue += extensionPrologue;
1095 if (p.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Value) {
1096 // we need to read the property to a local variable and then
1097 // write the updated value once the actual operation is done
1098 const QString aliasVar = u"alias_" + QString::number(i); // should be fairly unique
1099 ++i;
1100 queryPropertyFrame.prologue
1101 << u"auto " + aliasVar + u" = " + inVar + u"->" + p.read() + u"();";
1102 queryPropertyFrame.epilogueForWrite
1103 << inVar + u"->" + p.write() + u"(" + aliasVar + u");";
1104 // NB: since accessor becomes a value type, wrap it into an
1105 // addressof operator so that we could access it as a pointer
1106 inVar = QmltcCodeGenerator::wrap_addressof(addressed: aliasVar); // reset
1107 } else {
1108 inVar += u"->" + p.read() + u"()"; // update
1109 }
1110 queryPropertyFrame.outVar = inVar;
1111 queryPropertyFrame.epilogue += extensionEpilogue;
1112
1113 frames.push(t: queryPropertyFrame);
1114 };
1115
1116 QQmlJSUtils::ResolvedAlias result =
1117 QQmlJSUtils::resolveAlias(typeResolver: m_typeResolver, property: alias, owner, visitor: aliasVisitor);
1118 Q_ASSERT(result.kind != QQmlJSUtils::AliasTarget_Invalid);
1119
1120 unpackFrames(frames);
1121
1122 if (result.kind == QQmlJSUtils::AliasTarget_Property) {
1123 // we don't need the last frame here
1124 frames.pop();
1125
1126 // instead, add a custom frame
1127 AliasResolutionFrame customFinalFrame {};
1128 auto [extensionPrologue, extensionAccessor, extensionEpilogue] =
1129 QmltcCodeGenerator::wrap_extensionType(
1130 type: result.owner, p: result.property,
1131 accessor: QmltcCodeGenerator::wrap_privateClass(accessor: frames.top().outVar,
1132 p: result.property));
1133 customFinalFrame.prologue = extensionPrologue;
1134 customFinalFrame.outVar = extensionAccessor;
1135 customFinalFrame.epilogue = extensionEpilogue;
1136 frames.push(t: customFinalFrame);
1137 }
1138
1139 const QString latestAccessor = frames.top().outVar;
1140 const QStringList prologue =
1141 joinFrames(frames, project: [](const AliasResolutionFrame &frame) { return frame.prologue; });
1142 const QStringList epilogue =
1143 joinFrames(frames, project: [](const AliasResolutionFrame &frame) { return frame.epilogue; });
1144 const QString underlyingType = (result.kind == QQmlJSUtils::AliasTarget_Property)
1145 ? getUnderlyingType(p: result.property)
1146 : result.owner->internalName() + u" *";
1147
1148 QStringList mocLines;
1149 mocLines.reserve(asize: 10);
1150 mocLines << underlyingType << aliasName;
1151
1152 QmltcPropertyData compilationData(aliasName);
1153 // 1. add setter and getter
1154 QmltcMethod getter {};
1155 getter.returnType = underlyingType;
1156 getter.name = compilationData.read;
1157 getter.body += prologue;
1158 if (result.kind == QQmlJSUtils::AliasTarget_Property) {
1159 if (QString read = result.property.read(); !read.isEmpty()
1160 && !QQmlJSUtils::bindablePropertyHasDefaultAccessor(
1161 p: result.property, accessor: QQmlJSUtils::PropertyAccessor_Read)) {
1162 getter.body << u"return %1->%2();"_s.arg(args: latestAccessor, args&: read);
1163 } else { // use QObject::property() as a fallback when read method is unknown
1164 getter.body << u"return qvariant_cast<%1>(%2->property(\"%3\"));"_s.arg(
1165 args: underlyingType, args: latestAccessor, args: result.property.propertyName());
1166 }
1167 } else { // AliasTarget_Object
1168 getter.body << u"return " + latestAccessor + u";";
1169 }
1170 getter.body += epilogue;
1171 getter.userVisible = true;
1172 current.functions.emplaceBack(args&: getter);
1173 mocLines << u"READ"_s << getter.name;
1174
1175 if (result.property.isWritable()) {
1176 Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
1177 QmltcMethod setter {};
1178 setter.returnType = u"void"_s;
1179 setter.name = compilationData.write;
1180
1181 const QString setName = result.property.write();
1182 QList<QQmlJSMetaMethod> methods = result.owner->methods(name: setName);
1183 if (methods.isEmpty()) { // when we are compiling the property as well
1184 // QmltcVariable
1185 setter.parameterList.emplaceBack(args: QQmlJSUtils::constRefify(type: underlyingType),
1186 args: aliasName + u"_", args: u""_s);
1187 } else {
1188 setter.parameterList = compileMethodParameters(parameterInfos: methods.at(i: 0).parameters(),
1189 /* allow unnamed = */ allowUnnamed: true);
1190 }
1191
1192 setter.body += prologue;
1193 QStringList parameterNames;
1194 parameterNames.reserve(asize: setter.parameterList.size());
1195 std::transform(first: setter.parameterList.cbegin(), last: setter.parameterList.cend(),
1196 result: std::back_inserter(x&: parameterNames),
1197 unary_op: [](const QmltcVariable &x) { return x.name; });
1198 QString commaSeparatedParameterNames = parameterNames.join(sep: u", "_s);
1199 if (!setName.isEmpty()
1200 && !QQmlJSUtils::bindablePropertyHasDefaultAccessor(
1201 p: result.property, accessor: QQmlJSUtils::PropertyAccessor_Write)) {
1202 setter.body << u"%1->%2(%3);"_s.arg(args: latestAccessor, args: setName,
1203 args&: commaSeparatedParameterNames);
1204 } else { // use QObject::setProperty() as fallback when write method is unknown
1205 Q_ASSERT(parameterNames.size() == 1);
1206 const QString variantName = u"var_" + aliasName; // fairly unique
1207 setter.body << u"QVariant %1;"_s.arg(a: variantName);
1208 setter.body << u"%1.setValue(%2);"_s.arg(args: variantName, args&: commaSeparatedParameterNames);
1209 setter.body << u"%1->setProperty(\"%2\", std::move(%3));"_s.arg(
1210 args: latestAccessor, args: result.property.propertyName(), args: variantName);
1211 }
1212 setter.body += joinFrames(
1213 frames, project: [](const AliasResolutionFrame &frame) { return frame.epilogueForWrite; });
1214 setter.body += epilogue; // NB: *after* epilogueForWrite - see prologue construction
1215 setter.userVisible = true;
1216 current.functions.emplaceBack(args&: setter);
1217 mocLines << u"WRITE"_s << setter.name;
1218 }
1219 // 2. add bindable
1220 if (QString bindableName = result.property.bindable(); !bindableName.isEmpty()) {
1221 Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
1222 QmltcMethod bindable {};
1223 bindable.returnType = u"QBindable<" + underlyingType + u">";
1224 bindable.name = compilationData.bindable;
1225 bindable.body += prologue;
1226 bindable.body << u"return " + latestAccessor + u"->" + bindableName + u"()" + u";";
1227 bindable.body += epilogue;
1228 bindable.userVisible = true;
1229 current.functions.emplaceBack(args&: bindable);
1230 mocLines << u"BINDABLE"_s << bindable.name;
1231 }
1232
1233 // 3. add notify - which is pretty special
1234 // step 1: generate the moc instructions
1235 // mimic the engines behavior: do it even if the notify will never be emitted
1236 if (const QString aliasNotifyName = alias.notify(); !aliasNotifyName.isEmpty()) {
1237
1238 Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
1239
1240 mocLines << u"NOTIFY"_s << aliasNotifyName;
1241 }
1242
1243 // step 2: connect the notifier to the aliased property notifier, if this latter exists
1244 // otherwise, mimic the engines behavior and generate a useless notify
1245 if (const QString notifyName = result.property.notify(); !notifyName.isEmpty()) {
1246 auto notifyFrames = frames;
1247 notifyFrames.pop(); // we don't need the last frame at all in this case
1248
1249 const QStringList notifyPrologue = joinFrames(
1250 frames, project: [](const AliasResolutionFrame &frame) { return frame.prologue; });
1251 const QStringList notifyEpilogue = joinFrames(
1252 frames, project: [](const AliasResolutionFrame &frame) { return frame.epilogue; });
1253
1254 // notify is very special
1255 current.endInit.body << u"{ // alias notify connection:"_s;
1256 current.endInit.body += notifyPrologue;
1257 // TODO: use non-private accessor since signals must exist on the public
1258 // type, not on the private one -- otherwise, you can't connect to a
1259 // private property signal in C++ and so it is useless (hence, use
1260 // public type)
1261 const QString cppType = (m_visitor->qmlComponentIndex(type: result.owner) == -1)
1262 ? result.owner->internalName()
1263 : u"QQmlComponent"_s;
1264 const QString latestAccessorNonPrivate = notifyFrames.top().outVar;
1265 current.endInit.body << u"QObject::connect(" + latestAccessorNonPrivate + u", &" + cppType
1266 + u"::" + notifyName + u", this, &" + current.cppType + u"::"
1267 + compilationData.notify + u");";
1268 current.endInit.body += notifyEpilogue;
1269 current.endInit.body << u"}"_s;
1270 }
1271
1272 if (QString resetName = result.property.reset(); !resetName.isEmpty()) {
1273 Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
1274 QmltcMethod reset {};
1275 reset.returnType = u"void"_s;
1276 reset.name = compilationData.reset;
1277 reset.body += prologue;
1278 reset.body << latestAccessor + u"->" + resetName + u"()" + u";";
1279 reset.body += epilogue;
1280 reset.userVisible = true;
1281 current.functions.emplaceBack(args&: reset);
1282 mocLines << u"RESET"_s << reset.name;
1283 }
1284
1285 // mimic the engines behavior: aliases are never constants
1286 // mocLines << u"CONSTANT"_s;
1287 // mimic the engines behavior: aliases are never stored
1288 mocLines << u"STORED"_s << u"false"_s;
1289 // mimic the engines behavior: aliases are never designable
1290 mocLines << u"DESIGNABLE"_s << u"false"_s;
1291
1292 // 4. add moc entry
1293 // Q_PROPERTY(QString text READ text WRITE setText BINDABLE bindableText NOTIFY textChanged)
1294 current.mocCode << u"Q_PROPERTY(" + mocLines.join(sep: u" "_s) + u")";
1295
1296 // 5. add extra moc entry if this alias is default one
1297 if (aliasName == owner->defaultPropertyName()) {
1298 // Q_CLASSINFO("DefaultProperty", propertyName)
1299 current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(a: aliasName);
1300 }
1301}
1302
1303static QString generate_callCompilationUnit(const QString &urlMethodName)
1304{
1305 return u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(%1())"_s.arg(a: urlMethodName);
1306}
1307
1308static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope,
1309 const QString &propertyName);
1310
1311/*!
1312 * \internal
1313 * Helper method used to keep compileBindingByType() readable.
1314 */
1315void QmltcCompiler::compileObjectBinding(QmltcType &current,
1316 const QQmlJSMetaPropertyBinding &binding,
1317 const QQmlJSScope::ConstPtr &type,
1318 const BindingAccessorData &accessor)
1319{
1320 Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Object);
1321
1322 const QString &propertyName = binding.propertyName();
1323 const QQmlJSMetaProperty property = type->property(name: propertyName);
1324 QQmlJSScope::ConstPtr propertyType = property.type();
1325
1326 // NB: object is compiled with compileType(), here just need to use it
1327 auto object = binding.objectType();
1328
1329 // Note: despite a binding being set for `accessor`, we use "this" as a
1330 // parent of a created object. Both attached and grouped properties are
1331 // parented by "this", so lifetime-wise we should be fine
1332 const QString qobjectParent = u"this"_s;
1333
1334 if (!propertyType) {
1335 recordError(location: binding.sourceLocation(),
1336 message: u"Binding on property '" + propertyName + u"' of unknown type");
1337 return;
1338 }
1339
1340 const auto addObjectBinding = [&](const QString &value) {
1341 if (qIsReferenceTypeList(p: property)) {
1342 Q_ASSERT(unprocessedListProperty == property || unprocessedListBindings.empty());
1343 unprocessedListBindings.append(t: value);
1344 unprocessedListProperty = property;
1345 } else {
1346 QmltcCodeGenerator::generate_assignToProperty(block: &current.endInit.body, type, p: property,
1347 value, accessor: accessor.name, constructFromQObject: true);
1348 }
1349 };
1350
1351 // special case of implicit or explicit component:
1352 if (qsizetype index = m_visitor->qmlComponentIndex(type: object); index >= 0) {
1353 const QString objectName = newSymbol(base: u"sc"_s);
1354
1355 const qsizetype creationIndex = m_visitor->creationIndex(type: object);
1356
1357 QStringList *block = (creationIndex == -1) ? &current.endInit.body : &current.init.body;
1358 *block << u"{"_s;
1359 *block << QStringLiteral("auto thisContext = QQmlData::get(%1)->outerContext;")
1360 .arg(a: qobjectParent);
1361 *block << QStringLiteral("auto %1 = QQmlObjectCreator::createComponent(engine, "
1362 "%2, %3, %4, thisContext);")
1363 .arg(args: objectName, args: generate_callCompilationUnit(urlMethodName: m_urlMethodName),
1364 args: QString::number(index), args: qobjectParent);
1365 *block << QStringLiteral("thisContext->installContext(QQmlData::get(%1), "
1366 "QQmlContextData::OrdinaryObject);")
1367 .arg(a: objectName);
1368
1369 // objects wrapped in implicit components do not have visible ids,
1370 // however, explicit components can have an id and that one is going
1371 // to be visible in the common document context
1372 if (creationIndex != -1) {
1373 // explicit component
1374 Q_ASSERT(object->isComposite());
1375 Q_ASSERT(object->baseType()->internalName() == u"QQmlComponent"_s);
1376
1377 if (int id = m_visitor->runtimeId(type: object); id >= 0) {
1378 QString idString = m_visitor->addressableScopes().id(scope: object, referrer: object);
1379 if (idString.isEmpty())
1380 idString = u"<unknown>"_s;
1381 QmltcCodeGenerator::generate_setIdValue(block, context: u"thisContext"_s, index: id, accessor: objectName,
1382 idString);
1383 }
1384
1385 const QString creationIndexStr = QString::number(creationIndex);
1386 *block << QStringLiteral("creator->set(%1, %2);").arg(args: creationIndexStr, args: objectName);
1387 Q_ASSERT(block == &current.init.body);
1388 current.endInit.body << QStringLiteral("auto %1 = creator->get<%2>(%3);")
1389 .arg(args: objectName, args: u"QQmlComponent"_s, args: creationIndexStr);
1390 }
1391 addObjectBinding(objectName);
1392 *block << u"}"_s;
1393 return;
1394 }
1395
1396 const QString objectName = newSymbol(base: u"o"_s);
1397 current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg(
1398 args: objectName, args: object->internalName(), args: qobjectParent);
1399 current.init.body << u"creator->set(%1, %2);"_s.arg(
1400 args: QString::number(m_visitor->creationIndex(type: object)), args: objectName);
1401
1402 // refetch the same object during endInit to set the bindings
1403 current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg(
1404 args: objectName, args: object->internalName(), args: QString::number(m_visitor->creationIndex(type: object)));
1405 addObjectBinding(objectName);
1406}
1407
1408/*!
1409 * \internal
1410 * Helper method used to keep compileBindingByType() readable.
1411 */
1412void QmltcCompiler::compileValueSourceOrInterceptorBinding(QmltcType &current,
1413 const QQmlJSMetaPropertyBinding &binding,
1414 const QQmlJSScope::ConstPtr &type,
1415 const BindingAccessorData &accessor)
1416{
1417 Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::ValueSource
1418 || binding.bindingType() == QQmlSA::BindingType::Interceptor);
1419
1420 const QString &propertyName = binding.propertyName();
1421 const QQmlJSMetaProperty property = type->property(name: propertyName);
1422 QQmlJSScope::ConstPtr propertyType = property.type();
1423
1424 // NB: object is compiled with compileType(), here just need to use it
1425 QSharedPointer<const QQmlJSScope> object;
1426 if (binding.bindingType() == QQmlSA::BindingType::Interceptor)
1427 object = binding.interceptorType();
1428 else
1429 object = binding.valueSourceType();
1430
1431 // Note: despite a binding being set for `accessor`, we use "this" as a
1432 // parent of a created object. Both attached and grouped properties are
1433 // parented by "this", so lifetime-wise we should be fine
1434 const QString qobjectParent = u"this"_s;
1435
1436 if (!propertyType) {
1437 recordError(location: binding.sourceLocation(),
1438 message: u"Binding on property '" + propertyName + u"' of unknown type");
1439 return;
1440 }
1441
1442 auto &objectName = m_uniques[UniqueStringId(current, propertyName)].onAssignmentObjectName;
1443 if (objectName.isEmpty()) {
1444 objectName = u"onAssign_" + propertyName;
1445
1446 current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg(
1447 args&: objectName, args: object->internalName(), args: qobjectParent);
1448 current.init.body << u"creator->set(%1, %2);"_s.arg(
1449 args: QString::number(m_visitor->creationIndex(type: object)), args&: objectName);
1450
1451 current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg(
1452 args&: objectName, args: object->internalName(),
1453 args: QString::number(m_visitor->creationIndex(type: object)));
1454 }
1455
1456 // NB: we expect one "on" assignment per property, so creating
1457 // QQmlProperty each time should be fine (unlike QQmlListReference)
1458 current.endInit.body << u"{"_s;
1459 current.endInit.body << u"QQmlProperty qmlprop(%1, %2);"_s.arg(
1460 args: accessor.name, args: QQmlJSUtils::toLiteral(s: propertyName));
1461 current.endInit.body << u"QT_PREPEND_NAMESPACE(QQmlCppOnAssignmentHelper)::set(%1, qmlprop);"_s
1462 .arg(a: objectName);
1463 current.endInit.body << u"}"_s;
1464}
1465
1466/*!
1467 * \internal
1468 * Helper method used to keep compileBindingByType() readable.
1469 */
1470void QmltcCompiler::compileAttachedPropertyBinding(QmltcType &current,
1471 const QQmlJSMetaPropertyBinding &binding,
1472 const QQmlJSScope::ConstPtr &type,
1473 const BindingAccessorData &accessor)
1474{
1475 Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::AttachedProperty);
1476
1477 const QString &propertyName = binding.propertyName();
1478 const QQmlJSMetaProperty property = type->property(name: propertyName);
1479 QQmlJSScope::ConstPtr propertyType = property.type();
1480
1481 Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact
1482 const auto attachedType = binding.attachingType();
1483 Q_ASSERT(attachedType);
1484
1485 const QString attachingTypeName = propertyName; // acts as an identifier
1486 auto attachingType = m_typeResolver->typeForName(name: attachingTypeName);
1487
1488 QString attachedTypeName = attachedType->baseTypeName();
1489 Q_ASSERT(!attachedTypeName.isEmpty());
1490
1491 auto &attachedMemberName =
1492 m_uniques[UniqueStringId(current, propertyName)].attachedVariableName;
1493 if (attachedMemberName.isEmpty()) {
1494 attachedMemberName = uniqueVariableName(qmlName: attachingTypeName);
1495
1496 // add attached type as a member variable to allow noop lookup
1497 current.variables.emplaceBack(args: attachedTypeName + u" *", args&: attachedMemberName, args: u"nullptr"_s);
1498
1499 if (propertyName == u"Component"_s) { // Component attached type is special
1500 current.endInit.body << u"Q_ASSERT(qmlEngine(this));"_s;
1501 current.endInit.body
1502 << u"// attached Component must be added to the object's QQmlData"_s;
1503 current.endInit.body
1504 << u"Q_ASSERT(!QQmlEnginePrivate::get(qmlEngine(this))->activeObjectCreator);"_s;
1505 }
1506
1507 // Note: getting attached property is fairly expensive
1508 const QString getAttachedPropertyLine = u"qobject_cast<" + attachedTypeName
1509 + u" *>(qmlAttachedPropertiesObject<" + attachingType->internalName()
1510 + u">(this, /* create = */ true))";
1511 current.endInit.body << attachedMemberName + u" = " + getAttachedPropertyLine + u";";
1512
1513 if (propertyName == u"Component"_s) {
1514 // call completed/destruction signals appropriately
1515 current.handleOnCompleted.body << u"Q_EMIT " + attachedMemberName + u"->completed();";
1516 if (!current.dtor) {
1517 current.dtor = QmltcDtor{};
1518 current.dtor->name = u"~" + current.cppType;
1519 }
1520 current.dtor->body << u"Q_EMIT " + attachedMemberName + u"->destruction();";
1521 }
1522 }
1523
1524 auto subbindings = attachedType->ownPropertyBindingsInQmlIROrder();
1525 // compile bindings of the attached property
1526 partitionBindings(first: subbindings.begin(), last: subbindings.end());
1527 compileBinding(current, bindingStart: subbindings.begin(), bindingEnd: subbindings.end(), type: attachedType,
1528 accessor: { .scope: type, .name: attachedMemberName, .propertyName: propertyName, .isValueType: false });
1529}
1530
1531/*!
1532 * \internal
1533 * Helper method used to keep compileBindingByType() readable.
1534 */
1535void QmltcCompiler::compileGroupPropertyBinding(QmltcType &current,
1536 const QQmlJSMetaPropertyBinding &binding,
1537 const QQmlJSScope::ConstPtr &type,
1538 const BindingAccessorData &accessor)
1539{
1540 Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::GroupProperty);
1541
1542 const QString &propertyName = binding.propertyName();
1543 const QQmlJSMetaProperty property = type->property(name: propertyName);
1544 QQmlJSScope::ConstPtr propertyType = property.type();
1545
1546 Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact
1547 if (property.read().isEmpty()) {
1548 recordError(location: binding.sourceLocation(),
1549 message: u"READ function of group property '" + propertyName + u"' is unknown");
1550 return;
1551 }
1552
1553 auto groupType = binding.groupType();
1554 Q_ASSERT(groupType);
1555
1556 const bool isValueType = propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Value;
1557 if (!isValueType
1558 && propertyType->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
1559 recordError(location: binding.sourceLocation(),
1560 message: u"Group property '" + propertyName + u"' has unsupported access semantics");
1561 return;
1562 }
1563
1564 auto subbindings = groupType->ownPropertyBindingsInQmlIROrder();
1565 auto firstScript = partitionBindings(first: subbindings.begin(), last: subbindings.end());
1566
1567 // if we have no non-script bindings, we have no bindings that affect
1568 // the value type group, so no reason to generate the wrapping code
1569 const bool generateValueTypeCode = isValueType && (subbindings.begin() != firstScript);
1570
1571 QString groupAccessor = QmltcCodeGenerator::wrap_privateClass(accessor: accessor.name, p: property) + u"->"
1572 + property.read() + u"()";
1573 // NB: used when isValueType == true
1574 const QString groupPropertyVarName = accessor.name + u"_group_" + propertyName;
1575 // value types are special
1576 if (generateValueTypeCode) {
1577 if (property.write().isEmpty()) { // just reject this
1578 recordError(location: binding.sourceLocation(),
1579 message: u"Group property '" + propertyName + u"' is a value type without a setter");
1580 return;
1581 }
1582
1583 current.endInit.body << u"auto " + groupPropertyVarName + u" = " + groupAccessor + u";";
1584 // addressof operator is to make the binding logic work, which
1585 // expects that `accessor.name` is a pointer type
1586 groupAccessor = QmltcCodeGenerator::wrap_addressof(addressed: groupPropertyVarName);
1587 }
1588
1589 // compile bindings of the grouped property
1590 const auto compile = [&](const auto &bStart, const auto &bEnd) {
1591 compileBinding(current, bindingStart: bStart, bindingEnd: bEnd, type: groupType,
1592 accessor: { type, groupAccessor, propertyName, isValueType });
1593 };
1594
1595 auto it = subbindings.begin();
1596 Q_ASSERT(std::all_of(it, firstScript, [](const auto &x) {
1597 return x.bindingType() != QQmlSA::BindingType::Script;
1598 }));
1599 compile(it, firstScript);
1600 it = firstScript;
1601
1602 // NB: script bindings are special on group properties. if our group is
1603 // a value type, the binding would be installed on the *object* that
1604 // holds the value type and not on the value type itself. this may cause
1605 // subtle side issues (esp. when script binding is actually a simple
1606 // enum value assignment - which is not recognized specially):
1607 //
1608 // auto valueTypeGroupProperty = getCopy();
1609 // installBinding(valueTypeGroupProperty, "subproperty1"); // changes subproperty1 value
1610 // setCopy(valueTypeGroupProperty); // oops, subproperty1 value changed to old again
1611 if (generateValueTypeCode) { // write the value type back
1612 current.endInit.body << QmltcCodeGenerator::wrap_privateClass(accessor: accessor.name, p: property)
1613 + u"->" + property.write() + u"(" + groupPropertyVarName + u");";
1614 }
1615
1616 // once the value is written back, process the script bindings
1617 Q_ASSERT(std::all_of(it, subbindings.end(), [](const auto &x) {
1618 return x.bindingType() == QQmlSA::BindingType::Script;
1619 }));
1620 compile(it, subbindings.end());
1621}
1622
1623/*!
1624 * \internal
1625 * Helper method used to keep compileBindingByType() readable.
1626 */
1627void QmltcCompiler::compileTranslationBinding(QmltcType &current,
1628 const QQmlJSMetaPropertyBinding &binding,
1629 const QQmlJSScope::ConstPtr &type,
1630 const BindingAccessorData &accessor)
1631{
1632 Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Translation
1633 || binding.bindingType() == QQmlSA::BindingType::TranslationById);
1634
1635 const QString &propertyName = binding.propertyName();
1636
1637 auto [property, absoluteIndex] = getMetaPropertyIndex(scope: type, propertyName);
1638
1639 if (absoluteIndex < 0) {
1640 recordError(location: binding.sourceLocation(),
1641 message: u"Binding on unknown property '" + propertyName + u"'");
1642 return;
1643 }
1644
1645 QString bindingTarget = accessor.name;
1646
1647 int valueTypeIndex = -1;
1648 if (accessor.isValueType) {
1649 Q_ASSERT(accessor.scope != type);
1650 bindingTarget = u"this"_s; // TODO: not necessarily "this"?
1651 auto [groupProperty, groupPropertyIndex] =
1652 getMetaPropertyIndex(scope: accessor.scope, propertyName: accessor.propertyName);
1653 if (groupPropertyIndex < 0) {
1654 recordError(location: binding.sourceLocation(),
1655 message: u"Binding on group property '" + accessor.propertyName
1656 + u"' of unknown type");
1657 return;
1658 }
1659 valueTypeIndex = absoluteIndex;
1660 absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name
1661 }
1662
1663 QmltcCodeGenerator::TranslationBindingInfo info;
1664 info.unitVarName = generate_callCompilationUnit(urlMethodName: m_urlMethodName);
1665 info.scope = u"this"_s;
1666 info.target = u"this"_s;
1667 info.propertyIndex = absoluteIndex;
1668 info.property = property;
1669 info.data = binding.translationDataValue(qmlFileNameForContext: m_url);
1670 info.valueTypeIndex = valueTypeIndex;
1671 info.line = binding.sourceLocation().startLine;
1672 info.column = binding.sourceLocation().startColumn;
1673
1674 QmltcCodeGenerator::generate_createTranslationBindingOnProperty(block: &current.endInit.body, info);
1675}
1676
1677void QmltcCompiler::processLastListBindings(QmltcType &current, const QQmlJSScope::ConstPtr &type,
1678 const BindingAccessorData &accessor)
1679{
1680 if (unprocessedListBindings.empty())
1681 return;
1682
1683 QmltcCodeGenerator::generate_assignToListProperty(
1684 block: &current.endInit.body, type, p: unprocessedListProperty, value: unprocessedListBindings,
1685 accessor: accessor.name,
1686 qmlListVarName&: m_uniques[UniqueStringId(current, unprocessedListProperty.propertyName())]
1687 .qmlListVariableName);
1688
1689 unprocessedListBindings.clear();
1690}
1691
1692void QmltcCompiler::compileBinding(QmltcType &current,
1693 QList<QQmlJSMetaPropertyBinding>::iterator bindingStart,
1694 QList<QQmlJSMetaPropertyBinding>::iterator bindingEnd,
1695 const QQmlJSScope::ConstPtr &type,
1696 const BindingAccessorData &accessor)
1697{
1698 for (auto it = bindingStart; it != bindingEnd; it++) {
1699 const QQmlJSMetaPropertyBinding &binding = *it;
1700 const QString &propertyName = binding.propertyName();
1701 Q_ASSERT(!propertyName.isEmpty());
1702
1703 // Note: unlike QQmlObjectCreator, we don't have to do a complicated
1704 // deferral logic for bindings: if a binding is deferred, it is not compiled
1705 // (potentially, with all the bindings inside of it), period.
1706 if (type->isNameDeferred(name: propertyName)) {
1707 const auto location = binding.sourceLocation();
1708 // make sure group property is not generalized by checking if type really has a property
1709 // called propertyName. If not, it is probably an id.
1710 if (binding.bindingType() == QQmlSA::BindingType::GroupProperty
1711 && type->hasProperty(name: propertyName)) {
1712 qCWarning(lcQmltcCompiler)
1713 << QStringLiteral("Binding at line %1 column %2 is not deferred as it is a "
1714 "binding on a group property.")
1715 .arg(args: QString::number(location.startLine),
1716 args: QString::number(location.startColumn));
1717 // we do not support PropertyChanges and other types with similar
1718 // behavior yet, so this binding is compiled
1719 } else {
1720 qCDebug(lcQmltcCompiler)
1721 << QStringLiteral(
1722 "Binding at line %1 column %2 is deferred and thus not compiled")
1723 .arg(args: QString::number(location.startLine),
1724 args: QString::number(location.startColumn));
1725 continue;
1726 }
1727 }
1728
1729 const QQmlJSMetaProperty metaProperty = type->property(name: propertyName);
1730 const QQmlJSScope::ConstPtr propertyType = metaProperty.type();
1731
1732 if (!(qIsReferenceTypeList(p: metaProperty) && unprocessedListProperty == metaProperty)) {
1733 processLastListBindings(current, type, accessor);
1734 }
1735
1736 compileBindingByType(current, binding, type, accessor);
1737 }
1738
1739 processLastListBindings(current, type, accessor);
1740}
1741
1742void QmltcCompiler::compileBindingByType(QmltcType &current,
1743 const QQmlJSMetaPropertyBinding &binding,
1744 const QQmlJSScope::ConstPtr &type,
1745 const BindingAccessorData &accessor)
1746{
1747 const QString &propertyName = binding.propertyName();
1748 const QQmlJSMetaProperty metaProperty = type->property(name: propertyName);
1749 const QQmlJSScope::ConstPtr propertyType = metaProperty.type();
1750
1751 const auto assignToProperty = [&](const QQmlJSMetaProperty &p, const QString &value,
1752 bool constructFromQObject = false) {
1753 QmltcCodeGenerator::generate_assignToProperty(block: &current.endInit.body, type, p, value,
1754 accessor: accessor.name, constructFromQObject);
1755 };
1756 switch (binding.bindingType()) {
1757 case QQmlSA::BindingType::BoolLiteral: {
1758 const bool value = binding.boolValue();
1759 assignToProperty(metaProperty, value ? u"true"_s : u"false"_s);
1760 break;
1761 }
1762 case QQmlSA::BindingType::NumberLiteral: {
1763 assignToProperty(metaProperty, QString::number(binding.numberValue()));
1764 break;
1765 }
1766 case QQmlSA::BindingType::StringLiteral: {
1767 QString value = QQmlJSUtils::toLiteral(s: binding.stringValue());
1768 if (auto type = metaProperty.type()) {
1769 if (type->internalName() == u"QUrl"_s) {
1770 value = u"QUrl(%1)"_s.arg(a: value);
1771 }
1772 }
1773 assignToProperty(metaProperty, value);
1774 break;
1775 }
1776 case QQmlSA::BindingType::RegExpLiteral: {
1777 const QString value =
1778 u"QRegularExpression(%1)"_s.arg(a: QQmlJSUtils::toLiteral(s: binding.regExpValue()));
1779 assignToProperty(metaProperty, value);
1780 break;
1781 }
1782 case QQmlSA::BindingType::Null: {
1783 // poor check: null bindings are only supported for var and objects
1784 Q_ASSERT(propertyType->isSameType(m_typeResolver->varType())
1785 || propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference);
1786 if (propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
1787 assignToProperty(metaProperty, u"nullptr"_s);
1788 else
1789 assignToProperty(metaProperty, u"QVariant::fromValue(nullptr)"_s);
1790 break;
1791 }
1792 case QQmlSA::BindingType::Script: {
1793 QString bindingSymbolName
1794 = uniqueVariableName(qmlName: type->internalName() + u'_' + propertyName + u"_binding");
1795 compileScriptBinding(current, binding, bindingSymbolName, type, propertyName, propertyType,
1796 accessor);
1797 break;
1798 }
1799 case QQmlSA::BindingType::Object: {
1800 compileObjectBinding(current, binding, type, accessor);
1801 break;
1802 }
1803 case QQmlSA::BindingType::Interceptor:
1804 Q_FALLTHROUGH();
1805 case QQmlSA::BindingType::ValueSource: {
1806 compileValueSourceOrInterceptorBinding(current, binding, type, accessor);
1807 break;
1808 }
1809 case QQmlSA::BindingType::AttachedProperty: {
1810 compileAttachedPropertyBinding(current, binding, type, accessor);
1811 break;
1812 }
1813 case QQmlSA::BindingType::GroupProperty: {
1814 compileGroupPropertyBinding(current, binding, type, accessor);
1815 break;
1816 }
1817
1818 case QQmlSA::BindingType::TranslationById:
1819 case QQmlSA::BindingType::Translation: {
1820 compileTranslationBinding(current, binding, type, accessor);
1821 break;
1822 }
1823 case QQmlSA::BindingType::Invalid: {
1824 recordError(location: binding.sourceLocation(), message: u"This binding is invalid"_s);
1825 break;
1826 }
1827 default: {
1828 recordError(location: binding.sourceLocation(), message: u"Binding is not supported"_s);
1829 break;
1830 }
1831 }
1832}
1833
1834// returns compiled script binding for "property changed" handler in a form of object type
1835static QmltcType compileScriptBindingPropertyChangeHandler(const QQmlJSMetaPropertyBinding &binding,
1836 const QQmlJSScope::ConstPtr &objectType,
1837 const QString &urlMethodName,
1838 const QString &functorCppType,
1839 const QString &objectCppType)
1840{
1841 QmltcType bindingFunctor {};
1842 bindingFunctor.cppType = functorCppType;
1843 bindingFunctor.ignoreInit = true;
1844
1845 // default member variable and ctor:
1846 const QString pointerToObject = objectCppType + u" *";
1847 bindingFunctor.variables.emplaceBack(
1848 args: QmltcVariable { pointerToObject, u"m_self"_s, u"nullptr"_s });
1849 bindingFunctor.baselineCtor.name = functorCppType;
1850 bindingFunctor.baselineCtor.parameterList.emplaceBack(
1851 args: QmltcVariable { pointerToObject, u"self"_s, QString() });
1852 bindingFunctor.baselineCtor.initializerList.emplaceBack(args: u"m_self(self)"_s);
1853
1854 // call operator:
1855 QmltcMethod callOperator {};
1856 callOperator.returnType = u"void"_s;
1857 callOperator.name = u"operator()"_s;
1858 callOperator.modifiers << u"const"_s;
1859 QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
1860 block: &callOperator.body, url: urlMethodName + u"()",
1861 index: objectType->ownRuntimeFunctionIndex(index: binding.scriptIndex()), accessor: u"m_self"_s, returnType: u"void"_s, parameters: {});
1862
1863 bindingFunctor.functions.emplaceBack(args: std::move(callOperator));
1864
1865 return bindingFunctor;
1866}
1867
1868// finds property for given scope and returns it together with the absolute
1869// property index in the property array of the corresponding QMetaObject
1870static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope,
1871 const QString &propertyName)
1872{
1873 auto owner = QQmlJSScope::ownerOfProperty(self: scope, name: propertyName).scope;
1874 Q_ASSERT(owner);
1875 const QQmlJSMetaProperty p = owner->ownProperty(name: propertyName);
1876 if (!p.isValid())
1877 return { p, -1 };
1878 int index = p.index();
1879 if (index < 0) // this property doesn't have index - comes from QML
1880 return { p, -1 };
1881
1882 const auto increment = [&](const QQmlJSScope::ConstPtr &type, QQmlJSScope::ExtensionKind m) {
1883 // owner of property is not included in the offset calculation (relative
1884 // index is already added as p.index())
1885 if (type->isSameType(otherScope: owner))
1886 return;
1887
1888 // extension namespace and JavaScript properties are ignored
1889 if (m == QQmlJSScope::ExtensionNamespace || m == QQmlJSScope::ExtensionJavaScript)
1890 return;
1891
1892 index += int(type->ownProperties().size());
1893 };
1894 QQmlJSUtils::traverseFollowingMetaObjectHierarchy(scope, start: owner, act: increment);
1895 return { p, index };
1896}
1897
1898void QmltcCompiler::compileScriptBinding(QmltcType &current,
1899 const QQmlJSMetaPropertyBinding &binding,
1900 const QString &bindingSymbolName,
1901 const QQmlJSScope::ConstPtr &objectType,
1902 const QString &propertyName,
1903 const QQmlJSScope::ConstPtr &propertyType,
1904 const QmltcCompiler::BindingAccessorData &accessor)
1905{
1906 const auto compileScriptSignal = [&](const QString &name) {
1907 QString This_signal = u"this"_s;
1908 QString This_slot = u"this"_s;
1909 QString objectClassName_signal = objectType->internalName();
1910 QString objectClassName_slot = objectType->internalName();
1911
1912 // TODO: ugly crutch to make stuff work
1913 if (accessor.name != u"this"_s) { // e.g. if attached property
1914 This_signal = accessor.name;
1915 This_slot = u"this"_s; // still
1916 objectClassName_signal = objectType->baseTypeName();
1917 objectClassName_slot = current.cppType; // real base class where slot would go
1918 }
1919 Q_ASSERT(!objectClassName_signal.isEmpty());
1920 Q_ASSERT(!objectClassName_slot.isEmpty());
1921
1922 const auto signalMethods = objectType->methods(name, type: QQmlJSMetaMethodType::Signal);
1923 Q_ASSERT(!signalMethods.isEmpty()); // an error somewhere else
1924 QQmlJSMetaMethod signal = signalMethods.at(i: 0);
1925 Q_ASSERT(signal.methodType() == QQmlJSMetaMethodType::Signal);
1926
1927 const QString signalName = signal.methodName();
1928 const QString slotName = newSymbol(base: signalName + u"_slot");
1929
1930 const QString signalReturnType = figureReturnType(m: signal);
1931 const QList<QmltcVariable> slotParameters =
1932 compileMethodParameters(parameterInfos: signal.parameters(), /* allow unnamed = */ allowUnnamed: true);
1933
1934 // SignalHander specific:
1935 QmltcMethod slotMethod {};
1936 slotMethod.returnType = signalReturnType;
1937 slotMethod.name = slotName;
1938 slotMethod.parameterList = slotParameters;
1939
1940 QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
1941 block: &slotMethod.body, url: m_urlMethodName + u"()",
1942 index: objectType->ownRuntimeFunctionIndex(index: binding.scriptIndex()),
1943 accessor: u"this"_s, // Note: because script bindings always use current QML object scope
1944 returnType: signalReturnType, parameters: slotParameters);
1945 slotMethod.type = QQmlJSMetaMethodType::Slot;
1946
1947 current.functions << std::move(slotMethod);
1948 current.setComplexBindings.body << u"QObject::connect(" + This_signal + u", " + u"&"
1949 + objectClassName_signal + u"::" + signalName + u", " + This_slot + u", &"
1950 + objectClassName_slot + u"::" + slotName + u");";
1951 };
1952
1953 switch (binding.scriptKind()) {
1954 case QQmlSA::ScriptBindingKind::PropertyBinding: {
1955 if (!propertyType) {
1956 recordError(location: binding.sourceLocation(),
1957 message: u"Binding on property '" + propertyName + u"' of unknown type");
1958 return;
1959 }
1960
1961 auto [property, absoluteIndex] = getMetaPropertyIndex(scope: objectType, propertyName);
1962 if (absoluteIndex < 0) {
1963 recordError(location: binding.sourceLocation(),
1964 message: u"Binding on unknown property '" + propertyName + u"'");
1965 return;
1966 }
1967
1968 QString bindingTarget = accessor.name;
1969
1970 int valueTypeIndex = -1;
1971 if (accessor.isValueType) {
1972 Q_ASSERT(accessor.scope != objectType);
1973 bindingTarget = u"this"_s; // TODO: not necessarily "this"?
1974 auto [groupProperty, groupPropertyIndex] =
1975 getMetaPropertyIndex(scope: accessor.scope, propertyName: accessor.propertyName);
1976 if (groupPropertyIndex < 0) {
1977 recordError(location: binding.sourceLocation(),
1978 message: u"Binding on group property '" + accessor.propertyName
1979 + u"' of unknown type");
1980 return;
1981 }
1982 valueTypeIndex = absoluteIndex;
1983 absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name
1984 }
1985
1986 QmltcCodeGenerator::generate_createBindingOnProperty(
1987 block: &current.setComplexBindings.body, unitVarName: generate_callCompilationUnit(urlMethodName: m_urlMethodName),
1988 scope: u"this"_s, // NB: always using enclosing object as a scope for the binding
1989 functionIndex: static_cast<qsizetype>(objectType->ownRuntimeFunctionIndex(index: binding.scriptIndex())),
1990 target: bindingTarget, // binding target
1991 // value types are special and are bound through valueTypeIndex
1992 targetType: accessor.isValueType ? QQmlJSScope::ConstPtr() : objectType, propertyIndex: absoluteIndex,
1993 p: property, valueTypeIndex, subTarget: accessor.name);
1994 break;
1995 }
1996 case QQmlSA::ScriptBindingKind::SignalHandler: {
1997 const auto name = QQmlSignalNames::handlerNameToSignalName(handler: propertyName);
1998 Q_ASSERT(name.has_value());
1999 compileScriptSignal(*name);
2000 break;
2001 }
2002 case QQmlSA ::ScriptBindingKind::ChangeHandler: {
2003 const QString objectClassName = objectType->internalName();
2004 const QString bindingFunctorName = newSymbol(base: bindingSymbolName + u"Functor");
2005
2006 const auto signalName = QQmlSignalNames::handlerNameToSignalName(handler: propertyName);
2007 Q_ASSERT(signalName.has_value()); // an error somewhere else
2008 const auto actualProperty =
2009 QQmlJSUtils::propertyFromChangedHandler(scope: objectType, changedHandler: propertyName);
2010 Q_ASSERT(actualProperty.has_value()); // an error somewhere else
2011 const auto actualPropertyType = actualProperty->type();
2012 if (!actualPropertyType) {
2013 recordError(location: binding.sourceLocation(),
2014 message: u"Binding on property '" + actualProperty->propertyName()
2015 + u"' of unknown type");
2016 return;
2017 }
2018
2019 // due to historical reasons (QQmlObjectCreator), prefer NOTIFY over
2020 // BINDABLE when both are available. thus, test for notify first
2021 const QString notifyString = actualProperty->notify();
2022 if (!notifyString.isEmpty()) {
2023 compileScriptSignal(notifyString);
2024 break;
2025 }
2026 const QString bindableString = actualProperty->bindable();
2027 QString typeOfQmlBinding =
2028 u"std::unique_ptr<QPropertyChangeHandler<" + bindingFunctorName + u">>";
2029
2030 current.children << compileScriptBindingPropertyChangeHandler(
2031 binding, objectType, urlMethodName: m_urlMethodName, functorCppType: bindingFunctorName, objectCppType: objectClassName);
2032
2033 current.setComplexBindings.body << u"if (!%1.contains(QStringLiteral(\"%2\")))"_s.arg(
2034 args&: current.propertyInitializer.initializedCache.name, args: propertyName);
2035
2036 // TODO: this could be dropped if QQmlEngine::setContextForObject() is
2037 // done before currently generated C++ object is constructed
2038 current.setComplexBindings.body << u" "_s + bindingSymbolName + u".reset(new QPropertyChangeHandler<"
2039 + bindingFunctorName + u">("
2040 + QmltcCodeGenerator::wrap_privateClass(accessor: accessor.name, p: *actualProperty)
2041 + u"->" + bindableString + u"().onValueChanged(" + bindingFunctorName + u"("
2042 + accessor.name + u"))));";
2043
2044 current.variables.emplaceBack(
2045 args: QmltcVariable { typeOfQmlBinding, bindingSymbolName, QString() });
2046 break;
2047 }
2048 default:
2049 recordError(location: binding.sourceLocation(), message: u"Invalid script binding found"_s);
2050 break;
2051 }
2052}
2053
2054QT_END_NAMESPACE
2055

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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