1 | // Copyright (C) 2021 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "qmltcvisitor.h" |
5 | #include "qmltcpropertyutils.h" |
6 | |
7 | #include <QtCore/qfileinfo.h> |
8 | #include <QtCore/qstack.h> |
9 | #include <QtCore/qdir.h> |
10 | #include <QtCore/qloggingcategory.h> |
11 | |
12 | #include <private/qqmljsutils_p.h> |
13 | |
14 | #include <algorithm> |
15 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | using namespace Qt::StringLiterals; |
19 | |
20 | Q_DECLARE_LOGGING_CATEGORY(lcQmltcCompiler) |
21 | |
22 | static QString uniqueNameFromPieces(const QStringList &pieces, QHash<QString, int> &repetitions) |
23 | { |
24 | QString possibleName = pieces.join(sep: u'_'); |
25 | const int count = repetitions[possibleName]++; |
26 | if (count > 0) |
27 | possibleName.append(s: u"_" + QString::number(count)); |
28 | return possibleName; |
29 | } |
30 | |
31 | static bool isExplicitComponent(const QQmlJSScope::ConstPtr &type) |
32 | { |
33 | if (!type->isComposite()) |
34 | return false; |
35 | auto base = type->baseType(); |
36 | return base && base->internalName() == u"QQmlComponent" ; |
37 | } |
38 | |
39 | /*! \internal |
40 | Returns if type is an implicit component. |
41 | This method should only be called after implicit components |
42 | are detected, that is, after QQmlJSImportVisitor::endVisit(UiProgram *) |
43 | was called. |
44 | */ |
45 | static bool isImplicitComponent(const QQmlJSScope::ConstPtr &type) |
46 | { |
47 | if (!type->isComposite()) |
48 | return false; |
49 | const auto cppBase = QQmlJSScope::nonCompositeBaseType(type); |
50 | const bool isComponentBased = (cppBase && cppBase->internalName() == u"QQmlComponent" ); |
51 | return type->isComponentRootElement() && !isComponentBased; |
52 | } |
53 | |
54 | /*! \internal |
55 | Checks if type is inside or a (implicit or explicit) component. |
56 | This method should only be called after implicit components |
57 | are detected, that is, after QQmlJSImportVisitor::endVisit(UiProgram *) |
58 | was called. |
59 | */ |
60 | static bool isOrUnderComponent(QQmlJSScope::ConstPtr type) |
61 | { |
62 | Q_ASSERT(type->isComposite()); // we're dealing with composite types here |
63 | for (; type; type = type->parentScope()) { |
64 | if (isExplicitComponent(type) || isImplicitComponent(type)) { |
65 | return true; |
66 | } |
67 | } |
68 | return false; |
69 | } |
70 | |
71 | QmltcVisitor::QmltcVisitor(const QQmlJSScope::Ptr &target, QQmlJSImporter *importer, |
72 | QQmlJSLogger *logger, const QString &implicitImportDirectory, |
73 | const QStringList &qmldirFiles) |
74 | : QQmlJSImportVisitor(target, importer, logger, implicitImportDirectory, qmldirFiles) |
75 | { |
76 | m_qmlTypeNames.append(t: QFileInfo(logger->fileName()).baseName()); // put document root |
77 | } |
78 | |
79 | void QmltcVisitor::findCppIncludes() |
80 | { |
81 | // TODO: this pass is slow: we have to do exhaustive search because some C++ |
82 | // code could do forward declarations and they are hard to handle correctly |
83 | QSet<const QQmlJSScope *> visitedTypes; // we can still improve by walking all types only once |
84 | const auto visitType = [&visitedTypes](const QQmlJSScope::ConstPtr &type) -> bool { |
85 | if (visitedTypes.contains(value: type.data())) |
86 | return true; |
87 | visitedTypes.insert(value: type.data()); |
88 | return false; |
89 | }; |
90 | const auto addCppInclude = [this](const QQmlJSScope::ConstPtr &type) { |
91 | if (QString includeFile = type->filePath(); includeFile.endsWith(s: u".h" )) |
92 | m_cppIncludes.insert(value: std::move(includeFile)); |
93 | }; |
94 | |
95 | const auto findInType = [&](const QQmlJSScope::ConstPtr &type) { |
96 | if (!type) |
97 | return; |
98 | if (visitType(type)) // optimization - don't call nonCompositeBaseType() needlessly |
99 | return; |
100 | |
101 | // look in type |
102 | addCppInclude(type); |
103 | |
104 | // look in type's base type |
105 | auto base = type->baseType(); |
106 | if (!base && type->isComposite()) |
107 | // in this case, qqmljsimportvisitor would have already print an error message |
108 | // about the missing type, so just return silently without crashing |
109 | return; |
110 | if (!base || visitType(base)) |
111 | return; |
112 | addCppInclude(base); |
113 | }; |
114 | |
115 | const auto constructPrivateInclude = [](QStringView publicInclude) -> QString { |
116 | if (publicInclude.isEmpty()) |
117 | return QString(); |
118 | Q_ASSERT(publicInclude.endsWith(u".h"_s ) || publicInclude.endsWith(u".hpp"_s )); |
119 | const qsizetype dotLocation = publicInclude.lastIndexOf(c: u'.'); |
120 | QStringView extension = publicInclude.sliced(pos: dotLocation); |
121 | QStringView includeWithoutExtension = publicInclude.first(n: dotLocation); |
122 | // check if "public" include is in fact already private |
123 | if (publicInclude.startsWith(s: u"private" )) |
124 | return includeWithoutExtension + u"_p" + extension; |
125 | return u"private/" + includeWithoutExtension + u"_p" + extension; |
126 | }; |
127 | |
128 | // walk the whole type hierarchy |
129 | QStack<QQmlJSScope::ConstPtr> types; |
130 | types.push(t: m_exportedRootScope); |
131 | while (!types.isEmpty()) { |
132 | auto type = types.pop(); |
133 | Q_ASSERT(type); |
134 | |
135 | const auto scopeType = type->scopeType(); |
136 | if (scopeType != QQmlSA::ScopeType::QMLScope |
137 | && scopeType != QQmlSA::ScopeType::GroupedPropertyScope |
138 | && scopeType != QQmlSA::ScopeType::AttachedPropertyScope) { |
139 | continue; |
140 | } |
141 | |
142 | for (auto t = type; !type->isArrayScope() && t; t = t->baseType()) { |
143 | findInType(t); |
144 | |
145 | // look in properties |
146 | const auto properties = t->ownProperties(); |
147 | for (const QQmlJSMetaProperty &p : properties) { |
148 | findInType(p.type()); |
149 | |
150 | if (p.isPrivate() && t->filePath().endsWith(s: u".h" )) { |
151 | const QString ownersInclude = t->filePath(); |
152 | QString privateInclude = constructPrivateInclude(ownersInclude); |
153 | if (!privateInclude.isEmpty()) |
154 | m_cppIncludes.insert(value: std::move(privateInclude)); |
155 | } |
156 | } |
157 | |
158 | // look in methods |
159 | const auto methods = t->ownMethods(); |
160 | for (const QQmlJSMetaMethod &m : methods) { |
161 | findInType(m.returnType()); |
162 | |
163 | const auto parameters = m.parameters(); |
164 | for (const auto ¶m : parameters) |
165 | findInType(param.type()); |
166 | } |
167 | } |
168 | |
169 | types.append(other: type->childScopes()); |
170 | } |
171 | |
172 | // remove own include |
173 | m_cppIncludes.remove(value: m_exportedRootScope->filePath()); |
174 | } |
175 | |
176 | static void addCleanQmlTypeName(QStringList *names, const QQmlJSScope::ConstPtr &scope) |
177 | { |
178 | Q_ASSERT(scope->scopeType() == QQmlSA::ScopeType::QMLScope); |
179 | Q_ASSERT(!scope->isArrayScope()); |
180 | Q_ASSERT(!scope->baseTypeName().isEmpty()); |
181 | // the scope is guaranteed to be a new QML type, so any prefixes (separated |
182 | // by dots) should be import namespaces |
183 | const std::optional<QString> &inlineComponentName = scope->inlineComponentName(); |
184 | QString name = inlineComponentName ? *inlineComponentName : scope->baseTypeName(); |
185 | names->append(t: name.replace(before: u'.', after: u'_')); |
186 | } |
187 | |
188 | bool QmltcVisitor::visit(QQmlJS::AST::UiObjectDefinition *object) |
189 | { |
190 | const bool processingRoot = !rootScopeIsValid(); |
191 | |
192 | if (!QQmlJSImportVisitor::visit(object)) |
193 | return false; |
194 | |
195 | if (processingRoot || m_currentScope->isInlineComponent()) { |
196 | Q_ASSERT(rootScopeIsValid()); |
197 | setRootFilePath(); |
198 | } |
199 | |
200 | // we're not interested in non-QML scopes |
201 | if (m_currentScope->scopeType() != QQmlSA::ScopeType::QMLScope) |
202 | return true; |
203 | |
204 | if (m_currentScope->isInlineComponent()) { |
205 | m_inlineComponentNames.append(t: m_currentRootName); |
206 | m_inlineComponents[m_currentRootName] = m_currentScope; |
207 | } |
208 | |
209 | if (m_currentScope != m_exportedRootScope) // not document root |
210 | addCleanQmlTypeName(names: &m_qmlTypeNames, scope: m_currentScope); |
211 | // give C++-relevant internal names to QMLScopes, we can use them later in compiler |
212 | m_currentScope->setInternalName(uniqueNameFromPieces(pieces: m_qmlTypeNames, repetitions&: m_qmlTypeNameCounts)); |
213 | m_qmlTypesWithQmlBases[m_currentRootName].append(t: m_currentScope); |
214 | |
215 | return true; |
216 | } |
217 | |
218 | void QmltcVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *object) |
219 | { |
220 | if (m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope) |
221 | m_qmlTypeNames.removeLast(); |
222 | QQmlJSImportVisitor::endVisit(object); |
223 | } |
224 | |
225 | bool QmltcVisitor::visit(QQmlJS::AST::UiObjectBinding *uiob) |
226 | { |
227 | if (!QQmlJSImportVisitor::visit(uiob)) |
228 | return false; |
229 | |
230 | if (m_currentScope != m_exportedRootScope) // not document root |
231 | addCleanQmlTypeName(names: &m_qmlTypeNames, scope: m_currentScope); |
232 | // give C++-relevant internal names to QMLScopes, we can use them later in compiler |
233 | m_currentScope->setInternalName(uniqueNameFromPieces(pieces: m_qmlTypeNames, repetitions&: m_qmlTypeNameCounts)); |
234 | |
235 | m_qmlTypesWithQmlBases[m_currentRootName].append(t: m_currentScope); |
236 | return true; |
237 | } |
238 | |
239 | void QmltcVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob) |
240 | { |
241 | m_qmlTypeNames.removeLast(); |
242 | QQmlJSImportVisitor::endVisit(uiob); |
243 | } |
244 | |
245 | bool QmltcVisitor::visit(QQmlJS::AST::UiPublicMember *publicMember) |
246 | { |
247 | if (!QQmlJSImportVisitor::visit(publicMember)) |
248 | return false; |
249 | |
250 | // augment property: set its write/read/etc. methods |
251 | if (publicMember->type == QQmlJS::AST::UiPublicMember::Property) { |
252 | const auto name = publicMember->name.toString(); |
253 | |
254 | QQmlJSScope::Ptr owner = |
255 | m_savedBindingOuterScope ? m_savedBindingOuterScope : m_currentScope; |
256 | QQmlJSMetaProperty property = owner->ownProperty(name); |
257 | Q_ASSERT(property.isValid()); |
258 | if (!property.isAlias()) { // aliases are covered separately |
259 | QmltcPropertyData compiledData(property); |
260 | if (property.read().isEmpty()) |
261 | property.setRead(compiledData.read); |
262 | if (!property.isList()) { |
263 | if (property.write().isEmpty() && property.isWritable()) |
264 | property.setWrite(compiledData.write); |
265 | // Note: prefer BINDABLE to NOTIFY |
266 | if (property.bindable().isEmpty()) |
267 | property.setBindable(compiledData.bindable); |
268 | } |
269 | owner->addOwnProperty(prop: property); |
270 | } |
271 | |
272 | const QString notifyName = name + u"Changed"_s ; |
273 | // also check that notify is already a method of the scope |
274 | { |
275 | auto owningScope = m_savedBindingOuterScope ? m_savedBindingOuterScope : m_currentScope; |
276 | const auto methods = owningScope->ownMethods(name: notifyName); |
277 | if (methods.size() != 1) { |
278 | const QString errorString = |
279 | methods.isEmpty() ? u"no signal"_s : u"too many signals"_s ; |
280 | m_logger->log( |
281 | message: u"internal error: %1 found for property '%2'"_s .arg(args: errorString, args: name), |
282 | id: qmlCompiler, srcLocation: publicMember->identifierToken); |
283 | return false; |
284 | } else if (methods[0].methodType() != QQmlJSMetaMethodType::Signal) { |
285 | m_logger->log(message: u"internal error: method %1 of property %2 must be a signal"_s .arg( |
286 | args: notifyName, args: name), |
287 | id: qmlCompiler, srcLocation: publicMember->identifierToken); |
288 | return false; |
289 | } |
290 | } |
291 | } |
292 | |
293 | return true; |
294 | } |
295 | |
296 | bool QmltcVisitor::visit(QQmlJS::AST::UiScriptBinding *scriptBinding) |
297 | { |
298 | if (!QQmlJSImportVisitor::visit(scriptBinding)) |
299 | return false; |
300 | |
301 | { |
302 | const auto id = scriptBinding->qualifiedId; |
303 | if (!id->next && id->name == QLatin1String("id" )) |
304 | m_typesWithId[m_currentScope] = -1; // temporary value |
305 | } |
306 | |
307 | return true; |
308 | } |
309 | |
310 | bool QmltcVisitor::visit(QQmlJS::AST::UiInlineComponent *component) |
311 | { |
312 | if (!QQmlJSImportVisitor::visit(component)) |
313 | return false; |
314 | return true; |
315 | } |
316 | |
317 | void QmltcVisitor::endVisit(QQmlJS::AST::UiProgram *program) |
318 | { |
319 | QQmlJSImportVisitor::endVisit(program); |
320 | if (!rootScopeIsValid()) // in case we failed badly |
321 | return; |
322 | |
323 | QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> bindings; |
324 | for (const QQmlJSScope::ConstPtr &type : std::as_const(t&: m_qmlTypes)) { |
325 | if (isOrUnderComponent(type)) |
326 | continue; |
327 | bindings.insert(key: type, value: type->ownPropertyBindingsInQmlIROrder()); |
328 | } |
329 | |
330 | postVisitResolve(qmlIrOrderedBindings: bindings); |
331 | setupAliases(); |
332 | |
333 | if (m_mode != Mode::Compile) |
334 | return; |
335 | |
336 | findCppIncludes(); |
337 | |
338 | for (const QList<QQmlJSScope::ConstPtr> &qmlTypes : m_pureQmlTypes) |
339 | for (const QQmlJSScope::ConstPtr &type : qmlTypes) |
340 | checkForNamingCollisionsWithCpp(type); |
341 | } |
342 | |
343 | QQmlJSScope::ConstPtr fetchType(const QQmlJSMetaPropertyBinding &binding) |
344 | { |
345 | switch (binding.bindingType()) { |
346 | case QQmlSA::BindingType::Object: |
347 | return binding.objectType(); |
348 | case QQmlSA::BindingType::Interceptor: |
349 | return binding.interceptorType(); |
350 | case QQmlSA::BindingType::ValueSource: |
351 | return binding.valueSourceType(); |
352 | // TODO: AttachedProperty and GroupProperty are not supported yet, |
353 | // but have to also be acknowledged here |
354 | default: |
355 | return {}; |
356 | } |
357 | Q_UNREACHABLE_RETURN({}); |
358 | } |
359 | |
360 | template<typename Predicate> |
361 | void iterateTypes( |
362 | const QQmlJSScope::ConstPtr &root, |
363 | const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> &qmlIrOrderedBindings, |
364 | Predicate predicate) |
365 | { |
366 | // NB: depth-first-search is used here to mimic various QmlIR passes |
367 | QStack<QQmlJSScope::ConstPtr> types; |
368 | types.push(t: root); |
369 | while (!types.isEmpty()) { |
370 | auto current = types.pop(); |
371 | |
372 | if (predicate(current)) |
373 | continue; |
374 | |
375 | if (isOrUnderComponent(type: current)) // ignore implicit/explicit components |
376 | continue; |
377 | |
378 | Q_ASSERT(qmlIrOrderedBindings.contains(current)); |
379 | const auto &bindings = qmlIrOrderedBindings[current]; |
380 | // reverse the binding order here, because stack processes right-most |
381 | // child first and we need left-most first |
382 | for (auto it = bindings.rbegin(); it != bindings.rend(); ++it) { |
383 | const auto &binding = *it; |
384 | if (auto type = fetchType(binding)) |
385 | types.push(t: type); |
386 | } |
387 | } |
388 | } |
389 | |
390 | template<typename Predicate> |
391 | void iterateBindings( |
392 | const QQmlJSScope::ConstPtr &root, |
393 | const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> &qmlIrOrderedBindings, |
394 | Predicate predicate) |
395 | { |
396 | // NB: depth-first-search is used here to mimic various QmlIR passes |
397 | QStack<QQmlJSScope::ConstPtr> types; |
398 | types.push(t: root); |
399 | while (!types.isEmpty()) { |
400 | auto current = types.pop(); |
401 | |
402 | if (isOrUnderComponent(type: current)) // ignore implicit/explicit components |
403 | continue; |
404 | |
405 | Q_ASSERT(qmlIrOrderedBindings.contains(current)); |
406 | const auto &bindings = qmlIrOrderedBindings[current]; |
407 | // reverse the binding order here, because stack processes right-most |
408 | // child first and we need left-most first |
409 | for (auto it = bindings.rbegin(); it != bindings.rend(); ++it) { |
410 | const auto &binding = *it; |
411 | |
412 | if (predicate(current, binding)) |
413 | continue; |
414 | |
415 | if (auto type = fetchType(binding)) |
416 | types.push(t: type); |
417 | } |
418 | } |
419 | } |
420 | |
421 | /*! \internal |
422 | This is a special function that must be called after |
423 | QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiProgram *). It is used to |
424 | resolve things that couldn't be resolved during the AST traversal, such |
425 | as anything that is dependent on implicit or explicit components |
426 | */ |
427 | void QmltcVisitor::postVisitResolve( |
428 | const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> &qmlIrOrderedBindings) |
429 | { |
430 | |
431 | // add the document root (that is not an inline component), as we usually |
432 | // want to iterate on all inline components, followed by the document root |
433 | m_inlineComponentNames.append(t: RootDocumentNameType()); |
434 | m_inlineComponents[RootDocumentNameType()] = m_exportedRootScope; |
435 | |
436 | // match scopes to indices of QmlIR::Object from QmlIR::Document |
437 | qsizetype count = 0; |
438 | const auto setIndex = [&](const QQmlJSScope::Ptr ¤t) { |
439 | if (current->scopeType() != QQmlSA::ScopeType::QMLScope || current->isArrayScope()) |
440 | return; |
441 | Q_ASSERT(!m_qmlIrObjectIndices.contains(current)); |
442 | m_qmlIrObjectIndices[current] = count; |
443 | ++count; |
444 | }; |
445 | QQmlJSUtils::traverseFollowingQmlIrObjectStructure(root: m_exportedRootScope, act: setIndex); |
446 | |
447 | // find types that are part of the deferred bindings (we care about *types* |
448 | // exclusively here) |
449 | QSet<QQmlJSScope::ConstPtr> deferredTypes; |
450 | const auto findDeferred = [&](const QQmlJSScope::ConstPtr &type, |
451 | const QQmlJSMetaPropertyBinding &binding) { |
452 | const QString propertyName = binding.propertyName(); |
453 | Q_ASSERT(!propertyName.isEmpty()); |
454 | if (type->isNameDeferred(name: propertyName)) { |
455 | m_typesWithDeferredBindings.insert(value: type); |
456 | |
457 | if (binding.hasObject() || binding.hasInterceptor() || binding.hasValueSource()) { |
458 | deferredTypes.insert(value: fetchType(binding)); |
459 | return true; |
460 | } |
461 | } |
462 | return false; |
463 | }; |
464 | for (const auto &inlineComponentName : m_inlineComponentNames) { |
465 | iterateBindings(root: m_inlineComponents[inlineComponentName], qmlIrOrderedBindings, |
466 | predicate: findDeferred); |
467 | } |
468 | |
469 | const auto isOrUnderDeferred = [&deferredTypes](QQmlJSScope::ConstPtr type) { |
470 | for (; type; type = type->parentScope()) { |
471 | if (deferredTypes.contains(value: type)) |
472 | return true; |
473 | } |
474 | return false; |
475 | }; |
476 | |
477 | // find all "pure" QML types |
478 | QList<QQmlJSScope::ConstPtr> explicitComponents; |
479 | for (qsizetype i = 0; i < m_qmlTypes.size(); ++i) { |
480 | const QQmlJSScope::ConstPtr &type = m_qmlTypes.at(i); |
481 | |
482 | if (isOrUnderComponent(type) || isOrUnderDeferred(type)) { |
483 | // root is special: we compile Component roots. root is also never |
484 | // deferred, so in case `isOrUnderDeferred(type)` returns true, we |
485 | // always continue here |
486 | if (type != m_exportedRootScope) { |
487 | // if a type is an explicit component, its id "leaks" into the |
488 | // document context |
489 | if (isExplicitComponent(type)) |
490 | explicitComponents.append(t: type); |
491 | continue; |
492 | } |
493 | } |
494 | |
495 | const InlineComponentOrDocumentRootName inlineComponent = |
496 | type->enclosingInlineComponentName(); |
497 | QList<QQmlJSScope::ConstPtr> &pureQmlTypes = m_pureQmlTypes[inlineComponent]; |
498 | m_creationIndices[type] = pureQmlTypes.size(); |
499 | pureQmlTypes.append(t: type); |
500 | } |
501 | |
502 | // update the typeCounts |
503 | for (const auto &inlineComponent : m_inlineComponentNames) { |
504 | m_inlineComponentTypeCount[inlineComponent] = m_pureQmlTypes[inlineComponent].size(); |
505 | } |
506 | |
507 | // add explicit components to the object creation indices |
508 | { |
509 | QHash<InlineComponentOrDocumentRootName, qsizetype> index; |
510 | for (const QQmlJSScope::ConstPtr &c : std::as_const(t&: explicitComponents)) { |
511 | const InlineComponentOrDocumentRootName inlineComponent = |
512 | c->enclosingInlineComponentName(); |
513 | m_creationIndices[c] = |
514 | m_pureQmlTypes[inlineComponent].size() + index[inlineComponent]++; |
515 | m_inlineComponentTypeCount[inlineComponent]++; |
516 | } |
517 | } |
518 | |
519 | // m_qmlTypesWithQmlBases should contain the types to be compiled. |
520 | // Some types should not be compiled and are therefore filtered out: |
521 | // * deferred types |
522 | // * types inside of capital-c-Components (implicit and explicit) |
523 | // * non-composite types (that is, types not defined in qml) |
524 | // |
525 | // This can not be done earlier as implicitly wrapped Components are |
526 | // only known after visitation is over! |
527 | |
528 | // filter step: |
529 | for (const auto &inlineComponent : m_inlineComponentNames) { |
530 | QList<QQmlJSScope::ConstPtr> filteredQmlTypesWithQmlBases; |
531 | QList<QQmlJSScope::ConstPtr> &unfilteredQmlTypesWithQmlBases = |
532 | m_qmlTypesWithQmlBases[inlineComponent]; |
533 | filteredQmlTypesWithQmlBases.reserve(asize: unfilteredQmlTypesWithQmlBases.size()); |
534 | std::copy_if(first: unfilteredQmlTypesWithQmlBases.cbegin(), last: unfilteredQmlTypesWithQmlBases.cend(), |
535 | result: std::back_inserter(x&: filteredQmlTypesWithQmlBases), |
536 | pred: [&](const QQmlJSScope::ConstPtr &type) { |
537 | auto base = type->baseType(); |
538 | return base && base->isComposite() && !isOrUnderComponent(type) |
539 | && !isOrUnderDeferred(type); |
540 | }); |
541 | qSwap(value1&: unfilteredQmlTypesWithQmlBases, value2&: filteredQmlTypesWithQmlBases); |
542 | } |
543 | |
544 | // count QmlIR::Objects in the document - the amount is used to calculate |
545 | // object indices of implicit components |
546 | QHash<InlineComponentOrDocumentRootName, qsizetype> qmlScopeCount; |
547 | const auto countQmlScopes = [&](const QQmlJSScope::ConstPtr &scope) { |
548 | if (scope->isArrayScope()) // special kind of QQmlJSScope::QMLScope |
549 | return; |
550 | switch (scope->scopeType()) { |
551 | case QQmlSA::ScopeType::QMLScope: |
552 | case QQmlSA::ScopeType::GroupedPropertyScope: |
553 | case QQmlSA::ScopeType::AttachedPropertyScope: { |
554 | ++qmlScopeCount[scope->enclosingInlineComponentName()]; |
555 | break; |
556 | } |
557 | default: |
558 | return; |
559 | } |
560 | return; |
561 | }; |
562 | // order doesn't matter (so re-use QQmlJSUtils) |
563 | QQmlJSUtils::traverseFollowingQmlIrObjectStructure(root: m_exportedRootScope, act: countQmlScopes); |
564 | |
565 | // figure synthetic indices of QQmlComponent-wrapped types |
566 | int syntheticCreationIndex; |
567 | const auto addSyntheticIndex = [&](const QQmlJSScope::ConstPtr &type) { |
568 | // explicit component |
569 | if (isExplicitComponent(type)) { |
570 | m_syntheticTypeIndices[type] = m_qmlIrObjectIndices.value(key: type, defaultValue: -1); |
571 | return true; |
572 | } |
573 | // implicit component |
574 | if (isImplicitComponent(type)) { |
575 | const int index = |
576 | qmlScopeCount[type->enclosingInlineComponentName()] + syntheticCreationIndex++; |
577 | m_syntheticTypeIndices[type] = index; |
578 | return true; |
579 | } |
580 | return false; |
581 | }; |
582 | |
583 | for (const auto &inlineComponentName : m_inlineComponentNames) { |
584 | syntheticCreationIndex = 0; // reset for each inline component |
585 | iterateTypes(root: m_inlineComponents[inlineComponentName], qmlIrOrderedBindings, |
586 | predicate: addSyntheticIndex); |
587 | } |
588 | |
589 | // figure runtime object ids for non-component wrapped types |
590 | int currentId; |
591 | const auto setRuntimeId = [&](const QQmlJSScope::ConstPtr &type) { |
592 | // any type wrapped in an implicit component shouldn't be processed |
593 | // here. even if it has id, it doesn't need to be set by qmltc |
594 | if (type->isComponentRootElement()) { |
595 | return true; |
596 | } |
597 | |
598 | if (m_typesWithId.contains(key: type)) { |
599 | m_typesWithId[type] = currentId++; |
600 | } |
601 | |
602 | return false; |
603 | }; |
604 | |
605 | for (const auto &inlineComponentName : m_inlineComponentNames) { |
606 | currentId = 0; // reset for each inline component |
607 | iterateTypes(root: m_inlineComponents[inlineComponentName], qmlIrOrderedBindings, predicate: setRuntimeId); |
608 | } |
609 | } |
610 | |
611 | static void setAliasData(QQmlJSMetaProperty *alias, const QQmlJSUtils::ResolvedAlias &origin) |
612 | { |
613 | Q_ASSERT(origin.kind != QQmlJSUtils::AliasTarget_Invalid); |
614 | QmltcPropertyData compiledData(*alias); |
615 | if (alias->read().isEmpty()) |
616 | alias->setRead(compiledData.read); |
617 | if (origin.kind == QQmlJSUtils::AliasTarget_Object) // id-pointing aliases only have READ method |
618 | return; |
619 | if (origin.property.isWritable() && alias->write().isEmpty()) |
620 | alias->setWrite(compiledData.write); |
621 | |
622 | // the engine always compiles a notify for properties/aliases defined in qml code |
623 | // Yes, this generated notify will never be emitted. |
624 | if (alias->notify().isEmpty()) |
625 | alias->setNotify(compiledData.notify); |
626 | |
627 | if (!origin.property.bindable().isEmpty() && alias->bindable().isEmpty()) |
628 | alias->setBindable(compiledData.bindable); |
629 | } |
630 | |
631 | void QmltcVisitor::setupAliases() |
632 | { |
633 | QStack<QQmlJSScope::Ptr> types; |
634 | types.push(t: m_exportedRootScope); |
635 | |
636 | while (!types.isEmpty()) { |
637 | QQmlJSScope::Ptr current = types.pop(); |
638 | auto properties = current->ownProperties(); |
639 | |
640 | for (QQmlJSMetaProperty &p : properties) { |
641 | if (!p.isAlias()) |
642 | continue; |
643 | |
644 | auto result = QQmlJSUtils::resolveAlias(idScopes: m_scopesById, property: p, owner: current, |
645 | visitor: QQmlJSUtils::AliasResolutionVisitor {}); |
646 | if (result.kind == QQmlJSUtils::AliasTarget_Invalid) { |
647 | m_logger->log(QStringLiteral("Cannot resolve alias \"%1\"" ).arg(a: p.propertyName()), |
648 | id: qmlUnresolvedAlias, srcLocation: current->sourceLocation()); |
649 | continue; |
650 | } |
651 | setAliasData(alias: &p, origin: result); |
652 | current->addOwnProperty(prop: p); |
653 | } |
654 | } |
655 | } |
656 | |
657 | void QmltcVisitor::checkForNamingCollisionsWithCpp(const QQmlJSScope::ConstPtr &type) |
658 | { |
659 | static const QString cppKeywords[] = { |
660 | u"alignas"_s , |
661 | u"alignof"_s , |
662 | u"and"_s , |
663 | u"and_eq"_s , |
664 | u"asm"_s , |
665 | u"atomic_cancel"_s , |
666 | u"atomic_commit"_s , |
667 | u"atomic_noexcept"_s , |
668 | u"auto"_s , |
669 | u"bitand"_s , |
670 | u"bitor"_s , |
671 | u"bool"_s , |
672 | u"break"_s , |
673 | u"case"_s , |
674 | u"catch"_s , |
675 | u"char"_s , |
676 | u"char8_t"_s , |
677 | u"char16_t"_s , |
678 | u"char32_t"_s , |
679 | u"class"_s , |
680 | u"compl"_s , |
681 | u"concept"_s , |
682 | u"const"_s , |
683 | u"consteval"_s , |
684 | u"constexpr"_s , |
685 | u"const_cast"_s , |
686 | u"continue"_s , |
687 | u"co_await"_s , |
688 | u"co_return"_s , |
689 | u"co_yield"_s , |
690 | u"decltype"_s , |
691 | u"default"_s , |
692 | u"delete"_s , |
693 | u"do"_s , |
694 | u"double"_s , |
695 | u"dynamic_cast"_s , |
696 | u"else"_s , |
697 | u"enum"_s , |
698 | u"explicit"_s , |
699 | u"export"_s , |
700 | u"extern"_s , |
701 | u"false"_s , |
702 | u"float"_s , |
703 | u"for"_s , |
704 | u"friend"_s , |
705 | u"goto"_s , |
706 | u"if"_s , |
707 | u"inline"_s , |
708 | u"int"_s , |
709 | u"long"_s , |
710 | u"mutable"_s , |
711 | u"namespace"_s , |
712 | u"new"_s , |
713 | u"noexcept"_s , |
714 | u"not"_s , |
715 | u"not_eq"_s , |
716 | u"nullptr"_s , |
717 | u"operator"_s , |
718 | u"or"_s , |
719 | u"or_eq"_s , |
720 | u"private"_s , |
721 | u"protected"_s , |
722 | u"public"_s , |
723 | u"reflexpr"_s , |
724 | u"register"_s , |
725 | u"reinterpret_cast"_s , |
726 | u"requires"_s , |
727 | u"return"_s , |
728 | u"short"_s , |
729 | u"signed"_s , |
730 | u"sizeof"_s , |
731 | u"static"_s , |
732 | u"static_assert"_s , |
733 | u"static_cast"_s , |
734 | u"struct"_s , |
735 | u"switch"_s , |
736 | u"synchronized"_s , |
737 | u"template"_s , |
738 | u"this"_s , |
739 | u"thread_local"_s , |
740 | u"throw"_s , |
741 | u"true"_s , |
742 | u"try"_s , |
743 | u"typedef"_s , |
744 | u"typeid"_s , |
745 | u"typename"_s , |
746 | u"union"_s , |
747 | u"unsigned"_s , |
748 | u"using"_s , |
749 | u"virtual"_s , |
750 | u"void"_s , |
751 | u"volatile"_s , |
752 | u"wchar_t"_s , |
753 | u"while"_s , |
754 | u"xor"_s , |
755 | u"xor_eq"_s , |
756 | }; |
757 | |
758 | const auto isReserved = [&](QStringView word) { |
759 | if (word.startsWith(c: QChar(u'_')) && word.size() >= 2 |
760 | && (word[1].isUpper() || word[1] == QChar(u'_'))) { |
761 | return true; // Identifiers starting with underscore and uppercase are reserved in C++ |
762 | } |
763 | return std::binary_search(first: std::begin(arr: cppKeywords), last: std::end(arr: cppKeywords), val: word); |
764 | }; |
765 | |
766 | const auto validate = [&](QStringView name, QStringView errorPrefix) { |
767 | if (!isReserved(name)) |
768 | return; |
769 | m_logger->log(message: errorPrefix + u" '" + name + u"' is a reserved C++ word, consider renaming" , |
770 | id: qmlCompiler, srcLocation: type->sourceLocation()); |
771 | }; |
772 | |
773 | const auto enums = type->ownEnumerations(); |
774 | for (auto it = enums.cbegin(); it != enums.cend(); ++it) { |
775 | const QQmlJSMetaEnum e = it.value(); |
776 | validate(e.name(), u"Enumeration" ); |
777 | |
778 | const auto enumKeys = e.keys(); |
779 | for (const auto &key : enumKeys) |
780 | validate(key, u"Enumeration '%1' key"_s .arg(a: e.name())); |
781 | } |
782 | |
783 | const auto properties = type->ownProperties(); |
784 | for (auto it = properties.cbegin(); it != properties.cend(); ++it) { |
785 | const QQmlJSMetaProperty &p = it.value(); |
786 | validate(p.propertyName(), u"Property" ); |
787 | } |
788 | |
789 | const auto methods = type->ownMethods(); |
790 | for (auto it = methods.cbegin(); it != methods.cend(); ++it) { |
791 | const QQmlJSMetaMethod &m = it.value(); |
792 | validate(m.methodName(), u"Method" ); |
793 | |
794 | const auto parameterNames = m.parameterNames(); |
795 | for (const auto &name : parameterNames) |
796 | validate(name, u"Method '%1' parameter"_s .arg(a: m.methodName())); |
797 | } |
798 | |
799 | // TODO: one could also test signal handlers' parameters but we do not store |
800 | // this information in QQmlJSMetaPropertyBinding currently |
801 | } |
802 | |
803 | /*! \internal |
804 | * Sets the file paths for the document and the inline components roots. |
805 | */ |
806 | void QmltcVisitor::setRootFilePath() |
807 | { |
808 | const QString filePath = m_currentScope->filePath(); |
809 | if (filePath.endsWith(s: u".h" )) // assume the correct path is set |
810 | return; |
811 | Q_ASSERT(filePath.endsWith(u".qml"_s )); |
812 | |
813 | const QString correctedFilePath = sourceDirectoryPath(path: filePath); |
814 | const QStringList paths = m_importer->resourceFileMapper()->resourcePaths( |
815 | filter: QQmlJSResourceFileMapper::localFileFilter(file: correctedFilePath)); |
816 | auto = std::find_if(first: paths.cbegin(), last: paths.cend(), |
817 | pred: [](const QString &x) { return x.endsWith(s: u".h"_s ); }); |
818 | if (firstHeader == paths.cend()) { |
819 | const QString matchedPaths = paths.isEmpty() ? u"<none>"_s : paths.join(sep: u", " ); |
820 | qCDebug(lcQmltcCompiler, |
821 | "Failed to find a header file name for path %s. Paths checked:\n%s" , |
822 | correctedFilePath.toUtf8().constData(), matchedPaths.toUtf8().constData()); |
823 | return; |
824 | } |
825 | // NB: get the file name to avoid prefixes |
826 | m_currentScope->setFilePath(QFileInfo(*firstHeader).fileName()); |
827 | } |
828 | |
829 | QString QmltcVisitor::sourceDirectoryPath(const QString &path) |
830 | { |
831 | auto result = QQmlJSUtils::sourceDirectoryPath(importer: m_importer, buildDirectoryPath: path); |
832 | if (const QString *srcDirPath = std::get_if<QString>(ptr: &result)) |
833 | return *srcDirPath; |
834 | |
835 | const QQmlJS::DiagnosticMessage *error = std::get_if<QQmlJS::DiagnosticMessage>(ptr: &result); |
836 | Q_ASSERT(error); |
837 | qCDebug(lcQmltcCompiler, "%s" , error->message.toUtf8().constData()); |
838 | // return input as a fallback |
839 | return path; |
840 | } |
841 | |
842 | QT_END_NAMESPACE |
843 | |