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
16QT_BEGIN_NAMESPACE
17
18using namespace Qt::StringLiterals;
19
20Q_DECLARE_LOGGING_CATEGORY(lcQmltcCompiler)
21
22static 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
31static 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 */
45static 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 */
60static 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
71QmltcVisitor::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
79void 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 &param : 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
176static 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
188bool 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
218void 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
225bool 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
239void QmltcVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob)
240{
241 m_qmlTypeNames.removeLast();
242 QQmlJSImportVisitor::endVisit(uiob);
243}
244
245bool 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
296bool 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
310bool QmltcVisitor::visit(QQmlJS::AST::UiInlineComponent *component)
311{
312 if (!QQmlJSImportVisitor::visit(component))
313 return false;
314 return true;
315}
316
317void 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
343QQmlJSScope::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
360template<typename Predicate>
361void 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
390template<typename Predicate>
391void 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*/
427void 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 &current) {
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
611static 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
631void 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
657void 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 */
806void 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 firstHeader = 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
829QString 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
842QT_END_NAMESPACE
843

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