Revision control
++ // Copyright (C) 2021 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++ // Qt-Security score:significant
++
++ #include "qqmltcpropertyutils_p.h"
++
++ #include <private/qqmltcvisitor_p.h>
++
++ #include <QtCore/qfileinfo.h>
++ #include <QtCore/qstack.h>
++ #include <QtCore/qdir.h>
++ #include <QtCore/qloggingcategory.h>
++ #include <QtQml/private/qqmlsignalnames_p.h>
++
++ #include <private/qqmljsutils_p.h>
++
++ #include <algorithm>
++ #include <array>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg);
++
++ namespace QQmltc {
++
++ QSpan<const QmltcLoggingCategoryOverride> categoryOverrides()
++ {
++ const auto Error = std::make_optional(QQmlJS::WarningSeverity::Error);
++
++ // We want qmltc to be stricter than qmllint. Override certain categories.
++ // Kept in sync via tst_qmltc::ensureWarningCategoryOverridesSync
++ static const std::array overrides{
++ QmltcLoggingCategoryOverride{ qmlAccessSingleton, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlAliasCycle, Error },
++ QmltcLoggingCategoryOverride{ qmlAssignmentInCondition, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlAttachedPropertyReuse, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlBlockScopeVarDeclaration, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlComma, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlCompiler, Error },
++ QmltcLoggingCategoryOverride{ qmlComponentChildrenCount, Error },
++ QmltcLoggingCategoryOverride{ qmlConfusingExpressionStatement, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlConfusingMinuses, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlConfusingPluses, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlContextProperties, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlDeferredPropertyId, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlEnumsAreNotTypes, Error },
++ QmltcLoggingCategoryOverride{ qmlEqualityTypeCoercion, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlDeprecated, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlDuplicateEnumEntries, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlDuplicateImport, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlDuplicateInlineComponent, Error },
++ QmltcLoggingCategoryOverride{ qmlDuplicatePropertyBinding, Error },
++ QmltcLoggingCategoryOverride{ qmlDuplicatedName, Error },
++ QmltcLoggingCategoryOverride{ qmlEnumEntryMatchesEnum, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlEnumKeyCase, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlEval, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlFunctionUsedBeforeDeclaration, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlIdShadowsMember, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlImport, Error },
++ QmltcLoggingCategoryOverride{ qmlImportFileSelector, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlIncompatibleType, Error },
++ QmltcLoggingCategoryOverride{ qmlInheritanceCycle, Error },
++ QmltcLoggingCategoryOverride{ qmlInlineComponentEnums, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlInvalidLintDirective, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlLiteralConstructor, Error },
++ QmltcLoggingCategoryOverride{ qmlMaxWarningsExceeded, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlMissingEnumEntry, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlMissingProperty, Error },
++ QmltcLoggingCategoryOverride{ qmlMissingType, Error },
++ QmltcLoggingCategoryOverride{ qmlMultilineStrings, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlNonListProperty, Error },
++ QmltcLoggingCategoryOverride{ qmlNonRootEnums, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlPropertyOverride, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlUnterminatedCase, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlPreferNonVarProperties, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlPrefixedImportType, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlReadOnlyProperty, Error },
++ QmltcLoggingCategoryOverride{ qmlRecursionDepthErrors, Error },
++ QmltcLoggingCategoryOverride{ qmlRedundantOptionalChaining, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlRenamedType, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlRequired, Error },
++ QmltcLoggingCategoryOverride{ qmlShadow, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlSignalHandlerNameClash, Error },
++ QmltcLoggingCategoryOverride{ qmlSignalParameters, Error },
++ QmltcLoggingCategoryOverride{ qmlStalePropertyRead, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlSyntax, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlSyntaxDuplicateIds, Error },
++ QmltcLoggingCategoryOverride{ qmlSyntaxIdQuotation, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlTypeInstantiatedRecursively, Error },
++ QmltcLoggingCategoryOverride{ qmlTopLevelComponent, Error },
++ QmltcLoggingCategoryOverride{ qmlUncreatableType, Error },
++ QmltcLoggingCategoryOverride{ qmlUnintentionalEmptyBlock, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlUnqualified, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlUnreachableCode, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlUnresolvedAlias, Error },
++ QmltcLoggingCategoryOverride{ qmlUnresolvedType, Error },
++ QmltcLoggingCategoryOverride{ qmlUnusedImports, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlUseProperFunction, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlVarUsedBeforeDeclaration, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlVoid, std::nullopt },
++ QmltcLoggingCategoryOverride{ qmlWith, std::nullopt },
++ };
++
++ return overrides;
++ }
++
++ static QString uniqueNameFromPieces(const QStringList &pieces, QHash<QString, int> &repetitions)
++ {
++ QString possibleName = pieces.join(u'_');
++ const int count = repetitions[possibleName]++;
++ if (count > 0)
++ possibleName.append(u"_" + QString::number(count));
++ return possibleName;
++ }
++
++ static bool isExplicitComponent(const QQmlJSScope::ConstPtr &type)
++ {
++ if (!type->isComposite())
++ return false;
++ auto base = type->baseType();
++ return base && base->internalName() == u"QQmlComponent";
++ }
++
++ /*! \internal
++ Returns if type is an implicit component.
++ This method should only be called after implicit components
++ are detected, that is, after QQmlJSImportVisitor::endVisit(UiProgram *)
++ was called.
++ */
++ static bool isImplicitComponent(const QQmlJSScope::ConstPtr &type)
++ {
++ // root components and inline components are explicitly components.
++ if (!type->isComposite() || type->isFileRootComponent() || type->isInlineComponent())
++ return false;
++
++ const auto cppBase = QQmlJSScope::nonCompositeBaseType(type);
++ const bool isComponentBased = (cppBase && cppBase->internalName() == u"QQmlComponent");
++ return type->componentRootStatus() != QQmlJSScope::IsComponentRoot::No && !isComponentBased;
++ }
++
++ /*! \internal
++ Checks if type is inside or a (implicit or explicit) component.
++ This method should only be called after implicit components
++ are detected, that is, after QQmlJSImportVisitor::endVisit(UiProgram *)
++ was called.
++ */
++ static bool isOrUnderComponent(QQmlJSScope::ConstPtr type)
++ {
++ Q_ASSERT(type->isComposite()); // we're dealing with composite types here
++ for (; type; type = type->parentScope()) {
++ if (isExplicitComponent(type) || isImplicitComponent(type)) {
++ return true;
++ }
++ }
++ return false;
++ }
++
++ Visitor::Visitor(QQmlJSImporter *importer, QQmlJSLogger *logger,
++ const QString &implicitImportDirectory, const QStringList &qmldirFiles)
++ : QQmlJSImportVisitor(importer, logger, implicitImportDirectory, qmldirFiles)
++ {
++ m_qmlTypeNames.append(QFileInfo(logger->filePath()).baseName()); // put document root
++ }
++
++ void Visitor::findCppIncludes()
++ {
++ // TODO: this pass is slow: we have to do exhaustive search because some C++
++ // code could do forward declarations and they are hard to handle correctly
++ QSet<const QQmlJSScope *> visitedTypes; // we can still improve by walking all types only once
++ const auto visitType = [&visitedTypes](const QQmlJSScope::ConstPtr &type) -> bool {
++ if (visitedTypes.contains(type.data()))
++ return true;
++ visitedTypes.insert(type.data());
++ return false;
++ };
++ const auto addCppInclude = [this](const QQmlJSScope::ConstPtr &type) {
++ if (QString includeFile = filePath(type); !includeFile.isEmpty())
++ m_cppIncludes.insert(std::move(includeFile));
++ };
++
++ const auto findInType = [&](const QQmlJSScope::ConstPtr &type) {
++ if (!type)
++ return;
++ if (visitType(type)) // optimization - don't call nonCompositeBaseType() needlessly
++ return;
++
++ // look in type
++ addCppInclude(type);
++
++ if (type->isListProperty())
++ addCppInclude(type->elementType());
++
++ // look in type's base type
++ auto base = type->baseType();
++ if (!base && type->isComposite())
++ // in this case, qqmljsimportvisitor would have already print an error message
++ // about the missing type, so just return silently without crashing
++ return;
++ if (!base || visitType(base))
++ return;
++ addCppInclude(base);
++ };
++
++ const auto constructPrivateInclude = [](QStringView publicInclude) -> QString {
++ if (publicInclude.isEmpty())
++ return QString();
++ Q_ASSERT(publicInclude.endsWith(u".h"_s) || publicInclude.endsWith(u".hpp"_s));
++ const qsizetype dotLocation = publicInclude.lastIndexOf(u'.');
++ QStringView extension = publicInclude.sliced(dotLocation);
++ QStringView includeWithoutExtension = publicInclude.first(dotLocation);
++ // check if "public" include is in fact already private
++ if (publicInclude.startsWith(u"private"))
++ return includeWithoutExtension + u"_p" + extension;
++ return u"private/" + includeWithoutExtension + u"_p" + extension;
++ };
++
++ // walk the whole type hierarchy
++ QStack<QQmlJSScope::ConstPtr> types;
++ types.push(m_exportedRootScope);
++ while (!types.isEmpty()) {
++ auto type = types.pop();
++ Q_ASSERT(type);
++
++ const auto scopeType = type->scopeType();
++ if (scopeType != QQmlSA::ScopeType::QMLScope
++ && scopeType != QQmlSA::ScopeType::GroupedPropertyScope
++ && scopeType != QQmlSA::ScopeType::AttachedPropertyScope) {
++ continue;
++ }
++
++ for (auto t = type; !type->isArrayScope() && t; t = t->baseType()) {
++ findInType(t);
++
++ // look in properties
++ const auto properties = t->ownProperties();
++ for (const QQmlJSMetaProperty &p : properties) {
++ findInType(p.type());
++
++ if (p.isPrivate()) {
++ const QString ownersInclude = filePath(t);
++ QString privateInclude = constructPrivateInclude(ownersInclude);
++ if (!privateInclude.isEmpty())
++ m_cppIncludes.insert(std::move(privateInclude));
++ }
++ }
++
++ // look in methods
++ const auto methods = t->ownMethods();
++ for (const QQmlJSMetaMethod &m : methods) {
++ findInType(m.returnType());
++
++ const auto parameters = m.parameters();
++ for (const auto ¶m : parameters)
++ findInType(param.type());
++ }
++ }
++
++ types.append(type->childScopes());
++ }
++
++ // remove own include
++ m_cppIncludes.remove(filePath(m_exportedRootScope));
++ }
++
++ static void addCleanQmlTypeName(QStringList *names, const QQmlJSScope::ConstPtr &scope)
++ {
++ Q_ASSERT(scope->scopeType() == QQmlSA::ScopeType::QMLScope);
++ Q_ASSERT(!scope->isArrayScope());
++ Q_ASSERT(!scope->baseTypeName().isEmpty());
++ // the scope is guaranteed to be a new QML type, so any prefixes (separated
++ // by dots) should be import namespaces
++ const std::optional<QString> &inlineComponentName = scope->inlineComponentName();
++ QString name = inlineComponentName ? *inlineComponentName : scope->baseTypeName();
++ names->append(name.replace(u'.', u'_'));
++ }
++
++ bool Visitor::visit(QQmlJS::AST::UiObjectDefinition *object)
++ {
++ if (!QQmlJSImportVisitor::visit(object))
++ return false;
++
++ // we're not interested in non-QML scopes
++ if (m_currentScope->scopeType() != QQmlSA::ScopeType::QMLScope)
++ return true;
++
++ if (m_currentScope->isInlineComponent()) {
++ m_inlineComponentNames.append(m_currentRootName);
++ m_inlineComponents[m_currentRootName] = m_currentScope;
++ }
++
++ if (m_currentScope != m_exportedRootScope) // not document root
++ addCleanQmlTypeName(&m_qmlTypeNames, m_currentScope);
++ // give C++-relevant internal names to QMLScopes, we can use them later in compiler
++ m_currentScope->setInternalName(uniqueNameFromPieces(m_qmlTypeNames, m_qmlTypeNameCounts));
++ m_qmlTypesWithQmlBases[m_currentRootName].append(m_currentScope);
++
++ return true;
++ }
++
++ void Visitor::endVisit(QQmlJS::AST::UiObjectDefinition *object)
++ {
++ if (m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope)
++ m_qmlTypeNames.removeLast();
++ QQmlJSImportVisitor::endVisit(object);
++ }
++
++ bool Visitor::visit(QQmlJS::AST::UiObjectBinding *uiob)
++ {
++ if (!QQmlJSImportVisitor::visit(uiob))
++ return false;
++
++ if (m_currentScope != m_exportedRootScope) // not document root
++ addCleanQmlTypeName(&m_qmlTypeNames, m_currentScope);
++ // give C++-relevant internal names to QMLScopes, we can use them later in compiler
++ m_currentScope->setInternalName(uniqueNameFromPieces(m_qmlTypeNames, m_qmlTypeNameCounts));
++
++ m_qmlTypesWithQmlBases[m_currentRootName].append(m_currentScope);
++ return true;
++ }
++
++ void Visitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob)
++ {
++ m_qmlTypeNames.removeLast();
++ QQmlJSImportVisitor::endVisit(uiob);
++ }
++
++ bool Visitor::visit(QQmlJS::AST::UiPublicMember *publicMember)
++ {
++ if (!QQmlJSImportVisitor::visit(publicMember))
++ return false;
++
++ // augment property: set its write/read/etc. methods
++ if (publicMember->type == QQmlJS::AST::UiPublicMember::Property) {
++ const auto name = publicMember->name.toString();
++
++ QQmlJSScope::Ptr owner =
++ m_savedBindingOuterScope ? m_savedBindingOuterScope : m_currentScope;
++ QQmlJSMetaProperty property = owner->ownProperty(name);
++ Q_ASSERT(property.isValid());
++ if (!property.isAlias()) { // aliases are covered separately
++ PropertyData compiledData(property);
++ if (property.read().isEmpty())
++ property.setRead(compiledData.read);
++ if (!property.isList()) {
++ if (property.write().isEmpty() && property.isWritable())
++ property.setWrite(compiledData.write);
++ // Note: prefer BINDABLE to NOTIFY
++ if (property.bindable().isEmpty())
++ property.setBindable(compiledData.bindable);
++ }
++ owner->addOwnProperty(property);
++ }
++
++ const QString notifyName = QQmlSignalNames::propertyNameToChangedSignalName(name);
++ // also check that notify is already a method of the scope
++ {
++ auto owningScope = m_savedBindingOuterScope ? m_savedBindingOuterScope : m_currentScope;
++ const auto methods = owningScope->ownMethods(notifyName);
++ if (methods.size() != 1) {
++ const QString errorString =
++ methods.isEmpty() ? u"no signal"_s : u"too many signals"_s;
++ m_logger->log(
++ u"internal error: %1 found for property '%2'"_s.arg(errorString, name),
++ qmlCompiler, publicMember->identifierToken);
++ return false;
++ } else if (methods[0].methodType() != QQmlJSMetaMethodType::Signal) {
++ m_logger->log(u"internal error: method %1 of property %2 must be a signal"_s.arg(
++ notifyName, name),
++ qmlCompiler, publicMember->identifierToken);
++ return false;
++ }
++ }
++ }
++
++ return true;
++ }
++
++ bool Visitor::visit(QQmlJS::AST::UiScriptBinding *scriptBinding)
++ {
++ if (!QQmlJSImportVisitor::visit(scriptBinding))
++ return false;
++
++ {
++ const auto id = scriptBinding->qualifiedId;
++ if (!id->next && id->name == QLatin1String("id"))
++ m_typesWithId[m_currentScope] = -1; // temporary value
++ }
++
++ return true;
++ }
++
++ bool Visitor::visit(QQmlJS::AST::UiInlineComponent *component)
++ {
++ if (!QQmlJSImportVisitor::visit(component))
++ return false;
++ return true;
++ }
++
++ void Visitor::endVisit(QQmlJS::AST::UiProgram *program)
++ {
++ QQmlJSImportVisitor::endVisit(program);
++ if (!rootScopeIsValid()) // in case we failed badly
++ return;
++
++ QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> bindings;
++
++ // Yes, we want absolutely all bindings in the document.
++ // Not only the ones immediately assigned to QML types.
++ for (const QQmlJSScope::ConstPtr &type : std::as_const(m_scopesByIrLocation))
++ bindings.insert(type, type->ownPropertyBindingsInQmlIROrder());
++
++ postVisitResolve(bindings);
++ setupAliases();
++
++ if (m_mode != Mode::Compile)
++ return;
++
++ findCppIncludes();
++
++ for (const QList<QQmlJSScope::ConstPtr> &qmlTypes : std::as_const(m_pureQmlTypes))
++ for (const QQmlJSScope::ConstPtr &type : qmlTypes)
++ checkNamesAndTypes(type);
++ }
++
++ bool Visitor::checkCustomParser(const QQmlJSScope::ConstPtr &scope)
++ {
++ if (QQmlJSImportVisitor::checkCustomParser(scope))
++ m_seenCustomParsers = true;
++ return false;
++ }
++
++ void Visitor::checkGroupedAndAttachedScopes()
++ {
++ for (const auto &scope : std::as_const(m_objectDefinitionScopes))
++ checkCustomParser(scope);
++ }
++
++ QQmlJSScope::ConstPtr fetchType(const QQmlJSMetaPropertyBinding &binding)
++ {
++ switch (binding.bindingType()) {
++ case QQmlSA::BindingType::Object:
++ return binding.objectType();
++ case QQmlSA::BindingType::Interceptor:
++ return binding.interceptorType();
++ case QQmlSA::BindingType::ValueSource:
++ return binding.valueSourceType();
++ case QQmlSA::BindingType::AttachedProperty:
++ return binding.attachedType();
++ case QQmlSA::BindingType::GroupedProperty:
++ return binding.groupType();
++ default:
++ return {};
++ }
++ Q_UNREACHABLE_RETURN({});
++ }
++
++ template<typename TypePredicate, typename BindingPredicate>
++ void iterateBindings(
++ const QQmlJSScope::ConstPtr &root,
++ const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> &qmlIrOrderedBindings,
++ TypePredicate typePredicate, BindingPredicate bindingPredicate)
++ {
++ // NB: depth-first-search is used here to mimic various QmlIR passes
++ QStack<QQmlJSScope::ConstPtr> types;
++ types.push(root);
++ while (!types.isEmpty()) {
++ auto current = types.pop();
++
++ if (typePredicate(current))
++ continue;
++
++ Q_ASSERT(qmlIrOrderedBindings.contains(current));
++ const auto &bindings = qmlIrOrderedBindings[current];
++ // reverse the binding order here, because stack processes right-most
++ // child first and we need left-most first
++ for (auto it = bindings.rbegin(); it != bindings.rend(); ++it) {
++ const auto &binding = *it;
++
++ if (bindingPredicate(current, binding))
++ continue;
++
++ if (auto type = fetchType(binding))
++ types.push(type);
++ }
++ }
++ }
++
++ template<typename Predicate>
++ void iterateTypes(
++ const QQmlJSScope::ConstPtr &root,
++ const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> &qmlIrOrderedBindings,
++ Predicate predicate)
++ {
++ iterateBindings(root, qmlIrOrderedBindings, [predicate](const QQmlJSScope::ConstPtr ¤t) {
++ return predicate(current) || isOrUnderComponent(current);
++ }, [](const QQmlJSScope::ConstPtr &, const QQmlJSMetaPropertyBinding &) {
++ return false;
++ });
++ }
++
++ /*! \internal
++ This is a special function that must be called after
++ QQmlJSImportVisitor::endVisit(QQmlJS::AST::UiProgram *). It is used to
++ resolve things that couldn't be resolved during the AST traversal, such
++ as anything that is dependent on implicit or explicit components
++ */
++ void Visitor::postVisitResolve(
++ const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> &qmlIrOrderedBindings)
++ {
++
++ // add the document root (that is not an inline component), as we usually
++ // want to iterate on all inline components, followed by the document root
++ m_inlineComponentNames.append(RootDocumentNameType());
++ m_inlineComponents[RootDocumentNameType()] = m_exportedRootScope;
++
++ // match scopes to indices of QmlIR::Object from QmlIR::Document
++ qsizetype count = 0;
++ const auto setIndex = [&](const QQmlJSScope::Ptr ¤t) {
++ if (current->scopeType() != QQmlSA::ScopeType::QMLScope || current->isArrayScope())
++ return;
++ Q_ASSERT(!m_qmlIrObjectIndices.contains(current));
++ m_qmlIrObjectIndices[current] = count;
++ ++count;
++ };
++ QQmlJSUtils::traverseFollowingQmlIrObjectStructure(m_exportedRootScope, setIndex);
++
++ // find types that are part of the deferred bindings (we care about *types*
++ // exclusively here)
++ QSet<QQmlJSScope::ConstPtr> deferredTypes;
++ const auto findDeferred = [&](const QQmlJSScope::ConstPtr &type,
++ const QQmlJSMetaPropertyBinding &binding) {
++ const QString propertyName = binding.propertyName();
++ Q_ASSERT(!propertyName.isEmpty());
++ if (type->isNameDeferred(propertyName)) {
++ m_typesWithDeferredBindings.insert(type);
++
++ if (binding.hasObject() || binding.hasInterceptor() || binding.hasValueSource()) {
++ deferredTypes.insert(fetchType(binding));
++ return true;
++ }
++ }
++ return false;
++ };
++ for (const auto &inlineComponentName : std::as_const(m_inlineComponentNames)) {
++ iterateBindings(m_inlineComponents[inlineComponentName], qmlIrOrderedBindings,
++ isOrUnderComponent, findDeferred);
++ }
++
++ const auto isOrUnderDeferred = [&deferredTypes](QQmlJSScope::ConstPtr type) {
++ for (; type; type = type->parentScope()) {
++ if (deferredTypes.contains(type))
++ return true;
++ }
++ return false;
++ };
++
++ // find all "pure" QML types
++ QList<QQmlJSScope::ConstPtr> explicitComponents;
++ for (qsizetype i = 0; i < m_qmlTypes.size(); ++i) {
++ const QQmlJSScope::ConstPtr &type = m_qmlTypes.at(i);
++
++ if (isOrUnderComponent(type) || isOrUnderDeferred(type)) {
++ // root is special: we compile Component roots. root is also never
++ // deferred, so in case `isOrUnderDeferred(type)` returns true, we
++ // always continue here
++ if (type != m_exportedRootScope) {
++ // if a type is an explicit component, its id "leaks" into the
++ // document context
++ if (isExplicitComponent(type))
++ explicitComponents.append(type);
++ continue;
++ }
++ }
++
++ const InlineComponentOrDocumentRootName inlineComponent =
++ type->enclosingInlineComponentName();
++ QList<QQmlJSScope::ConstPtr> &pureQmlTypes = m_pureQmlTypes[inlineComponent];
++ m_creationIndices[type] = pureQmlTypes.size();
++ pureQmlTypes.append(type);
++ }
++
++ // update the typeCounts
++ for (const auto &inlineComponent : std::as_const(m_inlineComponentNames)) {
++ m_inlineComponentTypeCount[inlineComponent] = m_pureQmlTypes[inlineComponent].size();
++ }
++
++ // add explicit components to the object creation indices
++ {
++ QHash<InlineComponentOrDocumentRootName, qsizetype> index;
++ for (const QQmlJSScope::ConstPtr &c : std::as_const(explicitComponents)) {
++ const InlineComponentOrDocumentRootName inlineComponent =
++ c->enclosingInlineComponentName();
++ m_creationIndices[c] =
++ m_pureQmlTypes[inlineComponent].size() + index[inlineComponent]++;
++ m_inlineComponentTypeCount[inlineComponent]++;
++ }
++ }
++
++ // m_qmlTypesWithQmlBases should contain the types to be compiled.
++ // Some types should not be compiled and are therefore filtered out:
++ // * deferred types
++ // * types inside of capital-c-Components (implicit and explicit)
++ // * non-composite types (that is, types not defined in qml)
++ //
++ // This can not be done earlier as implicitly wrapped Components are
++ // only known after visitation is over!
++
++ // filter step:
++ for (const auto &inlineComponent : std::as_const(m_inlineComponentNames)) {
++ QList<QQmlJSScope::ConstPtr> filteredQmlTypesWithQmlBases;
++ QList<QQmlJSScope::ConstPtr> &unfilteredQmlTypesWithQmlBases =
++ m_qmlTypesWithQmlBases[inlineComponent];
++ filteredQmlTypesWithQmlBases.reserve(unfilteredQmlTypesWithQmlBases.size());
++ std::copy_if(unfilteredQmlTypesWithQmlBases.cbegin(), unfilteredQmlTypesWithQmlBases.cend(),
++ std::back_inserter(filteredQmlTypesWithQmlBases),
++ [&](const QQmlJSScope::ConstPtr &type) {
++ auto base = type->baseType();
++ return base && base->isComposite() && !isOrUnderComponent(type)
++ && !isOrUnderDeferred(type);
++ });
++ qSwap(unfilteredQmlTypesWithQmlBases, filteredQmlTypesWithQmlBases);
++ }
++
++ // count QmlIR::Objects in the document - the amount is used to calculate
++ // object indices of implicit components
++ QHash<InlineComponentOrDocumentRootName, qsizetype> qmlScopeCount;
++ const auto countQmlScopes = [&](const QQmlJSScope::ConstPtr &scope) {
++ if (scope->isArrayScope()) // special kind of QQmlJSScope::QMLScope
++ return;
++ switch (scope->scopeType()) {
++ case QQmlSA::ScopeType::QMLScope:
++ case QQmlSA::ScopeType::GroupedPropertyScope:
++ case QQmlSA::ScopeType::AttachedPropertyScope: {
++ ++qmlScopeCount[scope->enclosingInlineComponentName()];
++ break;
++ }
++ default:
++ return;
++ }
++ return;
++ };
++ // order doesn't matter (so re-use QQmlJSUtils)
++ QQmlJSUtils::traverseFollowingQmlIrObjectStructure(m_exportedRootScope, countQmlScopes);
++
++ // figure synthetic indices of QQmlComponent-wrapped types
++ int syntheticCreationIndex;
++ const auto addSyntheticIndex = [&](const QQmlJSScope::ConstPtr &type) {
++ // explicit component
++ if (isExplicitComponent(type)) {
++ m_syntheticTypeIndices[type] = m_qmlIrObjectIndices.value(type, -1);
++ return true;
++ }
++ // implicit component
++ if (isImplicitComponent(type)) {
++ const int index =
++ qmlScopeCount[type->enclosingInlineComponentName()] + syntheticCreationIndex++;
++ m_syntheticTypeIndices[type] = index;
++ return true;
++ }
++ return false;
++ };
++
++ for (const auto &inlineComponentName : std::as_const(m_inlineComponentNames)) {
++ syntheticCreationIndex = 0; // reset for each inline component
++ iterateTypes(m_inlineComponents[inlineComponentName], qmlIrOrderedBindings,
++ addSyntheticIndex);
++ }
++
++ // figure runtime object ids for non-component wrapped types
++ int currentId;
++ const auto setRuntimeId = [&](const QQmlJSScope::ConstPtr &type) {
++ // any type wrapped in an implicit component shouldn't be processed
++ // here. even if it has id, it doesn't need to be set by qmltc
++ if (type->isInlineComponent()) {
++ // explicit inline component
++ } else if (type->isFileRootComponent()) {
++ // explicit root component
++ } else if (type->componentRootStatus() != QQmlJSScope::IsComponentRoot::No) {
++ // Wrapped in implicit component, assigned to unknown property, or child of scope
++ // called "QQmlComponent". We consider this an "implicit component".
++ return true;
++ }
++
++ if (m_typesWithId.contains(type)) {
++ m_typesWithId[type] = currentId++;
++ }
++
++ return false;
++ };
++
++ for (const auto &inlineComponentName : std::as_const(m_inlineComponentNames)) {
++ currentId = 0; // reset for each inline component
++ iterateTypes(m_inlineComponents[inlineComponentName], qmlIrOrderedBindings, setRuntimeId);
++ }
++ }
++
++ static void setAliasData(QQmlJSMetaProperty *alias, const QQmlJSUtils::ResolvedAlias &origin)
++ {
++ Q_ASSERT(origin.kind != QQmlJSUtils::AliasTarget_Invalid);
++ PropertyData compiledData(*alias);
++ if (alias->read().isEmpty())
++ alias->setRead(compiledData.read);
++ if (origin.kind == QQmlJSUtils::AliasTarget_Object) // id-pointing aliases only have READ method
++ return;
++ if (origin.property.isWritable() && alias->write().isEmpty())
++ alias->setWrite(compiledData.write);
++
++ // the engine always compiles a notify for properties/aliases defined in qml code
++ // Yes, this generated notify will never be emitted.
++ if (alias->notify().isEmpty())
++ alias->setNotify(compiledData.notify);
++
++ if (!origin.property.bindable().isEmpty() && alias->bindable().isEmpty())
++ alias->setBindable(compiledData.bindable);
++ }
++
++ void Visitor::setupAliases()
++ {
++ QStack<QQmlJSScope::Ptr> types;
++ types.push(m_exportedRootScope);
++
++ while (!types.isEmpty()) {
++ QQmlJSScope::Ptr current = types.pop();
++ auto properties = current->ownProperties();
++
++ for (QQmlJSMetaProperty &p : properties) {
++ if (!p.isAlias())
++ continue;
++
++ auto result = QQmlJSUtils::resolveAlias(m_scopesById, p, current,
++ QQmlJSUtils::AliasResolutionVisitor {});
++ if (result.kind == QQmlJSUtils::AliasTarget_Invalid) {
++ m_logger->log(QStringLiteral("Cannot resolve alias \"%1\"").arg(p.propertyName()),
++ qmlUnresolvedAlias, current->sourceLocation());
++ continue;
++ }
++ setAliasData(&p, result);
++ current->addOwnProperty(p);
++ }
++ }
++ }
++
++ void Visitor::checkNamesAndTypes(const QQmlJSScope::ConstPtr &type)
++ {
++ static const QString cppKeywords[] = {
++ u"alignas"_s,
++ u"alignof"_s,
++ u"and"_s,
++ u"and_eq"_s,
++ u"asm"_s,
++ u"atomic_cancel"_s,
++ u"atomic_commit"_s,
++ u"atomic_noexcept"_s,
++ u"auto"_s,
++ u"bitand"_s,
++ u"bitor"_s,
++ u"bool"_s,
++ u"break"_s,
++ u"case"_s,
++ u"catch"_s,
++ u"char"_s,
++ u"char16_t"_s,
++ u"char32_t"_s,
++ u"char8_t"_s,
++ u"class"_s,
++ u"co_await"_s,
++ u"co_return"_s,
++ u"co_yield"_s,
++ u"compl"_s,
++ u"concept"_s,
++ u"const"_s,
++ u"const_cast"_s,
++ u"consteval"_s,
++ u"constexpr"_s,
++ u"constinit"_s,
++ u"continue"_s,
++ u"decltype"_s,
++ u"default"_s,
++ u"delete"_s,
++ u"do"_s,
++ u"double"_s,
++ u"dynamic_cast"_s,
++ u"else"_s,
++ u"enum"_s,
++ u"explicit"_s,
++ u"export"_s,
++ u"extern"_s,
++ u"false"_s,
++ u"float"_s,
++ u"for"_s,
++ u"friend"_s,
++ u"goto"_s,
++ u"if"_s,
++ u"inline"_s,
++ u"int"_s,
++ u"long"_s,
++ u"mutable"_s,
++ u"namespace"_s,
++ u"new"_s,
++ u"noexcept"_s,
++ u"not"_s,
++ u"not_eq"_s,
++ u"nullptr"_s,
++ u"operator"_s,
++ u"or"_s,
++ u"or_eq"_s,
++ u"private"_s,
++ u"protected"_s,
++ u"public"_s,
++ u"reflexpr"_s,
++ u"register"_s,
++ u"reinterpret_cast"_s,
++ u"requires"_s,
++ u"return"_s,
++ u"short"_s,
++ u"signed"_s,
++ u"sizeof"_s,
++ u"static"_s,
++ u"static_assert"_s,
++ u"static_cast"_s,
++ u"struct"_s,
++ u"switch"_s,
++ u"synchronized"_s,
++ u"template"_s,
++ u"this"_s,
++ u"thread_local"_s,
++ u"throw"_s,
++ u"true"_s,
++ u"try"_s,
++ u"typedef"_s,
++ u"typeid"_s,
++ u"typename"_s,
++ u"union"_s,
++ u"unsigned"_s,
++ u"using"_s,
++ u"virtual"_s,
++ u"void"_s,
++ u"volatile"_s,
++ u"wchar_t"_s,
++ u"while"_s,
++ u"xor"_s,
++ u"xor_eq"_s,
++ };
++ Q_ASSERT(std::is_sorted(std::begin(cppKeywords), std::end(cppKeywords)));
++
++ const auto isReserved = [&](QStringView word) {
++ if (word.startsWith(QChar(u'_')) && word.size() >= 2
++ && (word[1].isUpper() || word[1] == QChar(u'_'))) {
++ return true; // Identifiers starting with underscore and uppercase are reserved in C++
++ }
++ return std::binary_search(std::begin(cppKeywords), std::end(cppKeywords), word);
++ };
++
++ const auto validate = [&](QStringView name, QStringView errorPrefix) {
++ if (!isReserved(name))
++ return;
++ m_logger->log(errorPrefix + u" '" + name + u"' is a reserved C++ word, consider renaming",
++ qmlCompiler, type->sourceLocation());
++ };
++
++ const auto validateType = [&type, this](const QQmlJSScope::ConstPtr &typeToCheck,
++ QStringView name, QStringView errorPrefix) {
++ if (typeToCheck.isNull()) {
++ m_logger->log(
++ QStringLiteral(
++ "Can't compile the %1 type \"%2\" to C++ because it cannot be resolved")
++ .arg(errorPrefix, name),
++ qmlCompiler, type->sourceLocation());
++ return;
++ }
++
++ if (type->moduleName().isEmpty())
++ return;
++
++ if (typeToCheck->isComposite() && typeToCheck->moduleName() != type->moduleName()) {
++ m_logger->log(
++ QStringLiteral(
++ "Can't compile the %1 type \"%2\" to C++ because it "
++ "lives in \"%3\" instead of the current file's \"%4\" QML module.")
++ .arg(errorPrefix, name, typeToCheck->moduleName(), type->moduleName()),
++ qmlCompiler, type->sourceLocation());
++ }
++ };
++
++ validateType(type->baseType(), type->baseTypeName(), u"QML base");
++
++ const auto enums = type->ownEnumerations();
++ for (auto it = enums.cbegin(); it != enums.cend(); ++it) {
++ const QQmlJSMetaEnum e = it.value();
++ validate(e.name(), u"Enumeration");
++
++ const auto enumKeys = e.keys();
++ for (const auto &key : enumKeys)
++ validate(key, u"Enumeration '%1' key"_s.arg(e.name()));
++ }
++
++ const auto properties = type->ownProperties();
++ for (auto it = properties.cbegin(); it != properties.cend(); ++it) {
++ const QQmlJSMetaProperty &p = it.value();
++ validate(p.propertyName(), u"Property");
++
++ if (!p.isAlias() && !p.typeName().isEmpty())
++ validateType(p.type(), p.typeName(), u"QML property");
++ }
++
++ const auto methods = type->ownMethods();
++ for (auto it = methods.cbegin(); it != methods.cend(); ++it) {
++ const QQmlJSMetaMethod &m = it.value();
++ validate(m.methodName(), u"Method");
++ if (!m.returnTypeName().isEmpty())
++ validateType(m.returnType(), m.returnTypeName(), u"QML method return");
++
++ for (const auto ¶meter : m.parameters()) {
++ validate(parameter.name(), u"Method '%1' parameter"_s.arg(m.methodName()));
++ if (!parameter.typeName().isEmpty())
++ validateType(parameter.type(), parameter.typeName(), u"QML parameter");
++ }
++ }
++
++ // TODO: one could also test signal handlers' parameters but we do not store
++ // this information in QQmlJSMetaPropertyBinding currently
++ }
++
++ /*! \internal
++ * Returns the file path for the C++ header of \a scope or the header created
++ * by qmltc for it and its inline components.
++ */
++ QString Visitor::filePath(const QQmlJSScope::ConstPtr &scope) const
++ {
++ const QString filePath = scope->filePath();
++ if (!filePath.endsWith(u".qml")) // assume the correct path is set
++ return scope->filePath();
++
++ const QString correctedFilePath = sourceDirectoryPath(filePath);
++ const QStringList paths = m_importer->resourceFileMapper()->resourcePaths(
++ QQmlJSResourceFileMapper::localFileFilter(correctedFilePath));
++ auto firstHeader = std::find_if(paths.cbegin(), paths.cend(),
++ [](const QString &x) { return x.endsWith(u".h"_s); });
++ if (firstHeader == paths.cend()) {
++ const QString matchedPaths = paths.isEmpty() ? u"<none>"_s : paths.join(u", ");
++ qCDebug(lcQmltcCompiler,
++ "Failed to find a header file name for path %s. Paths checked:\n%s",
++ correctedFilePath.toUtf8().constData(), matchedPaths.toUtf8().constData());
++ return QString();
++ }
++ // NB: get the file name to avoid prefixes
++ return QFileInfo(*firstHeader).fileName();
++ }
++
++ QString Visitor::sourceDirectoryPath(const QString &path) const
++ {
++ auto result = QQmlJSUtils::sourceDirectoryPath(m_importer, path);
++ if (const QString *srcDirPath = std::get_if<QString>(&result))
++ return *srcDirPath;
++
++ const QQmlJS::DiagnosticMessage *error = std::get_if<QQmlJS::DiagnosticMessage>(&result);
++ Q_ASSERT(error);
++ qCDebug(lcQmltcCompiler, "%s", error->message.toUtf8().constData());
++ // return input as a fallback
++ return path;
++ }
++
++ } // namespace QQmltc
++
++ QT_END_NAMESPACE