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