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 |
156 | QQmlContext for the object and (in case \a type is a document |
157 | root) calls other object creation methods, and a user-provided |
158 | initialization callback, in a well-defined order: |
159 | 1. current.beginClass |
160 | 2. current.endInit |
161 | 3. user-provided initialization function |
162 | 4. current.setComplexBindings |
163 | 5. current.completeComponent |
164 | 6. current.finalizeComponent |
165 | 7. current.handleOnCompleted |
166 | |
167 | This function returns a QScopeGuard with the final instructions that have to |
168 | be generated at a later point, once everything else is compiled. |
169 | |
170 | \sa generate_initCodeForTopLevelComponent |
171 | */ |
172 | inline decltype(auto) QmltcCodeGenerator::generate_initCode(QmltcType ¤t, |
173 | const QQmlJSScope::ConstPtr &type) const |
174 | { |
175 | using namespace Qt::StringLiterals; |
176 | |
177 | // qmltc_init()'s parameters: |
178 | // * QQmltcObjectCreationHelper* creator |
179 | // * QQmlEngine* engine |
180 | // * const QQmlRefPointer<QQmlContextData>& parentContext |
181 | // * bool canFinalize [optional, when document root] |
182 | const bool isDocumentRoot = type == visitor->result(); |
183 | const bool isInlineComponent = type->isInlineComponent(); |
184 | |
185 | current.init.body << u"Q_UNUSED(creator)"_s ; // can happen sometimes |
186 | |
187 | current.init.body << u"auto context = parentContext;"_s ; |
188 | |
189 | // if parent scope has a QML base type and is not a (current) document root, |
190 | // the parentContext we passed as input to this object is a context of |
191 | // another document. we need to fix it by using parentContext->parent() |
192 | |
193 | const auto realQmlScope = [](const QQmlJSScope::ConstPtr &scope) { |
194 | if (scope->isArrayScope()) |
195 | return scope->parentScope(); |
196 | return scope; |
197 | }; |
198 | |
199 | if (auto parentScope = realQmlScope(type->parentScope()); |
200 | parentScope != visitor->result() && QQmlJSUtils::hasCompositeBase(scope: parentScope)) { |
201 | current.init.body << u"// NB: context->parent() is the context of this document"_s ; |
202 | current.init.body << u"context = context->parent();"_s ; |
203 | } |
204 | |
205 | // any object with QML base class has to call base's init method |
206 | if (auto base = type->baseType(); base->isComposite()) { |
207 | QString lhs; |
208 | // init creates new context. for document root, it's going to be a real |
209 | // parent context, so store it temporarily in `context` variable |
210 | if (isDocumentRoot || isInlineComponent) |
211 | lhs = u"context = "_s ; |
212 | current.init.body << u"// 0. call base's init method"_s ; |
213 | |
214 | const auto isCurrentType = [&](const QQmlJSScope::ConstPtr &qmlType) { |
215 | return qmlType == type; |
216 | }; |
217 | const QString creationOffset = |
218 | generate_typeCount(p: isCurrentType, inlinedComponent: type->enclosingInlineComponentName()); |
219 | |
220 | current.init.body << u"{"_s ; |
221 | current.init.body << u"QQmltcObjectCreationHelper subCreator(creator, %1);"_s .arg( |
222 | a: creationOffset); |
223 | current.init.body |
224 | << QStringLiteral("%1%2::%3(&subCreator, engine, context, /* finalize */ false);" ) |
225 | .arg(args&: lhs, args: base->internalName(), args&: current.init.name); |
226 | current.init.body << u"}"_s ; |
227 | } |
228 | |
229 | current.init.body |
230 | << QStringLiteral("auto %1 = QQmlEnginePrivate::get(engine);" ).arg(a: privateEngineName); |
231 | current.init.body << QStringLiteral("Q_UNUSED(%1)" ).arg(a: privateEngineName); // precaution |
232 | |
233 | // when generating root or inlineComponents, we need to create a new (document-level) context. |
234 | // otherwise, just use existing context as is |
235 | if (isDocumentRoot || isInlineComponent) { |
236 | current.init.body << u"// 1. create new QML context for this document"_s ; |
237 | current.init.body |
238 | << QStringLiteral( |
239 | "context = %1->createInternalContext(%1->compilationUnitFromUrl(%2()), " |
240 | "context, %3, true);" ) |
241 | .arg(args: privateEngineName, args: urlMethodName()) |
242 | .arg(a: this->visitor->creationIndex(type)); |
243 | } else { |
244 | current.init.body << u"// 1. use current context as this object's context"_s ; |
245 | current.init.body << u"// context = context;"_s ; |
246 | } |
247 | |
248 | if (!type->baseType()->isComposite() || isDocumentRoot || isInlineComponent) { |
249 | current.init.body << u"// 2. set context for this object"_s ; |
250 | current.init.body << QStringLiteral( |
251 | "%1->setInternalContext(this, context, QQmlContextData::%2);" ) |
252 | .arg(args: privateEngineName, |
253 | args: (isDocumentRoot ? u"DocumentRoot"_s |
254 | : u"OrdinaryObject"_s )); |
255 | if (isDocumentRoot || isInlineComponent) |
256 | current.init.body << u"context->setContextObject(this);"_s ; |
257 | } |
258 | |
259 | // context is this document's context. we must remember it in each type |
260 | current.variables.emplaceBack(args: u"QQmlRefPointer<QQmlContextData>"_s , args: u"q_qmltc_thisContext"_s , |
261 | args: u"nullptr"_s ); |
262 | current.init.body << u"%1::q_qmltc_thisContext = context;"_s .arg(a: type->internalName()); |
263 | |
264 | if (int id = visitor->runtimeId(type); id >= 0) { |
265 | current.init.body << u"// 3. set id since it is provided"_s ; |
266 | QString idString = visitor->addressableScopes().id(scope: type, referrer: type); |
267 | if (idString.isEmpty()) |
268 | idString = u"<unknown>"_s ; |
269 | QmltcCodeGenerator::generate_setIdValue(block: ¤t.init.body, context: u"context"_s , index: id, accessor: u"this"_s , |
270 | idString); |
271 | } |
272 | |
273 | // if type has an extension, create a dynamic meta object for it |
274 | bool hasExtension = false; |
275 | for (auto cppBase = QQmlJSScope::nonCompositeBaseType(type); cppBase; |
276 | cppBase = cppBase->baseType()) { |
277 | // QObject is special: we have a pseudo-extension on it due to builtins |
278 | if (cppBase->internalName() == u"QObject"_s ) |
279 | break; |
280 | if (cppBase->extensionType().extensionSpecifier != QQmlJSScope::NotExtension) { |
281 | hasExtension = true; |
282 | break; |
283 | } |
284 | } |
285 | if (hasExtension) { |
286 | current.init.body << u"{"_s ; |
287 | current.init.body << u"auto cppData = QmltcTypeData(this);"_s ; |
288 | current.init.body << u"qmltcCreateDynamicMetaObject(this, cppData);"_s ; |
289 | current.init.body << u"}"_s ; |
290 | } |
291 | |
292 | const auto generateFinalLines = [¤t, isDocumentRoot, isInlineComponent]() { |
293 | if (isDocumentRoot || isInlineComponent) { |
294 | current.init.body << u"// 4. finish the document root creation"_s ; |
295 | current.init.body << u"if (canFinalize) {"_s ; |
296 | current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);" ) |
297 | .arg(a: current.beginClass.name); |
298 | current.init.body << QStringLiteral(" %1(creator, engine);" ) |
299 | .arg(a: current.endInit.name); |
300 | |
301 | current.init.body << QStringLiteral(" {" ); |
302 | current.init.body << QStringLiteral(" PropertyInitializer propertyInitializer(*this);" ); |
303 | current.init.body << QStringLiteral(" initializer(propertyInitializer);" ); |
304 | current.init.body << QStringLiteral(" %1(creator, engine, propertyInitializer.initializedCache);" ).arg(a: current.setComplexBindings.name); |
305 | current.init.body << QStringLiteral(" }" ); |
306 | |
307 | |
308 | current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);" ) |
309 | .arg(a: current.completeComponent.name); |
310 | current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);" ) |
311 | .arg(a: current.finalizeComponent.name); |
312 | current.init.body << QStringLiteral(" %1(creator);" ) |
313 | .arg(a: current.handleOnCompleted.name); |
314 | current.init.body << u"}"_s ; |
315 | } |
316 | current.init.body << u"return context;"_s ; |
317 | }; |
318 | |
319 | return QScopeGuard(generateFinalLines); |
320 | } |
321 | |
322 | /*! |
323 | \internal |
324 | |
325 | Generates \a{current.init}'s code in case when \a type is a top-level |
326 | Component type. The init method in this case mimics |
327 | QQmlObjectCreator::createComponent() logic. |
328 | |
329 | \sa generate_initCode |
330 | */ |
331 | inline void |
332 | QmltcCodeGenerator::generate_initCodeForTopLevelComponent(QmltcType ¤t, |
333 | const QQmlJSScope::ConstPtr &type) |
334 | { |
335 | Q_UNUSED(type); |
336 | |
337 | using namespace Qt::StringLiterals; |
338 | |
339 | // since we create a document root as QQmlComponent, we only need to fake |
340 | // QQmlComponent construction in init: |
341 | current.init.body << u"// init QQmlComponent: see QQmlObjectCreator::createComponent()"_s ; |
342 | current.init.body << u"{"_s ; |
343 | // we already called QQmlComponent(parent) constructor. now we need: |
344 | // 1. QQmlComponent(engine, parent) logic: |
345 | current.init.body << u"// QQmlComponent(engine, parent):"_s ; |
346 | current.init.body << u"auto d = QQmlComponentPrivate::get(this);"_s ; |
347 | current.init.body << u"Q_ASSERT(d);"_s ; |
348 | current.init.body << u"d->engine = engine;"_s ; |
349 | current.init.body << u"QObject::connect(engine, &QObject::destroyed, this, [d]() {"_s ; |
350 | current.init.body << u" d->state.creator.reset();"_s ; |
351 | current.init.body << u" d->engine = nullptr;"_s ; |
352 | current.init.body << u"});"_s ; |
353 | // 2. QQmlComponent(engine, compilationUnit, start, parent) logic: |
354 | current.init.body << u"// QQmlComponent(engine, compilationUnit, start, parent):"_s ; |
355 | current.init.body |
356 | << u"auto compilationUnit = QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(" |
357 | + QmltcCodeGenerator::urlMethodName() + u"());" ; |
358 | current.init.body << u"d->compilationUnit = compilationUnit;"_s ; |
359 | current.init.body << u"d->start = 0;"_s ; |
360 | current.init.body << u"d->url = compilationUnit->finalUrl();"_s ; |
361 | current.init.body << u"d->progress = 1.0;"_s ; |
362 | // 3. QQmlObjectCreator::createComponent() logic which is left: |
363 | current.init.body << u"// QQmlObjectCreator::createComponent():"_s ; |
364 | current.init.body << u"d->creationContext = context;"_s ; |
365 | current.init.body << u"Q_ASSERT(QQmlData::get(this, /*create*/ false));"_s ; |
366 | current.init.body << u"}"_s ; |
367 | } |
368 | |
369 | /*! |
370 | \internal |
371 | |
372 | A generic helper function that generates special qmltc instruction code |
373 | boilerplate, adding it to a passed \a function. This is a building block |
374 | used to generate e.g. QML_endInit code. |
375 | */ |
376 | inline void QmltcCodeGenerator::generate_qmltcInstructionCallCode( |
377 | QmltcMethod *function, const QQmlJSScope::ConstPtr &type, |
378 | const QString &baseInstructionArgs, const QString &childInstructionArgs) const |
379 | { |
380 | using namespace Qt::StringLiterals; |
381 | |
382 | if (auto base = type->baseType(); base->isComposite()) { |
383 | function->body << u"// call base's method"_s ; |
384 | const auto isCurrentType = [&](const QQmlJSScope::ConstPtr &qmlType) { |
385 | return qmlType == type; |
386 | }; |
387 | const QString creationOffset = |
388 | generate_typeCount(p: isCurrentType, inlinedComponent: type->enclosingInlineComponentName()); |
389 | function->body << u"{"_s ; |
390 | function->body << u"QQmltcObjectCreationHelper subCreator(creator, %1);"_s .arg( |
391 | a: creationOffset); |
392 | if (!baseInstructionArgs.isEmpty()) { |
393 | function->body << u"%1::%2(&subCreator, %3);"_s .arg( |
394 | args: base->internalName(), args&: function->name, args: baseInstructionArgs); |
395 | } else { |
396 | function->body << u"%1::%2(&subCreator);"_s .arg(args: base->internalName(), args&: function->name); |
397 | } |
398 | function->body << u"}"_s ; |
399 | } |
400 | |
401 | const bool isDocumentRoot = type == visitor->result(); |
402 | const bool isInlineComponent = type->isInlineComponent(); |
403 | |
404 | if (!(isDocumentRoot |
405 | || isInlineComponent)) // document/inline component root does all the work here |
406 | return; |
407 | auto name = isInlineComponent |
408 | ? InlineComponentOrDocumentRootName(*type->inlineComponentName()) |
409 | : InlineComponentOrDocumentRootName(QQmlJSScope::RootDocumentNameType()); |
410 | const auto types = visitor->pureQmlTypes(inlineComponent: name); |
411 | function->body << u"// call children's methods"_s ; |
412 | for (qsizetype i = 1; i < types.size(); ++i) { |
413 | const auto &type = types[i]; |
414 | Q_ASSERT(type->componentRootStatus() == QQmlJSScope::IsComponentRoot::No); |
415 | function->body << u"creator->get<%1>(%2)->%3(%4);"_s .arg( |
416 | args: type->internalName(), args: QString::number(i), args&: function->name, args: childInstructionArgs); |
417 | } |
418 | function->body << u"// call own method code"_s ; |
419 | } |
420 | |
421 | /*! |
422 | \internal |
423 | |
424 | Generates \a{current.endInit}'s code. The endInit method creates bindings, |
425 | connects signals with slots and generally performs other within-object |
426 | initialization. Additionally, the QML document root's endInit calls endInit |
427 | methods of all the necessary QML types within the document. |
428 | */ |
429 | inline void QmltcCodeGenerator::generate_endInitCode(QmltcType ¤t, |
430 | const QQmlJSScope::ConstPtr &type) const |
431 | { |
432 | using namespace Qt::StringLiterals; |
433 | |
434 | // QML_endInit()'s parameters: |
435 | // * QQmltcObjectCreationHelper* creator |
436 | // * QQmlEngine* engine |
437 | current.endInit.body << u"Q_UNUSED(creator)"_s ; |
438 | current.endInit.body << u"Q_UNUSED(engine)"_s ; |
439 | |
440 | generate_qmltcInstructionCallCode(function: ¤t.endInit, type, baseInstructionArgs: u"engine"_s , childInstructionArgs: u"creator, engine"_s ); |
441 | |
442 | if (visitor->hasDeferredBindings(type)) { |
443 | QString icName; |
444 | if (auto potentialICName = type->enclosingInlineComponentName(); |
445 | std::holds_alternative<QQmlJSScope::InlineComponentNameType>(v: potentialICName)) |
446 | icName =get<QQmlJSScope::InlineComponentNameType>(v&: potentialICName); |
447 | else |
448 | icName = u"{}"_s ; |
449 | current.endInit.body << u"{ // defer bindings"_s ; |
450 | current.endInit.body << u"auto ddata = QQmlData::get(this);"_s ; |
451 | current.endInit.body << u"auto thisContext = ddata->outerContext;"_s ; |
452 | current.endInit.body << u"Q_ASSERT(thisContext);"_s ; |
453 | current.endInit.body << QStringLiteral("ddata->deferData(%1, " |
454 | "QQmlEnginePrivate::get(engine)->" |
455 | "compilationUnitFromUrl(%2()), thisContext, %3);" ) |
456 | .arg(args: QString::number(visitor->qmlIrObjectIndex(type)), |
457 | args: QmltcCodeGenerator::urlMethodName(), args&: icName); |
458 | current.endInit.body << u"}"_s ; |
459 | } |
460 | } |
461 | |
462 | /*! |
463 | \internal |
464 | |
465 | Generates \a{current.setComplexBindings}'s code. The setComplexBindings |
466 | method creates complex bindings (such as script bindings). Additionally, the |
467 | QML document root's setComplexBindings calls setComplexBindings methods of |
468 | all the necessary QML types within the document. |
469 | */ |
470 | inline void |
471 | QmltcCodeGenerator::generate_setComplexBindingsCode(QmltcType ¤t, |
472 | const QQmlJSScope::ConstPtr &type) const |
473 | { |
474 | using namespace Qt::StringLiterals; |
475 | |
476 | // QML_setComplexBindings()'s parameters: |
477 | // * QQmltcObjectCreationHelper* creator |
478 | // * QQmlEngine* engine |
479 | current.setComplexBindings.body << u"Q_UNUSED(creator)"_s ; |
480 | current.setComplexBindings.body << u"Q_UNUSED(engine)"_s ; |
481 | |
482 | generate_qmltcInstructionCallCode(function: ¤t.setComplexBindings, type, baseInstructionArgs: u"engine"_s , |
483 | childInstructionArgs: u"creator, engine"_s ); |
484 | } |
485 | |
486 | /*! |
487 | \internal |
488 | |
489 | A generic helper function that generates interface code boilerplate, adding |
490 | it to a passed \a function. This is a building block used to generate e.g. |
491 | QQmlParserStatus API calls. |
492 | */ |
493 | inline void QmltcCodeGenerator::generate_interfaceCallCode(QmltcMethod *function, |
494 | const QQmlJSScope::ConstPtr &type, |
495 | const QString &interfaceName, |
496 | const QString &interfaceCall) const |
497 | { |
498 | using namespace Qt::StringLiterals; |
499 | |
500 | // function's parameters: |
501 | // * QQmltcObjectCreationHelper* creator |
502 | // * bool canFinalize [optional, when document root or inline component root] |
503 | const bool isDocumentRoot = type == visitor->result(); |
504 | const bool isInlineComponent = type->isInlineComponent(); |
505 | function->body << u"Q_UNUSED(creator)"_s ; |
506 | if (isDocumentRoot || isInlineComponent) |
507 | function->body << u"Q_UNUSED(canFinalize)"_s ; |
508 | |
509 | if (auto base = type->baseType(); base->isComposite()) { |
510 | function->body << u"// call base's method"_s ; |
511 | const auto isCurrentType = [&](const QQmlJSScope::ConstPtr &qmlType) { |
512 | return qmlType == type; |
513 | }; |
514 | const QString creationOffset = |
515 | generate_typeCount(p: isCurrentType, inlinedComponent: type->enclosingInlineComponentName()); |
516 | function->body << u"{"_s ; |
517 | function->body << u"QQmltcObjectCreationHelper subCreator(creator, %1);"_s .arg( |
518 | a: creationOffset); |
519 | function->body << u"%1::%2(&subCreator, /* finalize */ false);"_s .arg(args: base->internalName(), |
520 | args&: function->name); |
521 | function->body << u"}"_s ; |
522 | } |
523 | |
524 | if (!(isDocumentRoot || isInlineComponent)) |
525 | return; |
526 | |
527 | auto name = isInlineComponent |
528 | ? InlineComponentOrDocumentRootName(*type->inlineComponentName()) |
529 | : InlineComponentOrDocumentRootName(QQmlJSScope::RootDocumentNameType()); |
530 | |
531 | const auto types = visitor->pureQmlTypes(inlineComponent: name); |
532 | function->body << u"// call children's methods"_s ; |
533 | for (qsizetype i = 1; i < types.size(); ++i) { |
534 | const auto &type = types[i]; |
535 | Q_ASSERT(type->componentRootStatus() == QQmlJSScope::IsComponentRoot::No); |
536 | function->body << u"{"_s ; |
537 | function->body << u"auto child = creator->get<%1>(%2);"_s .arg(args: type->internalName(), |
538 | args: QString::number(i)); |
539 | function->body << u"child->%1(creator);"_s .arg(a: function->name); |
540 | if (type->hasInterface(name: interfaceName)) { |
541 | function->body << u"static_assert(std::is_base_of<%1, %2>::value);"_s .arg( |
542 | args: interfaceName, args: type->internalName()); |
543 | function->body << u"child->%1();"_s .arg(a: interfaceCall); |
544 | } |
545 | function->body << u"}"_s ; |
546 | } |
547 | |
548 | if (type->hasInterface(name: interfaceName)) { |
549 | function->body << u"if (canFinalize) {"_s ; |
550 | function->body << u" // call own method"_s ; |
551 | function->body << u" static_assert(std::is_base_of<%1, %2>::value);"_s .arg( |
552 | args: interfaceName, args: type->internalName()); |
553 | function->body << u" this->%1();"_s .arg(a: interfaceCall); |
554 | function->body << u"}"_s ; |
555 | } |
556 | } |
557 | |
558 | /*! |
559 | \internal |
560 | |
561 | Generates \a{current.beginClass}'s code. The beginClass method optionally |
562 | calls QQmlParserStatus::classBegin() when \a type implements the |
563 | corresponding interface. |
564 | */ |
565 | inline void QmltcCodeGenerator::generate_beginClassCode(QmltcType ¤t, |
566 | const QQmlJSScope::ConstPtr &type) const |
567 | { |
568 | using namespace Qt::StringLiterals; |
569 | generate_interfaceCallCode(function: ¤t.beginClass, type, interfaceName: u"QQmlParserStatus"_s , interfaceCall: u"classBegin"_s ); |
570 | } |
571 | |
572 | /*! |
573 | \internal |
574 | |
575 | Generates \a{current.completeComponent}'s code. The completeComponent method |
576 | optionally calls QQmlParserStatus::componentComplete() when \a type |
577 | implements the corresponding interface. |
578 | */ |
579 | inline void |
580 | QmltcCodeGenerator::generate_completeComponentCode(QmltcType ¤t, |
581 | const QQmlJSScope::ConstPtr &type) const |
582 | { |
583 | using namespace Qt::StringLiterals; |
584 | generate_interfaceCallCode(function: ¤t.completeComponent, type, interfaceName: u"QQmlParserStatus"_s , |
585 | interfaceCall: u"componentComplete"_s ); |
586 | } |
587 | |
588 | /*! |
589 | \internal |
590 | |
591 | Generates \a{current.finalizeComponent}'s code. The finalizeComponent method |
592 | optionally calls QQmlFinalizerHook::componentFinalized() when \a type |
593 | implements the corresponding interface. |
594 | */ |
595 | inline void |
596 | QmltcCodeGenerator::generate_finalizeComponentCode(QmltcType ¤t, |
597 | const QQmlJSScope::ConstPtr &type) const |
598 | { |
599 | using namespace Qt::StringLiterals; |
600 | generate_interfaceCallCode(function: ¤t.finalizeComponent, type, interfaceName: u"QQmlFinalizerHook"_s , |
601 | interfaceCall: u"componentFinalized"_s ); |
602 | } |
603 | |
604 | /*! |
605 | \internal |
606 | |
607 | Generates \a{current.handleOnCompleted}'s code. The handleOnCompleted method |
608 | optionally calls a Component.onCompleted handler if that is present in \a |
609 | type. |
610 | */ |
611 | inline void |
612 | QmltcCodeGenerator::generate_handleOnCompletedCode(QmltcType ¤t, |
613 | const QQmlJSScope::ConstPtr &type) const |
614 | { |
615 | using namespace Qt::StringLiterals; |
616 | |
617 | // QML_handleOnCompleted()'s parameters: |
618 | // * QQmltcObjectCreationHelper* creator |
619 | current.handleOnCompleted.body << u"Q_UNUSED(creator)"_s ; |
620 | |
621 | generate_qmltcInstructionCallCode(function: ¤t.handleOnCompleted, type, baseInstructionArgs: QString(), childInstructionArgs: u"creator"_s ); |
622 | } |
623 | |
624 | /*! |
625 | \internal |
626 | |
627 | Generates a constexpr function consisting of a sum of type counts for a |
628 | current QML document. Predicate \a p acts as a stop condition to prematurely |
629 | end the sum generation. |
630 | |
631 | The high-level idea: |
632 | |
633 | Each qmltc-compiled document root has a notion of type count. Type count is |
634 | a number of types the current QML document contains (except for |
635 | Component-wrapped types) plus the sum of all type counts of all the QML |
636 | documents used in the current document: if current document has a type with |
637 | QML base type, this type's type count is added to the type count of the |
638 | current document. |
639 | |
640 | To be able to lookup created objects during the creation process, one needs |
641 | to know an index of each object within the document + an offset of the |
642 | document. Index comes from QmltcVisitor and is basically a serial number of |
643 | a type in the document (index < type count of the document root type). The |
644 | offset is more indirect. |
645 | |
646 | The current document always starts with an offset of 0, each type that has a |
647 | QML base type also "has a sub-document". Each sub-document has a non-0 |
648 | offset X, where X is calculated as a sum of the current document's type |
649 | count and a cumulative type count of all the previous sub-documents that |
650 | appear before the sub-document of interest: |
651 | |
652 | \code |
653 | // A.qml |
654 | Item { // offset: 0; number of types == 1 (document root) + 3 (children) |
655 | |
656 | QmlBase1 { } // offset: 4 (number of types in A.qml itself) |
657 | |
658 | QmlBase2 { } // offset: 4 + N, where N == typeCount(QmlBase1.qml) |
659 | |
660 | QmlBase3 { } // offset: (4 + N) + M, where M == typeCount(QmlBase2.qml) |
661 | |
662 | } // typeCount(A.qml) == 4 + N + M + O, where O == typeCount(QmlBase3.qml) |
663 | \endcode |
664 | |
665 | As all objects are put into an array, schematically you can look at it in |
666 | the following way: |
667 | |
668 | ``` |
669 | count: 4 N M O |
670 | objects: aaaa|xxxxxxxxxxxxx|yyyyyyy|zzz |
671 | ^ ^ ^ ^ |
672 | files: | QmlBase1.qml | QmlBase3.qml |
673 | A.qml QmlBase2.qml |
674 | ``` |
675 | |
676 | For the object lookup logic itself, see QQmltcObjectCreationHelper |
677 | */ |
678 | template<typename Predicate> |
679 | inline QString QmltcCodeGenerator::generate_typeCount( |
680 | Predicate p, const InlineComponentOrDocumentRootName &inlinedComponent) const |
681 | { |
682 | using namespace Qt::StringLiterals; |
683 | |
684 | const QList<QQmlJSScope::ConstPtr> typesWithBaseTypeCount = |
685 | visitor->qmlTypesWithQmlBases(inlinedComponentName: inlinedComponent); |
686 | QStringList components; |
687 | components.reserve(asize: 1 + typesWithBaseTypeCount.size()); |
688 | |
689 | Q_ASSERT(visitor->pureQmlTypes(inlinedComponent).size() > 0); |
690 | Q_ASSERT(visitor->typeCount(inlinedComponent) |
691 | >= visitor->pureQmlTypes(inlinedComponent).size()); |
692 | qsizetype typeCount = visitor->typeCount(inlineComponent: inlinedComponent); |
693 | |
694 | // add this document's type counts minus document root (if not an inline component) |
695 | if (std::holds_alternative<RootDocumentNameType>(v: inlinedComponent)) |
696 | typeCount--; |
697 | components << QString::number(typeCount); |
698 | |
699 | // traverse types with QML base classes |
700 | for (const QQmlJSScope::ConstPtr &t : typesWithBaseTypeCount) { |
701 | if (p(t)) |
702 | break; |
703 | QString typeCountTemplate = u"QQmltcObjectCreationHelper::typeCount<%1>()"_s ; |
704 | if (t == visitor->result()) { // t is this document's root |
705 | components << typeCountTemplate.arg(a: t->baseTypeName()); |
706 | } else if (t->isInlineComponent()) { |
707 | // inline components always have a base class, by definition |
708 | Q_ASSERT(t->baseType()); |
709 | components << typeCountTemplate.arg(a: t->baseType()->internalName()); |
710 | } else { |
711 | components << typeCountTemplate.arg(a: t->internalName()); |
712 | } |
713 | } |
714 | |
715 | return components.join(sep: u" + "_s ); |
716 | } |
717 | |
718 | QT_END_NAMESPACE |
719 | |
720 | #endif // QMLTCCOMPILERPIECES_H |
721 | |