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

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