1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #ifndef QMLTCCOMPILERPIECES_H |
5 | #define QMLTCCOMPILERPIECES_H |
6 | |
7 | #include <QtCore/qscopeguard.h> |
8 | #include <QtCore/qstringbuilder.h> |
9 | #include <QtCore/qfileinfo.h> |
10 | |
11 | #include <private/qqmljsutils_p.h> |
12 | #include <private/qqmlglobal_p.h> |
13 | #include <private/qqmltranslation_p.h> |
14 | |
15 | #include "qmltcoutputir.h" |
16 | #include "qmltcvisitor.h" |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | /*! |
21 | \internal |
22 | |
23 | Helper class that generates code for the output IR. Takes care of |
24 | complicated, repetitive, nasty logic which is better kept in a single |
25 | confined place. |
26 | */ |
27 | struct QmltcCodeGenerator |
28 | { |
29 | static const QString privateEngineName; |
30 | static const QString typeCountName; |
31 | |
32 | QString documentUrl; |
33 | QmltcVisitor *visitor = nullptr; |
34 | |
35 | using InlineComponentOrDocumentRootName = QQmlJSScope::InlineComponentOrDocumentRootName; |
36 | using RootDocumentNameType = QQmlJSScope::RootDocumentNameType; |
37 | |
38 | [[nodiscard]] inline decltype(auto) generate_initCode(QmltcType ¤t, |
39 | const QQmlJSScope::ConstPtr &type) const; |
40 | inline void generate_initCodeForTopLevelComponent(QmltcType ¤t, |
41 | const QQmlJSScope::ConstPtr &type); |
42 | |
43 | inline void generate_qmltcInstructionCallCode(QmltcMethod *function, |
44 | const QQmlJSScope::ConstPtr &type, |
45 | const QString &baseInstructionArgs, |
46 | const QString &childInstructionArgs) const; |
47 | inline void generate_endInitCode(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) const; |
48 | inline void generate_setComplexBindingsCode(QmltcType ¤t, |
49 | const QQmlJSScope::ConstPtr &type) const; |
50 | |
51 | inline void generate_interfaceCallCode(QmltcMethod *function, const QQmlJSScope::ConstPtr &type, |
52 | const QString &interfaceName, |
53 | const QString &interfaceCall) const; |
54 | inline void generate_beginClassCode(QmltcType ¤t, |
55 | const QQmlJSScope::ConstPtr &type) const; |
56 | inline void generate_completeComponentCode(QmltcType ¤t, |
57 | const QQmlJSScope::ConstPtr &type) const; |
58 | inline void generate_finalizeComponentCode(QmltcType ¤t, |
59 | const QQmlJSScope::ConstPtr &type) const; |
60 | inline void generate_handleOnCompletedCode(QmltcType ¤t, |
61 | const QQmlJSScope::ConstPtr &type) const; |
62 | |
63 | static void generate_assignToProperty(QStringList *block, const QQmlJSScope::ConstPtr &type, |
64 | const QQmlJSMetaProperty &p, const QString &value, |
65 | const QString &accessor, |
66 | bool constructFromQObject = false); |
67 | |
68 | static void generate_assignToListProperty(QStringList *block, const QQmlJSScope::ConstPtr &type, |
69 | const QQmlJSMetaProperty &p, const QStringList &value, |
70 | const QString &accessor, QString &qmlListVarName); |
71 | |
72 | static void generate_setIdValue(QStringList *block, const QString &context, qsizetype index, |
73 | const QString &accessor, const QString &idString); |
74 | |
75 | inline QString |
76 | generate_typeCount(const InlineComponentOrDocumentRootName &inlinedComponent) const |
77 | { |
78 | return generate_typeCount(p: [](const QQmlJSScope::ConstPtr &) { return false; }, |
79 | inlinedComponent); |
80 | } |
81 | |
82 | /*! |
83 | * \internal |
84 | * Generate the constexpr typeCount expression for given inlinedComponent. Leave |
85 | * inlinedComponent empty to generate the expression for the main component. |
86 | */ |
87 | template<typename Predicate> |
88 | inline QString |
89 | generate_typeCount(Predicate p, |
90 | const InlineComponentOrDocumentRootName &inlinedComponent) const; |
91 | |
92 | static void generate_callExecuteRuntimeFunction(QStringList *block, const QString &url, |
93 | QQmlJSMetaMethod::AbsoluteFunctionIndex index, |
94 | const QString &accessor, |
95 | const QString &returnType, |
96 | const QList<QmltcVariable> ¶meters = {}); |
97 | |
98 | static void generate_createBindingOnProperty(QStringList *block, const QString &unitVarName, |
99 | const QString &scope, qsizetype functionIndex, |
100 | const QString &target, |
101 | const QQmlJSScope::ConstPtr &targetType, |
102 | int propertyIndex, const QQmlJSMetaProperty &p, |
103 | int valueTypeIndex, const QString &subTarget); |
104 | |
105 | // Used in generate_createTranslationBindingOnProperty to transport its numerous arguments. |
106 | struct TranslationBindingInfo |
107 | { |
108 | QString unitVarName; |
109 | QString scope; |
110 | QString target; |
111 | int propertyIndex; |
112 | QQmlJSMetaProperty property; |
113 | |
114 | QQmlTranslation data; |
115 | |
116 | int valueTypeIndex; |
117 | // For the source location of the translation binding |
118 | uint line; |
119 | // For the source location of the translation binding |
120 | uint column; |
121 | }; |
122 | |
123 | static void generate_createTranslationBindingOnProperty(QStringList *block, |
124 | const TranslationBindingInfo &info); |
125 | |
126 | static inline void generate_getCompilationUnitFromUrl(); |
127 | |
128 | struct PreparedValue |
129 | { |
130 | QStringList prologue; |
131 | QString value; |
132 | QStringList epilogue; |
133 | }; |
134 | |
135 | static PreparedValue wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p, QString value); |
136 | static PreparedValue wrap_extensionType(const QQmlJSScope::ConstPtr &type, |
137 | const QQmlJSMetaProperty &p, const QString &accessor); |
138 | |
139 | static QString wrap_privateClass(const QString &accessor, const QQmlJSMetaProperty &p); |
140 | static QString wrap_qOverload(const QList<QmltcVariable> ¶meters, |
141 | const QString &overloaded); |
142 | static QString wrap_addressof(const QString &addressed); |
143 | |
144 | QString urlMethodName() const |
145 | { |
146 | using namespace Qt::StringLiterals; |
147 | QFileInfo fi(documentUrl); |
148 | return u"q_qmltc_docUrl_" + fi.fileName().replace(before: u".qml"_s , after: u""_s ).replace(before: u'.', after: u'_'); |
149 | } |
150 | }; |
151 | |
152 | /*! |
153 | \internal |
154 | |
155 | Generates \a{current.init}'s code. The init method sets up a QQmlContext for |
156 | the object and (in case \a type is a document root) calls other object |
157 | creation methods in a well-defined order: |
158 | 1. current.beginClass |
159 | 2. current.endInit |
160 | 3. current.completeComponent |
161 | 4. current.finalizeComponent |
162 | 5. current.handleOnCompleted |
163 | |
164 | This function returns a QScopeGuard with the final instructions that have to |
165 | be generated at a later point, once everything else is compiled. |
166 | |
167 | \sa generate_initCodeForTopLevelComponent |
168 | */ |
169 | inline decltype(auto) QmltcCodeGenerator::generate_initCode(QmltcType ¤t, |
170 | const QQmlJSScope::ConstPtr &type) const |
171 | { |
172 | using namespace Qt::StringLiterals; |
173 | |
174 | // qmltc_init()'s parameters: |
175 | // * QQmltcObjectCreationHelper* creator |
176 | // * QQmlEngine* engine |
177 | // * const QQmlRefPointer<QQmlContextData>& parentContext |
178 | // * bool canFinalize [optional, when document root] |
179 | const bool isDocumentRoot = type == visitor->result(); |
180 | const bool isInlineComponent = type->isInlineComponent(); |
181 | |
182 | current.init.body << u"Q_UNUSED(creator)"_s ; // can happen sometimes |
183 | |
184 | current.init.body << u"auto context = parentContext;"_s ; |
185 | |
186 | // if parent scope has a QML base type and is not a (current) document root, |
187 | // the parentContext we passed as input to this object is a context of |
188 | // another document. we need to fix it by using parentContext->parent() |
189 | |
190 | const auto realQmlScope = [](const QQmlJSScope::ConstPtr &scope) { |
191 | if (scope->isArrayScope()) |
192 | return scope->parentScope(); |
193 | return scope; |
194 | }; |
195 | |
196 | if (auto parentScope = realQmlScope(type->parentScope()); |
197 | parentScope != visitor->result() && QQmlJSUtils::hasCompositeBase(scope: parentScope)) { |
198 | current.init.body << u"// NB: context->parent() is the context of this document"_s ; |
199 | current.init.body << u"context = context->parent();"_s ; |
200 | } |
201 | |
202 | // any object with QML base class has to call base's init method |
203 | if (auto base = type->baseType(); base->isComposite()) { |
204 | QString lhs; |
205 | // init creates new context. for document root, it's going to be a real |
206 | // parent context, so store it temporarily in `context` variable |
207 | if (isDocumentRoot || isInlineComponent) |
208 | lhs = u"context = "_s ; |
209 | current.init.body << u"// 0. call base's init method"_s ; |
210 | |
211 | const auto isCurrentType = [&](const QQmlJSScope::ConstPtr &qmlType) { |
212 | return qmlType == type; |
213 | }; |
214 | const QString creationOffset = |
215 | generate_typeCount(p: isCurrentType, inlinedComponent: type->enclosingInlineComponentName()); |
216 | |
217 | current.init.body << u"{"_s ; |
218 | current.init.body << u"QQmltcObjectCreationHelper subCreator(creator, %1);"_s .arg( |
219 | a: creationOffset); |
220 | current.init.body |
221 | << QStringLiteral("%1%2::%3(&subCreator, engine, context, /* finalize */ false);" ) |
222 | .arg(args&: lhs, args: base->internalName(), args&: current.init.name); |
223 | current.init.body << u"}"_s ; |
224 | } |
225 | |
226 | current.init.body |
227 | << QStringLiteral("auto %1 = QQmlEnginePrivate::get(engine);" ).arg(a: privateEngineName); |
228 | current.init.body << QStringLiteral("Q_UNUSED(%1)" ).arg(a: privateEngineName); // precaution |
229 | |
230 | // when generating root or inlineComponents, we need to create a new (document-level) context. |
231 | // otherwise, just use existing context as is |
232 | if (isDocumentRoot || isInlineComponent) { |
233 | current.init.body << u"// 1. create new QML context for this document"_s ; |
234 | current.init.body |
235 | << QStringLiteral( |
236 | "context = %1->createInternalContext(%1->compilationUnitFromUrl(%2()), " |
237 | "context, %3, true);" ) |
238 | .arg(args: privateEngineName, args: urlMethodName()) |
239 | .arg(a: this->visitor->creationIndex(type)); |
240 | } else { |
241 | current.init.body << u"// 1. use current context as this object's context"_s ; |
242 | current.init.body << u"// context = context;"_s ; |
243 | } |
244 | |
245 | if (!type->baseType()->isComposite() || isDocumentRoot || isInlineComponent) { |
246 | current.init.body << u"// 2. set context for this object"_s ; |
247 | current.init.body << QStringLiteral( |
248 | "%1->setInternalContext(this, context, QQmlContextData::%2);" ) |
249 | .arg(args: privateEngineName, |
250 | args: (isDocumentRoot ? u"DocumentRoot"_s |
251 | : u"OrdinaryObject"_s )); |
252 | if (isDocumentRoot || isInlineComponent) |
253 | current.init.body << u"context->setContextObject(this);"_s ; |
254 | } |
255 | |
256 | // context is this document's context. we must remember it in each type |
257 | current.variables.emplaceBack(args: u"QQmlRefPointer<QQmlContextData>"_s , args: u"q_qmltc_thisContext"_s , |
258 | args: u"nullptr"_s ); |
259 | current.init.body << u"%1::q_qmltc_thisContext = context;"_s .arg(a: type->internalName()); |
260 | |
261 | if (int id = visitor->runtimeId(type); id >= 0) { |
262 | current.init.body << u"// 3. set id since it is provided"_s ; |
263 | QString idString = visitor->addressableScopes().id(scope: type, referrer: type); |
264 | if (idString.isEmpty()) |
265 | idString = u"<unknown>"_s ; |
266 | QmltcCodeGenerator::generate_setIdValue(block: ¤t.init.body, context: u"context"_s , index: id, accessor: u"this"_s , |
267 | idString); |
268 | } |
269 | |
270 | // if type has an extension, create a dynamic meta object for it |
271 | bool hasExtension = false; |
272 | for (auto cppBase = QQmlJSScope::nonCompositeBaseType(type); cppBase; |
273 | cppBase = cppBase->baseType()) { |
274 | // QObject is special: we have a pseudo-extension on it due to builtins |
275 | if (cppBase->internalName() == u"QObject"_s ) |
276 | break; |
277 | if (cppBase->extensionType().extensionSpecifier != QQmlJSScope::NotExtension) { |
278 | hasExtension = true; |
279 | break; |
280 | } |
281 | } |
282 | if (hasExtension) { |
283 | current.init.body << u"{"_s ; |
284 | current.init.body << u"auto cppData = QmltcTypeData(this);"_s ; |
285 | current.init.body << u"qmltcCreateDynamicMetaObject(this, cppData);"_s ; |
286 | current.init.body << u"}"_s ; |
287 | } |
288 | |
289 | const auto generateFinalLines = [¤t, isDocumentRoot, isInlineComponent]() { |
290 | if (isDocumentRoot || isInlineComponent) { |
291 | current.init.body << u"// 4. finish the document root creation"_s ; |
292 | current.init.body << u"if (canFinalize) {"_s ; |
293 | current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);" ) |
294 | .arg(a: current.beginClass.name); |
295 | current.init.body << QStringLiteral(" %1(creator, engine);" ) |
296 | .arg(a: current.endInit.name); |
297 | current.init.body << QStringLiteral(" %1(creator, engine);" ) |
298 | .arg(a: current.setComplexBindings.name); |
299 | current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);" ) |
300 | .arg(a: current.completeComponent.name); |
301 | current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);" ) |
302 | .arg(a: current.finalizeComponent.name); |
303 | current.init.body << QStringLiteral(" %1(creator);" ) |
304 | .arg(a: current.handleOnCompleted.name); |
305 | current.init.body << u"}"_s ; |
306 | } |
307 | current.init.body << u"return context;"_s ; |
308 | }; |
309 | |
310 | return QScopeGuard(generateFinalLines); |
311 | } |
312 | |
313 | /*! |
314 | \internal |
315 | |
316 | Generates \a{current.init}'s code in case when \a type is a top-level |
317 | Component type. The init method in this case mimics |
318 | QQmlObjectCreator::createComponent() logic. |
319 | |
320 | \sa generate_initCode |
321 | */ |
322 | inline void |
323 | QmltcCodeGenerator::generate_initCodeForTopLevelComponent(QmltcType ¤t, |
324 | const QQmlJSScope::ConstPtr &type) |
325 | { |
326 | Q_UNUSED(type); |
327 | |
328 | using namespace Qt::StringLiterals; |
329 | |
330 | // since we create a document root as QQmlComponent, we only need to fake |
331 | // QQmlComponent construction in init: |
332 | current.init.body << u"// init QQmlComponent: see QQmlObjectCreator::createComponent()"_s ; |
333 | current.init.body << u"{"_s ; |
334 | // we already called QQmlComponent(parent) constructor. now we need: |
335 | // 1. QQmlComponent(engine, parent) logic: |
336 | current.init.body << u"// QQmlComponent(engine, parent):"_s ; |
337 | current.init.body << u"auto d = QQmlComponentPrivate::get(this);"_s ; |
338 | current.init.body << u"Q_ASSERT(d);"_s ; |
339 | current.init.body << u"d->engine = engine;"_s ; |
340 | current.init.body << u"QObject::connect(engine, &QObject::destroyed, this, [d]() {"_s ; |
341 | current.init.body << u" d->state.creator.reset();"_s ; |
342 | current.init.body << u" d->engine = nullptr;"_s ; |
343 | current.init.body << u"});"_s ; |
344 | // 2. QQmlComponent(engine, compilationUnit, start, parent) logic: |
345 | current.init.body << u"// QQmlComponent(engine, compilationUnit, start, parent):"_s ; |
346 | current.init.body |
347 | << u"auto compilationUnit = QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(" |
348 | + QmltcCodeGenerator::urlMethodName() + u"());" ; |
349 | current.init.body << u"d->compilationUnit = compilationUnit;"_s ; |
350 | current.init.body << u"d->start = 0;"_s ; |
351 | current.init.body << u"d->url = compilationUnit->finalUrl();"_s ; |
352 | current.init.body << u"d->progress = 1.0;"_s ; |
353 | // 3. QQmlObjectCreator::createComponent() logic which is left: |
354 | current.init.body << u"// QQmlObjectCreator::createComponent():"_s ; |
355 | current.init.body << u"d->creationContext = context;"_s ; |
356 | current.init.body << u"Q_ASSERT(QQmlData::get(this, /*create*/ false));"_s ; |
357 | current.init.body << u"}"_s ; |
358 | } |
359 | |
360 | /*! |
361 | \internal |
362 | |
363 | A generic helper function that generates special qmltc instruction code |
364 | boilerplate, adding it to a passed \a function. This is a building block |
365 | used to generate e.g. QML_endInit code. |
366 | */ |
367 | inline void QmltcCodeGenerator::generate_qmltcInstructionCallCode( |
368 | QmltcMethod *function, const QQmlJSScope::ConstPtr &type, |
369 | const QString &baseInstructionArgs, const QString &childInstructionArgs) const |
370 | { |
371 | using namespace Qt::StringLiterals; |
372 | |
373 | if (auto base = type->baseType(); base->isComposite()) { |
374 | function->body << u"// call base's method"_s ; |
375 | const auto isCurrentType = [&](const QQmlJSScope::ConstPtr &qmlType) { |
376 | return qmlType == type; |
377 | }; |
378 | const QString creationOffset = |
379 | generate_typeCount(p: isCurrentType, inlinedComponent: type->enclosingInlineComponentName()); |
380 | function->body << u"{"_s ; |
381 | function->body << u"QQmltcObjectCreationHelper subCreator(creator, %1);"_s .arg( |
382 | a: creationOffset); |
383 | if (!baseInstructionArgs.isEmpty()) { |
384 | function->body << u"%1::%2(&subCreator, %3);"_s .arg( |
385 | args: base->internalName(), args&: function->name, args: baseInstructionArgs); |
386 | } else { |
387 | function->body << u"%1::%2(&subCreator);"_s .arg(args: base->internalName(), args&: function->name); |
388 | } |
389 | function->body << u"}"_s ; |
390 | } |
391 | |
392 | const bool isDocumentRoot = type == visitor->result(); |
393 | const bool isInlineComponent = type->isInlineComponent(); |
394 | |
395 | if (!(isDocumentRoot |
396 | || isInlineComponent)) // document/inline component root does all the work here |
397 | return; |
398 | auto name = isInlineComponent |
399 | ? InlineComponentOrDocumentRootName(*type->inlineComponentName()) |
400 | : InlineComponentOrDocumentRootName(QQmlJSScope::RootDocumentNameType()); |
401 | const auto types = visitor->pureQmlTypes(inlineComponent: name); |
402 | function->body << u"// call children's methods"_s ; |
403 | for (qsizetype i = 1; i < types.size(); ++i) { |
404 | const auto &type = types[i]; |
405 | Q_ASSERT(!type->isComponentRootElement()); |
406 | function->body << u"creator->get<%1>(%2)->%3(%4);"_s .arg( |
407 | args: type->internalName(), args: QString::number(i), args&: function->name, args: childInstructionArgs); |
408 | } |
409 | function->body << u"// call own method code"_s ; |
410 | } |
411 | |
412 | /*! |
413 | \internal |
414 | |
415 | Generates \a{current.endInit}'s code. The endInit method creates bindings, |
416 | connects signals with slots and generally performs other within-object |
417 | initialization. Additionally, the QML document root's endInit calls endInit |
418 | methods of all the necessary QML types within the document. |
419 | */ |
420 | inline void QmltcCodeGenerator::generate_endInitCode(QmltcType ¤t, |
421 | const QQmlJSScope::ConstPtr &type) const |
422 | { |
423 | using namespace Qt::StringLiterals; |
424 | |
425 | // QML_endInit()'s parameters: |
426 | // * QQmltcObjectCreationHelper* creator |
427 | // * QQmlEngine* engine |
428 | current.endInit.body << u"Q_UNUSED(creator)"_s ; |
429 | current.endInit.body << u"Q_UNUSED(engine)"_s ; |
430 | |
431 | generate_qmltcInstructionCallCode(function: ¤t.endInit, type, baseInstructionArgs: u"engine"_s , childInstructionArgs: u"creator, engine"_s ); |
432 | |
433 | if (visitor->hasDeferredBindings(type)) { |
434 | current.endInit.body << u"{ // defer bindings"_s ; |
435 | current.endInit.body << u"auto ddata = QQmlData::get(this);"_s ; |
436 | current.endInit.body << u"auto thisContext = ddata->outerContext;"_s ; |
437 | current.endInit.body << u"Q_ASSERT(thisContext);"_s ; |
438 | current.endInit.body << QStringLiteral("ddata->deferData(%1, " |
439 | "QQmlEnginePrivate::get(engine)->" |
440 | "compilationUnitFromUrl(%2()), thisContext);" ) |
441 | .arg(args: QString::number(visitor->qmlIrObjectIndex(type)), |
442 | args: QmltcCodeGenerator::urlMethodName()); |
443 | current.endInit.body << u"}"_s ; |
444 | } |
445 | } |
446 | |
447 | /*! |
448 | \internal |
449 | |
450 | Generates \a{current.setComplexBindings}'s code. The setComplexBindings |
451 | method creates complex bindings (such as script bindings). Additionally, the |
452 | QML document root's setComplexBindings calls setComplexBindings methods of |
453 | all the necessary QML types within the document. |
454 | */ |
455 | inline void |
456 | QmltcCodeGenerator::generate_setComplexBindingsCode(QmltcType ¤t, |
457 | const QQmlJSScope::ConstPtr &type) const |
458 | { |
459 | using namespace Qt::StringLiterals; |
460 | |
461 | // QML_setComplexBindings()'s parameters: |
462 | // * QQmltcObjectCreationHelper* creator |
463 | // * QQmlEngine* engine |
464 | current.setComplexBindings.body << u"Q_UNUSED(creator)"_s ; |
465 | current.setComplexBindings.body << u"Q_UNUSED(engine)"_s ; |
466 | |
467 | generate_qmltcInstructionCallCode(function: ¤t.setComplexBindings, type, baseInstructionArgs: u"engine"_s , |
468 | childInstructionArgs: u"creator, engine"_s ); |
469 | } |
470 | |
471 | /*! |
472 | \internal |
473 | |
474 | A generic helper function that generates interface code boilerplate, adding |
475 | it to a passed \a function. This is a building block used to generate e.g. |
476 | QQmlParserStatus API calls. |
477 | */ |
478 | inline void QmltcCodeGenerator::generate_interfaceCallCode(QmltcMethod *function, |
479 | const QQmlJSScope::ConstPtr &type, |
480 | const QString &interfaceName, |
481 | const QString &interfaceCall) const |
482 | { |
483 | using namespace Qt::StringLiterals; |
484 | |
485 | // function's parameters: |
486 | // * QQmltcObjectCreationHelper* creator |
487 | // * bool canFinalize [optional, when document root or inline component root] |
488 | const bool isDocumentRoot = type == visitor->result(); |
489 | const bool isInlineComponent = type->isInlineComponent(); |
490 | function->body << u"Q_UNUSED(creator)"_s ; |
491 | if (isDocumentRoot || isInlineComponent) |
492 | function->body << u"Q_UNUSED(canFinalize)"_s ; |
493 | |
494 | if (auto base = type->baseType(); base->isComposite()) { |
495 | function->body << u"// call base's method"_s ; |
496 | const auto isCurrentType = [&](const QQmlJSScope::ConstPtr &qmlType) { |
497 | return qmlType == type; |
498 | }; |
499 | const QString creationOffset = |
500 | generate_typeCount(p: isCurrentType, inlinedComponent: type->enclosingInlineComponentName()); |
501 | function->body << u"{"_s ; |
502 | function->body << u"QQmltcObjectCreationHelper subCreator(creator, %1);"_s .arg( |
503 | a: creationOffset); |
504 | function->body << u"%1::%2(&subCreator, /* finalize */ false);"_s .arg(args: base->internalName(), |
505 | args&: function->name); |
506 | function->body << u"}"_s ; |
507 | } |
508 | |
509 | if (!(isDocumentRoot || isInlineComponent)) |
510 | return; |
511 | |
512 | auto name = isInlineComponent |
513 | ? InlineComponentOrDocumentRootName(*type->inlineComponentName()) |
514 | : InlineComponentOrDocumentRootName(QQmlJSScope::RootDocumentNameType()); |
515 | |
516 | const auto types = visitor->pureQmlTypes(inlineComponent: name); |
517 | function->body << u"// call children's methods"_s ; |
518 | for (qsizetype i = 1; i < types.size(); ++i) { |
519 | const auto &type = types[i]; |
520 | Q_ASSERT(!type->isComponentRootElement()); |
521 | function->body << u"{"_s ; |
522 | function->body << u"auto child = creator->get<%1>(%2);"_s .arg(args: type->internalName(), |
523 | args: QString::number(i)); |
524 | function->body << u"child->%1(creator);"_s .arg(a: function->name); |
525 | if (type->hasInterface(name: interfaceName)) { |
526 | function->body << u"static_assert(std::is_base_of<%1, %2>::value);"_s .arg( |
527 | args: interfaceName, args: type->internalName()); |
528 | function->body << u"child->%1();"_s .arg(a: interfaceCall); |
529 | } |
530 | function->body << u"}"_s ; |
531 | } |
532 | |
533 | if (type->hasInterface(name: interfaceName)) { |
534 | function->body << u"if (canFinalize) {"_s ; |
535 | function->body << u" // call own method"_s ; |
536 | function->body << u" static_assert(std::is_base_of<%1, %2>::value);"_s .arg( |
537 | args: interfaceName, args: type->internalName()); |
538 | function->body << u" this->%1();"_s .arg(a: interfaceCall); |
539 | function->body << u"}"_s ; |
540 | } |
541 | } |
542 | |
543 | /*! |
544 | \internal |
545 | |
546 | Generates \a{current.beginClass}'s code. The beginClass method optionally |
547 | calls QQmlParserStatus::classBegin() when \a type implements the |
548 | corresponding interface. |
549 | */ |
550 | inline void QmltcCodeGenerator::generate_beginClassCode(QmltcType ¤t, |
551 | const QQmlJSScope::ConstPtr &type) const |
552 | { |
553 | using namespace Qt::StringLiterals; |
554 | generate_interfaceCallCode(function: ¤t.beginClass, type, interfaceName: u"QQmlParserStatus"_s , interfaceCall: u"classBegin"_s ); |
555 | } |
556 | |
557 | /*! |
558 | \internal |
559 | |
560 | Generates \a{current.completeComponent}'s code. The completeComponent method |
561 | optionally calls QQmlParserStatus::componentComplete() when \a type |
562 | implements the corresponding interface. |
563 | */ |
564 | inline void |
565 | QmltcCodeGenerator::generate_completeComponentCode(QmltcType ¤t, |
566 | const QQmlJSScope::ConstPtr &type) const |
567 | { |
568 | using namespace Qt::StringLiterals; |
569 | generate_interfaceCallCode(function: ¤t.completeComponent, type, interfaceName: u"QQmlParserStatus"_s , |
570 | interfaceCall: u"componentComplete"_s ); |
571 | } |
572 | |
573 | /*! |
574 | \internal |
575 | |
576 | Generates \a{current.finalizeComponent}'s code. The finalizeComponent method |
577 | optionally calls QQmlFinalizerHook::componentFinalized() when \a type |
578 | implements the corresponding interface. |
579 | */ |
580 | inline void |
581 | QmltcCodeGenerator::generate_finalizeComponentCode(QmltcType ¤t, |
582 | const QQmlJSScope::ConstPtr &type) const |
583 | { |
584 | using namespace Qt::StringLiterals; |
585 | generate_interfaceCallCode(function: ¤t.finalizeComponent, type, interfaceName: u"QQmlFinalizerHook"_s , |
586 | interfaceCall: u"componentFinalized"_s ); |
587 | } |
588 | |
589 | /*! |
590 | \internal |
591 | |
592 | Generates \a{current.handleOnCompleted}'s code. The handleOnCompleted method |
593 | optionally calls a Component.onCompleted handler if that is present in \a |
594 | type. |
595 | */ |
596 | inline void |
597 | QmltcCodeGenerator::generate_handleOnCompletedCode(QmltcType ¤t, |
598 | const QQmlJSScope::ConstPtr &type) const |
599 | { |
600 | using namespace Qt::StringLiterals; |
601 | |
602 | // QML_handleOnCompleted()'s parameters: |
603 | // * QQmltcObjectCreationHelper* creator |
604 | current.handleOnCompleted.body << u"Q_UNUSED(creator)"_s ; |
605 | |
606 | generate_qmltcInstructionCallCode(function: ¤t.handleOnCompleted, type, baseInstructionArgs: QString(), childInstructionArgs: u"creator"_s ); |
607 | } |
608 | |
609 | /*! |
610 | \internal |
611 | |
612 | Generates a constexpr function consisting of a sum of type counts for a |
613 | current QML document. Predicate \a p acts as a stop condition to prematurely |
614 | end the sum generation. |
615 | |
616 | The high-level idea: |
617 | |
618 | Each qmltc-compiled document root has a notion of type count. Type count is |
619 | a number of types the current QML document contains (except for |
620 | Component-wrapped types) plus the sum of all type counts of all the QML |
621 | documents used in the current document: if current document has a type with |
622 | QML base type, this type's type count is added to the type count of the |
623 | current document. |
624 | |
625 | To be able to lookup created objects during the creation process, one needs |
626 | to know an index of each object within the document + an offset of the |
627 | document. Index comes from QmltcVisitor and is basically a serial number of |
628 | a type in the document (index < type count of the document root type). The |
629 | offset is more indirect. |
630 | |
631 | The current document always starts with an offset of 0, each type that has a |
632 | QML base type also "has a sub-document". Each sub-document has a non-0 |
633 | offset X, where X is calculated as a sum of the current document's type |
634 | count and a cumulative type count of all the previous sub-documents that |
635 | appear before the sub-document of interest: |
636 | |
637 | \code |
638 | // A.qml |
639 | Item { // offset: 0; number of types == 1 (document root) + 3 (children) |
640 | |
641 | QmlBase1 { } // offset: 4 (number of types in A.qml itself) |
642 | |
643 | QmlBase2 { } // offset: 4 + N, where N == typeCount(QmlBase1.qml) |
644 | |
645 | QmlBase3 { } // offset: (4 + N) + M, where M == typeCount(QmlBase2.qml) |
646 | |
647 | } // typeCount(A.qml) == 4 + N + M + O, where O == typeCount(QmlBase3.qml) |
648 | \endcode |
649 | |
650 | As all objects are put into an array, schematically you can look at it in |
651 | the following way: |
652 | |
653 | ``` |
654 | count: 4 N M O |
655 | objects: aaaa|xxxxxxxxxxxxx|yyyyyyy|zzz |
656 | ^ ^ ^ ^ |
657 | files: | QmlBase1.qml | QmlBase3.qml |
658 | A.qml QmlBase2.qml |
659 | ``` |
660 | |
661 | For the object lookup logic itself, see QQmltcObjectCreationHelper |
662 | */ |
663 | template<typename Predicate> |
664 | inline QString QmltcCodeGenerator::generate_typeCount( |
665 | Predicate p, const InlineComponentOrDocumentRootName &inlinedComponent) const |
666 | { |
667 | using namespace Qt::StringLiterals; |
668 | |
669 | const QList<QQmlJSScope::ConstPtr> typesWithBaseTypeCount = |
670 | visitor->qmlTypesWithQmlBases(inlinedComponentName: inlinedComponent); |
671 | QStringList components; |
672 | components.reserve(asize: 1 + typesWithBaseTypeCount.size()); |
673 | |
674 | Q_ASSERT(visitor->pureQmlTypes(inlinedComponent).size() > 0); |
675 | Q_ASSERT(visitor->typeCount(inlinedComponent) |
676 | >= visitor->pureQmlTypes(inlinedComponent).size()); |
677 | qsizetype typeCount = visitor->typeCount(inlineComponent: inlinedComponent); |
678 | |
679 | // add this document's type counts minus document root (if not an inline component) |
680 | if (std::holds_alternative<RootDocumentNameType>(v: inlinedComponent)) |
681 | typeCount--; |
682 | components << QString::number(typeCount); |
683 | |
684 | // traverse types with QML base classes |
685 | for (const QQmlJSScope::ConstPtr &t : typesWithBaseTypeCount) { |
686 | if (p(t)) |
687 | break; |
688 | QString typeCountTemplate = u"QQmltcObjectCreationHelper::typeCount<%1>()"_s ; |
689 | if (t == visitor->result()) { // t is this document's root |
690 | components << typeCountTemplate.arg(a: t->baseTypeName()); |
691 | } else if (t->isInlineComponent()) { |
692 | // inline components always have a base class, by definition |
693 | Q_ASSERT(t->baseType()); |
694 | components << typeCountTemplate.arg(a: t->baseType()->internalName()); |
695 | } else { |
696 | components << typeCountTemplate.arg(a: t->internalName()); |
697 | } |
698 | } |
699 | |
700 | return components.join(sep: u" + "_s ); |
701 | } |
702 | |
703 | QT_END_NAMESPACE |
704 | |
705 | #endif // QMLTCCOMPILERPIECES_H |
706 | |