Revision control
++ // Copyright (C) 2019 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 "qqmljscontextualtypes_p.h"
++ #include "qqmljsscope_p.h"
++ #include "qqmljstypereader_p.h"
++ #include "qqmljsimporter_p.h"
++ #include "qqmljsutils_p.h"
++ #include "qqmlsa.h"
++
++ #include <QtCore/qqueue.h>
++ #include <QtCore/qsharedpointer.h>
++
++ #include <private/qduplicatetracker_p.h>
++
++ #include <algorithm>
++
++ QT_BEGIN_NAMESPACE
++
++ /*!
++ \class QQmlJSScope
++ \internal
++ \brief Tracks the types for the QmlCompiler
++
++ QQmlJSScope tracks the types used in qml for the QmlCompiler.
++
++ Multiple QQmlJSScope objects might be created for the same conceptual type, except when reused
++ due to extensive caching. Two QQmlJSScope objects are considered equal when they are backed
++ by the same implementation, that is, they have the same internalName.
++ */
++
++ using namespace Qt::StringLiterals;
++
++ QQmlJSScope::QQmlJSScope(const QString &internalName) : QQmlJSScope{}
++ {
++ m_internalName = internalName;
++ }
++
++ void QQmlJSScope::reparent(const QQmlJSScope::Ptr &parentScope, const QQmlJSScope::Ptr &childScope)
++ {
++ if (const QQmlJSScope::Ptr parent = childScope->m_parentScope.toStrongRef())
++ parent->m_childScopes.removeOne(childScope);
++ if (parentScope)
++ parentScope->m_childScopes.append(childScope);
++ childScope->m_parentScope = parentScope;
++ }
++
++ /*!
++ \internal
++ Prepares the scope to be used by QQmlJSImportVisitor: we don't want to have "left-overs" from a
++ potential previous import visitor run, so remove all information except for the information set
++ by the qqmljsimporter (internalName, moduleName and isSingleton). Remove the scope's factory to
++ avoid populating the scope via lazy-loading, if there is one.
++
++ Warning: All pre-existing references to scope's children become invalid after this method was called.
++ For example, a weak QQmlJSMetaProperty::type pointer pointing to a child of scope becomes invalid
++ after resetForReparse was called. This is fine for snippets (that are never referenced by other files),
++ and might cause bogus warnings (missing type for example) otherwise.
++ */
++ const QQmlJSScope::Ptr &QQmlJSScope::resetForReparse(const Ptr &scope)
++ {
++ auto *factory = scope.factory();
++ if (!factory) {
++ const QString moduleName = scope->moduleName();
++ const bool isSingleton = scope->isSingleton();
++ const QString filePath = scope->filePath();
++ *scope = QQmlJSScope{ scope->internalName() };
++ scope->setOwnModuleName(moduleName);
++ scope->setIsSingleton(isSingleton);
++ scope->setFilePath(filePath);
++ return scope;
++ }
++ // we are about to reparse the file belonging to this scope,
++ // so remove any factory to avoid populating the scope twice.
++ const QString moduleName = factory->moduleName();
++ const QString internalName = factory->internalName();
++ const bool isSingleton = factory->isSingleton();
++ const QString filePath = factory->filePath();
++ *scope.factory() = QQmlJSScope::ConstPtr::Factory{ };
++ scope->setFilePath(filePath);
++ scope->setOwnModuleName(moduleName);
++ scope->setInternalName(internalName);
++ scope->setIsSingleton(isSingleton);
++ return scope;
++ }
++
++ /*!
++ \internal
++ Return all the JavaScript identifiers defined in the current scope.
++ */
++ QHash<QString, QQmlJSScope::JavaScriptIdentifier> QQmlJSScope::ownJSIdentifiers() const
++ {
++ return m_jsIdentifiers;
++ }
++
++ void QQmlJSScope::insertJSIdentifier(const QString &name, const JavaScriptIdentifier &identifier)
++ {
++ Q_ASSERT(m_scopeType != QQmlSA::ScopeType::QMLScope);
++ if (identifier.kind == JavaScriptIdentifier::LexicalScoped
++ || identifier.kind == JavaScriptIdentifier::Injected
++ || QQmlSA::isFunctionScope(m_scopeType)) {
++ m_jsIdentifiers.insert(name, identifier);
++ } else {
++ auto targetScope = parentScope();
++ while (targetScope->m_scopeType != QQmlSA::ScopeType::JSFunctionScope)
++ targetScope = targetScope->parentScope();
++ targetScope->m_jsIdentifiers.insert(name, identifier);
++ }
++ }
++
++ void QQmlJSScope::setLineNumber(quint32 lineNumber)
++ {
++ m_sourceLocation.startLine = lineNumber;
++ // also set the startColumn to make the QQmlJSSourceLocation usable
++ m_sourceLocation.startColumn = 1;
++ }
++
++ void QQmlJSScope::setLineNumberInResolvedFile(quint32 lineNumber)
++ {
++ m_lineNumberInResolvedFile = lineNumber;
++ }
++
++ bool QQmlJSScope::hasMethod(const QString &name) const
++ {
++ return QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) {
++ if (mode == QQmlJSScope::ExtensionNamespace)
++ return false;
++ return scope->m_methods.contains(name);
++ });
++ }
++
++ /*!
++ Returns all methods visible from this scope including those of
++ base types and extensions.
++
++ \note Methods that get shadowed are not included and only the
++ version visible from this scope is contained. Additionally method
++ overrides are not included either, only the first visible version
++ of any method is included.
++ */
++ QHash<QString, QQmlJSMetaMethod> QQmlJSScope::methods() const
++ {
++ QHash<QString, QQmlJSMetaMethod> results;
++ QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) {
++ if (mode == QQmlJSScope::ExtensionNamespace)
++ return false;
++ for (auto it = scope->m_methods.constBegin(); it != scope->m_methods.constEnd();
++ it++) {
++ if (!results.contains(it.key()))
++ results.insert(it.key(), it.value());
++ }
++ return false;
++ });
++
++ return results;
++ }
++
++ QList<QQmlJSMetaMethod> QQmlJSScope::methods(const QString &name) const
++ {
++ QList<QQmlJSMetaMethod> results;
++
++ QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) {
++ if (mode == QQmlJSScope::ExtensionNamespace)
++ return false;
++ results.append(scope->ownMethods(name));
++ return false;
++ });
++ return results;
++ }
++
++ QList<QQmlJSMetaMethod> QQmlJSScope::methods(const QString &name, QQmlJSMetaMethodType type) const
++ {
++ QList<QQmlJSMetaMethod> results;
++
++ QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) {
++ if (mode == QQmlJSScope::ExtensionNamespace)
++ return false;
++ const auto ownMethods = scope->ownMethods(name);
++ for (const auto &method : ownMethods) {
++ if (method.methodType() == type)
++ results.append(method);
++ }
++ return false;
++ });
++ return results;
++ }
++
++ bool QQmlJSScope::hasEnumeration(const QString &name) const
++ {
++ return QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope) { return scope->m_enumerations.contains(name); });
++ }
++
++ bool QQmlJSScope::hasOwnEnumerationKey(const QString &name) const
++ {
++ for (const auto &e : m_enumerations) {
++ if (e.keys().contains(name))
++ return true;
++ }
++ return false;
++ }
++
++ bool QQmlJSScope::hasEnumerationKey(const QString &name) const
++ {
++ return QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope) { return scope->hasOwnEnumerationKey(name); });
++ }
++
++ QQmlJSMetaEnum QQmlJSScope::enumeration(const QString &name) const
++ {
++ QQmlJSMetaEnum result;
++
++ QQmlJSUtils::searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
++ const auto it = scope->m_enumerations.find(name);
++ if (it == scope->m_enumerations.end())
++ return false;
++ result = *it;
++ return true;
++ });
++
++ return result;
++ }
++
++ QHash<QString, QQmlJSMetaEnum> QQmlJSScope::enumerations() const
++ {
++ QHash<QString, QQmlJSMetaEnum> results;
++
++ QQmlJSUtils::searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
++ for (auto it = scope->m_enumerations.constBegin(); it != scope->m_enumerations.constEnd();
++ it++) {
++ if (!results.contains(it.key()))
++ results.insert(it.key(), it.value());
++ }
++ return false;
++ });
++
++ return results;
++ }
++
++ QString QQmlJSScope::augmentedInternalName() const
++ {
++ using namespace Qt::StringLiterals;
++ Q_ASSERT(!m_internalName.isEmpty());
++
++ switch (m_semantics) {
++ case AccessSemantics::Reference:
++ return m_internalName + " *"_L1;
++ case AccessSemantics::Value:
++ case AccessSemantics::Sequence:
++ break;
++ case AccessSemantics::None:
++ // If we got a namespace, it might still be a regular type, exposed as namespace.
++ // We may need to travel the inheritance chain all the way up to QObject to
++ // figure this out, since all other types may be exposed the same way.
++ for (QQmlJSScope::ConstPtr base = baseType(); base; base = base->baseType()) {
++ switch (base->accessSemantics()) {
++ case AccessSemantics::Reference:
++ return m_internalName + " *"_L1;
++ case AccessSemantics::Value:
++ case AccessSemantics::Sequence:
++ return m_internalName;
++ case AccessSemantics::None:
++ break;
++ }
++ }
++ break;
++ }
++ return m_internalName;
++ }
++
++ QString QQmlJSScope::prettyName(QAnyStringView name)
++ {
++ const auto internal = "$internal$."_L1;
++ const QString anonymous = "$anonymous$."_L1;
++
++ QString pretty = name.toString();
++
++ if (pretty.startsWith(internal))
++ pretty = pretty.mid(internal.size());
++ else if (pretty.startsWith(anonymous))
++ pretty = pretty.mid(anonymous.size());
++
++ if (pretty == u"std::nullptr_t")
++ return u"null"_s;
++
++ if (pretty == u"void")
++ return u"undefined"_s;
++
++ return pretty;
++ }
++
++ /*!
++ \internal
++
++ Returns \c Yes if the scope is the outermost element of a separate Component. Either:
++ a, It is the root element of a QML document
++ b, It is an inline component
++ c, It has been implicitly wrapped, e.g. due to an assignment to a Component property
++ d, It is the first (and only) child of a Component
++
++ Returns \c No if we can clearly determine that this is not the case.
++ Returns \c Maybe if the scope is assigned to an unknown property. This may
++ or may not be a Component.
++
++ For visitors: This method should only be called after implicit components
++ are detected, that is, after QQmlJSImportVisitor::endVisit(UiProgram *)
++ was called.
++ */
++ QQmlJSScope::IsComponentRoot QQmlJSScope::componentRootStatus() const {
++ if (m_flags.testAnyFlags(
++ Flags(WrappedInImplicitComponent | FileRootComponent | InlineComponent))) {
++ return IsComponentRoot::Yes;
++ }
++
++ // If the object is assigned to an unknown property, assume it's Component.
++ if (m_flags.testFlag(AssignedToUnknownProperty))
++ return IsComponentRoot::Maybe;
++
++ auto base = nonCompositeBaseType(parentScope()); // handles null parentScope()
++ if (!base)
++ return IsComponentRoot::No;
++ return base->internalName() == u"QQmlComponent"
++ ? IsComponentRoot::Yes
++ : IsComponentRoot::No;
++ }
++
++ std::optional<QQmlJSScope::JavaScriptIdentifier>
++ QQmlJSScope::jsIdentifier(const QString &id) const
++ {
++ for (const auto *scope = this; scope; scope = scope->parentScope().data()) {
++ if (QQmlSA::isFunctionScope(scope->m_scopeType)
++ || scope->m_scopeType == QQmlSA::ScopeType::JSLexicalScope) {
++ auto it = scope->m_jsIdentifiers.find(id);
++ if (it != scope->m_jsIdentifiers.end())
++ return *it;
++ }
++ }
++
++ return std::optional<JavaScriptIdentifier>{};
++ }
++
++ std::optional<QQmlJSScope::JavaScriptIdentifier> QQmlJSScope::ownJSIdentifier(const QString &id) const
++ {
++ auto it = m_jsIdentifiers.find(id);
++ if (it != m_jsIdentifiers.end())
++ return *it;
++
++ return std::optional<JavaScriptIdentifier>{};
++ }
++
++ static QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr>
++ qFindInlineComponents(QStringView typeName, const QQmlJS::ContextualTypes &contextualTypes)
++ {
++ const int separatorIndex = typeName.lastIndexOf(u'.');
++ // do not crash in typeName.sliced() when it starts or ends with an '.'.
++ if (separatorIndex < 1 || separatorIndex >= typeName.size() - 1)
++ return {};
++
++ const auto parentIt = contextualTypes.types().constFind(typeName.first(separatorIndex).toString());
++ if (parentIt == contextualTypes.types().constEnd())
++ return {};
++
++ auto inlineComponentParent = *parentIt;
++
++ // find the inline components using BFS, as inline components defined in childrens are also
++ // accessible from other qml documents. Same for inline components defined in a base class of
++ // the parent. Use BFS over DFS as the inline components are probably not deeply-nested.
++
++ QStringView inlineComponentName = typeName.sliced(separatorIndex + 1);
++ QQueue<QQmlJSScope::ConstPtr> candidatesForInlineComponents;
++ candidatesForInlineComponents.enqueue(inlineComponentParent.scope);
++ while (candidatesForInlineComponents.size()) {
++ QQmlJSScope::ConstPtr current = candidatesForInlineComponents.dequeue();
++ if (!current) // if some type was not resolved, ignore it instead of crashing
++ continue;
++ if (current->isInlineComponent() && current->inlineComponentName() == inlineComponentName) {
++ return { current, inlineComponentParent.revision };
++ }
++
++ // check alternatively the inline components at layer 1 in current and basetype, then at
++ // layer 2, etc...
++ const auto &childScopes = current->childScopes();
++ for (const auto &child : childScopes) {
++ if (child->scopeType() == QQmlSA::ScopeType::QMLScope)
++ candidatesForInlineComponents.enqueue(child);
++ }
++
++ if (const auto base = current->baseType())
++ candidatesForInlineComponents.enqueue(base);
++ }
++ return {};
++ }
++
++ /*! \internal
++ * Finds a type in contextualTypes with given name.
++ * If a type is found, then its name is inserted into usedTypes (when provided).
++ * If contextualTypes has mode INTERNAl, then namespace resolution for enums is
++ * done (eg for Qt::Alignment).
++ * If contextualTypes has mode QML, then inline component resolution is done
++ * ("qmlFileName.IC" is correctly resolved from qmlFileName).
++ */
++ QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr> QQmlJSScope::findType(
++ const QString &name, const QQmlJS::ContextualTypes &contextualTypes,
++ QSet<QString> *usedTypes)
++ {
++ const auto useType = [&]() {
++ if (usedTypes != nullptr)
++ usedTypes->insert(name);
++ };
++
++ auto type = contextualTypes.types().constFind(name);
++ const QString currentSelector = contextualTypes.currentFileSelector();
++
++ if (type != contextualTypes.types().constEnd()) {
++ // fast path: no selector context active
++ if (currentSelector.isEmpty()) {
++ useType();
++ return *type;
++ }
++ // selector active: discard a mismatched promoted file-selected variant
++ const QString typeSelector = QQmlJSUtils::fileSelectorFor(type->scope);
++ if (typeSelector.isEmpty() || typeSelector == currentSelector) {
++ useType();
++ return *type;
++ }
++ if (auto matching = contextualTypes.fileSelectedTypeFor(name, currentSelector)) {
++ useType();
++ return *matching;
++ }
++ } else if (!currentSelector.isEmpty()) {
++ // try a file-selected variant matching the current selector
++ if (auto matching = contextualTypes.fileSelectedTypeFor(name, currentSelector)) {
++ useType();
++ return *matching;
++ }
++ }
++
++ const auto findListType = [&](const QString &prefix, const QString &postfix)
++ -> ImportedScope<ConstPtr> {
++ if (name.startsWith(prefix) && name.endsWith(postfix)) {
++ const qsizetype prefixLength = prefix.length();
++ const QString &elementName
++ = name.mid(prefixLength, name.length() - prefixLength - postfix.length());
++ const ImportedScope<ConstPtr> element
++ = findType(elementName, contextualTypes, usedTypes);
++ if (element.scope) {
++ useType();
++ return { element.scope->listType(), element.revision };
++ }
++ }
++
++ return {};
++ };
++
++ switch (contextualTypes.context()) {
++ case QQmlJS::ContextualTypes::INTERNAL: {
++ if (const auto listType = findListType(u"QList<"_s, u">"_s);
++ listType.scope && !listType.scope->isReferenceType()) {
++ return listType;
++ }
++
++ if (const auto listType = findListType(u"QQmlListProperty<"_s, u">"_s);
++ listType.scope && listType.scope->isReferenceType()) {
++ return listType;
++ }
++
++ // look for c++ namescoped enums!
++ const auto colonColon = name.lastIndexOf(QStringLiteral("::"));
++ if (colonColon == -1)
++ break;
++
++ const QString outerTypeName = name.left(colonColon);
++ const auto outerType = contextualTypes.types().constFind(outerTypeName);
++ if (outerType == contextualTypes.types().constEnd())
++ break;
++
++ for (const auto &innerType : std::as_const(outerType->scope->m_childScopes)) {
++ if (innerType->m_internalName == name) {
++ useType();
++ return { innerType, outerType->revision };
++ }
++ }
++
++ break;
++ }
++ case QQmlJS::ContextualTypes::QML: {
++ // look after inline components
++ const auto inlineComponent = qFindInlineComponents(name, contextualTypes);
++ if (inlineComponent.scope) {
++ useType();
++ return inlineComponent;
++ }
++
++ if (const auto listType = findListType(u"list<"_s, u">"_s); listType.scope)
++ return listType;
++
++ break;
++ }
++ }
++ return {};
++ }
++
++ QTypeRevision QQmlJSScope::resolveType(
++ const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &context,
++ QSet<QString> *usedTypes)
++ {
++ if (self->accessSemantics() == AccessSemantics::Sequence
++ && self->internalName().startsWith(u"QQmlListProperty<"_s)) {
++ self->setIsListProperty(true);
++ }
++
++ const QString baseTypeName = self->baseTypeName();
++ const auto baseType = findType(baseTypeName, context, usedTypes);
++ if (!self->m_baseType.scope && !baseTypeName.isEmpty())
++ self->m_baseType = { baseType.scope, baseType.revision };
++
++ if (!self->m_attachedType && !self->m_attachedTypeName.isEmpty())
++ self->m_attachedType = findType(self->m_attachedTypeName, context, usedTypes).scope;
++
++ if (!self->m_elementType && !self->m_elementTypeName.isEmpty())
++ self->m_elementType = findType(self->m_elementTypeName, context, usedTypes).scope;
++
++ if (!self->m_extensionType) {
++ if (self->m_extensionTypeName.isEmpty()) {
++ if (self->accessSemantics() == AccessSemantics::Sequence) {
++ // All sequence types are implicitly extended by JS Array.
++ self->setExtensionTypeName(u"Array"_s);
++ self->setExtensionIsJavaScript(true);
++ self->m_extensionType = context.arrayType();
++ }
++ } else {
++ self->m_extensionType = findType(self->m_extensionTypeName, context, usedTypes).scope;
++ }
++ }
++
++
++ for (auto it = self->m_properties.begin(), end = self->m_properties.end(); it != end; ++it) {
++ const QString typeName = it->typeName();
++ if (it->type() || typeName.isEmpty())
++ continue;
++
++ if (const auto type = findType(typeName, context, usedTypes); type.scope) {
++ it->setType(it->isList() ? type.scope->listType() : type.scope);
++ continue;
++ }
++
++ const auto enumeration = self->m_enumerations.find(typeName);
++ if (enumeration != self->m_enumerations.end()) {
++ it->setType(it->isList()
++ ? enumeration->type()->listType()
++ : QQmlJSScope::ConstPtr(enumeration->type()));
++ }
++ }
++
++ const auto resolveParameter = [&](QQmlJSMetaParameter ¶meter) {
++ if (const QString typeName = parameter.typeName();
++ !parameter.type() && !typeName.isEmpty()) {
++ auto type = findType(typeName, context, usedTypes);
++ if (type.scope && parameter.isList()) {
++ type.scope = type.scope->listType();
++ parameter.setIsList(false);
++ parameter.setIsPointer(false);
++ parameter.setTypeName(type.scope ? type.scope->internalName() : QString());
++ } else if (type.scope && type.scope->isReferenceType()) {
++ parameter.setIsPointer(true);
++ }
++ parameter.setType({ type.scope });
++ }
++ };
++
++ for (auto it = self->m_methods.begin(), end = self->m_methods.end(); it != end; ++it) {
++ auto returnValue = it->returnValue();
++ resolveParameter(returnValue);
++ it->setReturnValue(returnValue);
++
++ auto parameters = it->parameters();
++ for (int i = 0, length = parameters.size(); i < length; ++i)
++ resolveParameter(parameters[i]);
++ it->setParameters(parameters);
++ }
++
++ for (auto it = self->m_jsIdentifiers.begin(); it != self->m_jsIdentifiers.end(); ++it) {
++ if (it->typeName)
++ it->scope = findType(it->typeName.value(), context, usedTypes).scope;
++ }
++
++ return baseType.revision;
++ }
++
++ void QQmlJSScope::updateChildScope(
++ const QQmlJSScope::Ptr &childScope, const QQmlJSScope::Ptr &self,
++ const QQmlJS::ContextualTypes &contextualTypes, QSet<QString> *usedTypes)
++ {
++ switch (childScope->scopeType()) {
++ case QQmlSA::ScopeType::GroupedPropertyScope:
++ QQmlJSUtils::searchBaseAndExtensionTypes(
++ self.data(), [&](const QQmlJSScope *type, QQmlJSScope::ExtensionKind mode) {
++ if (mode == QQmlJSScope::ExtensionNamespace)
++ return false;
++ const auto propertyIt = type->m_properties.find(childScope->internalName());
++ if (propertyIt != type->m_properties.end()) {
++ childScope->m_baseType.scope = QQmlJSScope::ConstPtr(propertyIt->type());
++ if (propertyIt->type())
++ childScope->m_semantics = propertyIt->type()->accessSemantics();
++ childScope->setBaseTypeName(propertyIt->typeName());
++ return true;
++ }
++ return false;
++ });
++ break;
++ case QQmlSA::ScopeType::AttachedPropertyScope:
++ if (const auto attachedBase = findType(
++ childScope->internalName(), contextualTypes, usedTypes).scope) {
++ childScope->m_baseType.scope = attachedBase->attachedType();
++ childScope->setBaseTypeName(attachedBase->attachedTypeName());
++ }
++ break;
++ default:
++ break;
++ }
++ }
++
++ template<typename Resolver, typename ChildScopeUpdater>
++ static QTypeRevision resolveTypesInternal(
++ Resolver resolve, ChildScopeUpdater update, const QQmlJSScope::Ptr &self,
++ const QQmlJS::ContextualTypes &contextualTypes, QSet<QString> *usedTypes)
++ {
++ const QTypeRevision revision = resolve(self, contextualTypes, usedTypes);
++ // NB: constness ensures no detach
++ const auto childScopes = self->childScopes();
++ for (auto it = childScopes.begin(), end = childScopes.end(); it != end; ++it) {
++ const auto childScope = *it;
++ update(childScope, self, contextualTypes, usedTypes);
++ resolveTypesInternal(resolve, update, childScope, contextualTypes, usedTypes); // recursion
++ }
++ return revision;
++ }
++
++ QTypeRevision QQmlJSScope::resolveTypes(
++ const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &contextualTypes,
++ QSet<QString> *usedTypes)
++ {
++ const auto resolveAll = [](const QQmlJSScope::Ptr &self,
++ const QQmlJS::ContextualTypes &contextualTypes,
++ QSet<QString> *usedTypes) {
++ resolveEnums(self, contextualTypes, usedTypes);
++ resolveList(self, contextualTypes.arrayType());
++ return resolveType(self, contextualTypes, usedTypes);
++ };
++ return resolveTypesInternal(resolveAll, updateChildScope, self, contextualTypes, usedTypes);
++ }
++
++ void QQmlJSScope::resolveNonEnumTypes(
++ const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &contextualTypes,
++ QSet<QString> *usedTypes)
++ {
++ resolveTypesInternal(resolveType, updateChildScope, self, contextualTypes, usedTypes);
++ }
++
++ static QString flagStorage(const QString &underlyingType)
++ {
++ // All numeric types are builtins. Therefore we can exhaustively check the internal names.
++
++ if (underlyingType == u"uint"
++ || underlyingType == u"quint8"
++ || underlyingType == u"ushort"
++ || underlyingType == u"ulonglong") {
++ return u"uint"_s;
++ }
++
++ if (underlyingType == u"int"
++ || underlyingType == u"qint8"
++ || underlyingType == u"short"
++ || underlyingType == u"longlong") {
++ return u"int"_s;
++ }
++
++ // Will fail to resolve and produce an error on usage.
++ // It's harmless if you never use the enum.
++ return QString();
++ }
++
++ /*!
++ \internal
++ Resolves all enums of self.
++
++ Some enums happen to have an alias, e.g. when an enum is used as a flag, the enum will exist in
++ two versions, once as enum (e.g. Qt::MouseButton) and once as a flag (e.g. Qt::MouseButtons). In
++ this case, normally only the flag is exposed to the qt metatype system and tools like qmltc will
++ have troubles when encountering the enum in signal parameters etc. To solve this problem,
++ resolveEnums() will create a QQmlJSMetaEnum copy for the alias in case the 'self'-scope already
++ does not have an enum called like the alias.
++ */
++ void QQmlJSScope::resolveEnums(
++ const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &contextualTypes,
++ QSet<QString> *usedTypes)
++ {
++ // temporary hash to avoid messing up m_enumerations while iterators are active on it
++ QHash<QString, QQmlJSMetaEnum> toBeAppended;
++ for (auto it = self->m_enumerations.begin(), end = self->m_enumerations.end(); it != end; ++it) {
++ if (it->type())
++ continue;
++ QQmlJSScope::Ptr enumScope = QQmlJSScope::create();
++ reparent(self, enumScope);
++ enumScope->m_scopeType = QQmlSA::ScopeType::EnumScope;
++
++ QString typeName = it->typeName();
++ if (typeName.isEmpty())
++ typeName = QStringLiteral("int");
++ else if (it->isFlag())
++ typeName = flagStorage(typeName);
++ enumScope->setBaseTypeName(typeName);
++ const auto type = findType(typeName, contextualTypes, usedTypes);
++ enumScope->m_baseType = { type.scope, type.revision };
++
++ enumScope->m_semantics = AccessSemantics::Value;
++ enumScope->m_internalName = self->internalName() + QStringLiteral("::") + it->name();
++ resolveList(enumScope, contextualTypes.arrayType());
++ if (QString alias = it->alias(); !alias.isEmpty()
++ && self->m_enumerations.constFind(alias) == self->m_enumerations.constEnd()) {
++ auto aliasScope = QQmlJSScope::create();
++ *aliasScope = *enumScope;
++ reparent(self, aliasScope);
++ aliasScope->m_internalName = self->internalName() + QStringLiteral("::") + alias;
++ QQmlJSMetaEnum cpy(*it);
++ cpy.setType(QQmlJSScope::ConstPtr(aliasScope));
++ toBeAppended.insert(alias, cpy);
++ }
++ it->setType(QQmlJSScope::ConstPtr(enumScope));
++ }
++ // no more iterators active on m_enumerations, so it can be changed safely now
++ self->m_enumerations.insert(toBeAppended);
++ }
++
++ void QQmlJSScope::resolveList(const QQmlJSScope::Ptr &self, const QQmlJSScope::ConstPtr &arrayType)
++ {
++ if (self->listType() || self->accessSemantics() == AccessSemantics::Sequence)
++ return;
++
++ Q_ASSERT(!arrayType.isNull());
++ QQmlJSScope::Ptr listType = QQmlJSScope::create();
++ listType->setAccessSemantics(AccessSemantics::Sequence);
++ listType->setElementTypeName(self->internalName());
++
++ if (self->isComposite()) {
++ // There is no internalName for this thing. Just set the value type right away
++ listType->setInternalName(u"QQmlListProperty<>"_s);
++ listType->m_elementType = QQmlJSScope::ConstPtr(self);
++ } else if (self->isReferenceType()) {
++ listType->setInternalName(u"QQmlListProperty<%2>"_s.arg(self->internalName()));
++ // Do not set a filePath on the list type, so that we have to generalize it
++ // even in direct mode.
++ } else {
++ listType->setInternalName(u"QList<%2>"_s.arg(self->internalName()));
++ listType->setFilePath(self->filePath());
++ }
++
++ const QQmlJS::ContextualType element = { self, QTypeRevision(),
++ quint8(QQmlJS::PrecedenceValues::Default) };
++ const QQmlJSImportedScope array = {arrayType, QTypeRevision()};
++ QQmlJS::ContextualTypes contextualTypes(
++ QQmlJS::ContextualTypes::INTERNAL,
++ { { self->internalName(), element }, },
++ { { self, self->internalName() }, },
++ arrayType);
++ QQmlJSScope::resolveTypes(listType, contextualTypes);
++
++ Q_ASSERT(listType->elementType() == self);
++ self->m_listType = listType;
++ }
++
++ void QQmlJSScope::resolveGroup(
++ const Ptr &self, const ConstPtr &baseType,
++ const QQmlJS::ContextualTypes &contextualTypes, QSet<QString> *usedTypes)
++ {
++ Q_ASSERT(baseType);
++ // Generalized grouped properties are always composite,
++ // which means we expect contextualTypes to be QML names.
++ Q_ASSERT(self->isComposite());
++
++ self->m_baseType.scope = baseType;
++ self->m_semantics = baseType->accessSemantics();
++ resolveNonEnumTypes(self, contextualTypes, usedTypes);
++ }
++
++ QQmlJSScope::ConstPtr QQmlJSScope::findCurrentQMLScope(const QQmlJSScope::ConstPtr &scope)
++ {
++ auto qmlScope = scope;
++ while (qmlScope && qmlScope->m_scopeType != QQmlSA::ScopeType::QMLScope)
++ qmlScope = qmlScope->parentScope();
++ return qmlScope;
++ }
++
++ bool QQmlJSScope::hasProperty(const QString &name) const
++ {
++ return QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) {
++ if (mode == QQmlJSScope::ExtensionNamespace)
++ return false;
++ return scope->m_properties.contains(name);
++ });
++ }
++
++ QQmlJSMetaProperty QQmlJSScope::property(const QString &name) const
++ {
++ QQmlJSMetaProperty prop;
++ QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) {
++ if (mode == QQmlJSScope::ExtensionNamespace)
++ return false;
++ const auto it = scope->m_properties.find(name);
++ if (it == scope->m_properties.end())
++ return false;
++ prop = *it;
++ return true;
++ });
++ return prop;
++ }
++
++ /*!
++ Returns all properties visible from this scope including those of
++ base types and extensions.
++
++ \note Properties that get shadowed are not included and only the
++ version visible from this scope is contained.
++ */
++ QHash<QString, QQmlJSMetaProperty> QQmlJSScope::properties() const
++ {
++ QHash<QString, QQmlJSMetaProperty> results;
++ QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) {
++ if (mode == QQmlJSScope::ExtensionNamespace)
++ return false;
++ for (auto it = scope->m_properties.constBegin();
++ it != scope->m_properties.constEnd(); it++) {
++ if (!results.contains(it.key()))
++ results.insert(it.key(), it.value());
++ }
++ return false;
++ });
++ return results;
++ }
++
++ template <typename Predicate>
++ QQmlJSScope::AnnotatedScope searchOwner(const QQmlJSScope::ConstPtr &self, Predicate &&p)
++ {
++ QQmlJSScope::AnnotatedScope owner;
++ QQmlJSUtils::searchBaseAndExtensionTypes(
++ self, [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) {
++ if (mode == QQmlJSScope::ExtensionNamespace)
++ return false;
++ if (p(scope)) {
++ owner = { scope, mode };
++ return true;
++ }
++ return false;
++ });
++ return owner;
++ }
++
++ QQmlJSScope::AnnotatedScope QQmlJSScope::ownerOfProperty(const QQmlJSScope::ConstPtr &self,
++ const QString &name)
++ {
++ return searchOwner(self, [&name](const QQmlJSScope::ConstPtr &scope) {
++ return scope->hasOwnProperty(name);
++ });
++ }
++
++ QQmlJSScope::AnnotatedScope QQmlJSScope::ownerOfMethod(const QQmlJSScope::ConstPtr &self,
++ const QString &name)
++ {
++ return searchOwner(self, [&name](const QQmlJSScope::ConstPtr &scope) {
++ return scope->hasOwnMethod(name);
++ });
++ }
++
++ QQmlJSScope::AnnotatedScope QQmlJSScope::ownerOfEnum(const QQmlJSScope::ConstPtr &self,
++ const QString &name)
++ {
++ return searchOwner(self, [&name](const QQmlJSScope::ConstPtr &scope) {
++ return scope->hasOwnEnumeration(name);
++ });
++ }
++
++ void QQmlJSScope::setPropertyLocallyRequired(const QString &name, bool isRequired)
++ {
++ if (!isRequired)
++ m_requiredPropertyNames.removeOne(name);
++ else if (!m_requiredPropertyNames.contains(name))
++ m_requiredPropertyNames.append(name);
++ }
++
++ bool QQmlJSScope::isPropertyRequired(const QString &name) const
++ {
++ bool isRequired = false;
++ QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) {
++ if (scope->isPropertyLocallyRequired(name)) {
++ isRequired = true;
++ return true;
++ }
++
++ // the hasOwnProperty() below only makes sense if our scope is
++ // not an extension namespace
++ if (mode == QQmlJSScope::ExtensionNamespace)
++ return false;
++
++ // If it has a property of that name, and that is not required, then none of the
++ // base types matter. You cannot make a derived type's property required with
++ // a "required" specification in a base type.
++ return scope->hasOwnProperty(name);
++ });
++ return isRequired;
++ }
++
++ bool QQmlJSScope::isPropertyLocallyRequired(const QString &name) const
++ {
++ return m_requiredPropertyNames.contains(name);
++ }
++
++ void QQmlJSScope::addOwnPropertyBinding(const QQmlJSMetaPropertyBinding &binding, BindingTargetSpecifier specifier)
++ {
++ Q_ASSERT(binding.sourceLocation().isValid());
++ m_propertyBindings.insert(binding.propertyName(), binding);
++
++ // NB: insert() prepends \a binding to the list of bindings, but we need
++ // append, so rotate
++ using iter = typename QMultiHash<QString, QQmlJSMetaPropertyBinding>::iterator;
++ std::pair<iter, iter> r = m_propertyBindings.equal_range(binding.propertyName());
++ std::rotate(r.first, std::next(r.first), r.second);
++
++ // additionally store bindings in the QmlIR compatible order
++ addOwnPropertyBindingInQmlIROrder(binding, specifier);
++ Q_ASSERT(m_propertyBindings.size() == m_propertyBindingsArray.size());
++ }
++
++ void QQmlJSScope::addOwnPropertyBindingInQmlIROrder(const QQmlJSMetaPropertyBinding &binding,
++ BindingTargetSpecifier specifier)
++ {
++ // the order:
++ // * ordinary bindings are prepended to the binding array
++ // * list bindings are properly ordered within each other, so basically
++ // prepended "in bulk"
++ // * bindings to default properties (which are not explicitly mentioned in
++ // binding expression) are inserted by source location's offset
++
++ static_assert(QTypeInfo<QQmlJSScope::QmlIRCompatibilityBindingData>::isRelocatable,
++ "We really want T to be relocatable as it improves QList<T> performance");
++
++ switch (specifier) {
++ case BindingTargetSpecifier::SimplePropertyTarget: {
++ m_propertyBindingsArray.emplaceFront(binding.propertyName(),
++ binding.sourceLocation().offset);
++ break;
++ }
++ case BindingTargetSpecifier::ListPropertyTarget: {
++ const auto bindingOnTheSameProperty =
++ [&](const QQmlJSScope::QmlIRCompatibilityBindingData &x) {
++ return x.propertyName == binding.propertyName();
++ };
++ // fake "prepend in bulk" by appending a list binding to the sequence of
++ // bindings to the same property. there's an implicit QML language
++ // guarantee that such sequence does not contain arbitrary in-between
++ // bindings that do not belong to the same list property
++ auto pos = std::find_if_not(m_propertyBindingsArray.begin(), m_propertyBindingsArray.end(),
++ bindingOnTheSameProperty);
++ Q_ASSERT(pos == m_propertyBindingsArray.begin()
++ || std::prev(pos)->propertyName == binding.propertyName());
++ m_propertyBindingsArray.emplace(pos, binding.propertyName(),
++ binding.sourceLocation().offset);
++ break;
++ }
++ case BindingTargetSpecifier::UnnamedPropertyTarget: {
++ // Implicit default property bindings are appended in file order,
++ // matching QmlIR::Object::appendBinding().
++ m_propertyBindingsArray.emplaceBack(
++ binding.propertyName(), binding.sourceLocation().offset);
++ break;
++ }
++ default: {
++ Q_UNREACHABLE();
++ break;
++ }
++ }
++ }
++
++ QList<QQmlJSMetaPropertyBinding> QQmlJSScope::ownPropertyBindingsInQmlIROrder() const
++ {
++ QList<QQmlJSMetaPropertyBinding> qmlIrOrdered;
++ qmlIrOrdered.reserve(m_propertyBindingsArray.size());
++
++ for (const auto &data : m_propertyBindingsArray) {
++ const auto [first, last] = m_propertyBindings.equal_range(data.propertyName);
++ Q_ASSERT(first != last);
++ auto binding = std::find_if(first, last, [&](const QQmlJSMetaPropertyBinding &x) {
++ return x.sourceLocation().offset == data.sourceLocationOffset;
++ });
++ Q_ASSERT(binding != last);
++ qmlIrOrdered.append(*binding);
++ }
++
++ return qmlIrOrdered;
++ }
++
++ bool QQmlJSScope::hasPropertyBindings(const QString &name) const
++ {
++ return QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) {
++ if (mode != QQmlJSScope::NotExtension) {
++ Q_ASSERT(!scope->hasOwnPropertyBindings(name));
++ return false;
++ }
++ return scope->hasOwnPropertyBindings(name);
++ });
++ }
++
++ QList<QQmlJSMetaPropertyBinding> QQmlJSScope::propertyBindings(const QString &name) const
++ {
++ QList<QQmlJSMetaPropertyBinding> bindings;
++ QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) {
++ if (mode != QQmlJSScope::NotExtension) {
++ Q_ASSERT(!scope->hasOwnPropertyBindings(name));
++ return false;
++ }
++ const auto range = scope->ownPropertyBindings(name);
++ for (auto it = range.first; it != range.second; ++it)
++ bindings.append(*it);
++ return false;
++ });
++ return bindings;
++ }
++
++ bool QQmlJSScope::hasInterface(const QString &name) const
++ {
++ return QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) {
++ if (mode != QQmlJSScope::NotExtension)
++ return false;
++ return scope->m_interfaceNames.contains(name);
++ });
++ }
++
++ bool QQmlJSScope::isNameDeferred(const QString &name) const
++ {
++ bool isDeferred = false;
++
++ QQmlJSUtils::searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
++ const QStringList immediate = scope->ownImmediateNames();
++ if (!immediate.isEmpty()) {
++ isDeferred = !immediate.contains(name);
++ return true;
++ }
++ const QStringList deferred = scope->ownDeferredNames();
++ if (!deferred.isEmpty()) {
++ isDeferred = deferred.contains(name);
++ return true;
++ }
++ return false;
++ });
++
++ return isDeferred;
++ }
++
++ void QQmlJSScope::setBaseTypeName(const QString &baseTypeName)
++ {
++ m_flags.setFlag(HasBaseTypeError, false);
++ m_baseTypeNameOrError = baseTypeName;
++ }
++
++ QString QQmlJSScope::baseTypeName() const
++ {
++ return m_flags.testFlag(HasBaseTypeError) ? QString() : m_baseTypeNameOrError;
++ }
++
++ void QQmlJSScope::setBaseTypeError(const QString &baseTypeError)
++ {
++ m_flags.setFlag(HasBaseTypeError);
++ m_baseTypeNameOrError = baseTypeError;
++ }
++
++ /*!
++ \internal
++ The name of the module is only saved in the QmlComponent. Iterate through the parent scopes until
++ the QmlComponent or the root is reached to find out the module name of the component in which `this`
++ resides.
++ */
++ QString QQmlJSScope::moduleName() const
++ {
++ for (const QQmlJSScope *it = this; it; it = it->parentScope().get()) {
++ const QString name = it->ownModuleName();
++ if (!name.isEmpty())
++ return name;
++ }
++ return {};
++ }
++
++ QString QQmlJSScope::baseTypeError() const
++ {
++ return m_flags.testFlag(HasBaseTypeError) ? m_baseTypeNameOrError : QString();
++ }
++
++ QString QQmlJSScope::attachedTypeName() const
++ {
++ QString name;
++ QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) {
++ if (mode != QQmlJSScope::NotExtension)
++ return false;
++ if (scope->ownAttachedType().isNull())
++ return false;
++ name = scope->ownAttachedTypeName();
++ return true;
++ });
++
++ return name;
++ }
++
++ QQmlJSScope::ConstPtr QQmlJSScope::attachedType() const
++ {
++ QQmlJSScope::ConstPtr ptr;
++ QQmlJSUtils::searchBaseAndExtensionTypes(
++ this, [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) {
++ if (mode != QQmlJSScope::NotExtension)
++ return false;
++ if (scope->ownAttachedType().isNull())
++ return false;
++ ptr = scope->ownAttachedType();
++ return true;
++ });
++
++ return ptr;
++ }
++
++ QQmlJSScope::AnnotatedScope QQmlJSScope::extensionType() const
++ {
++ if (!m_extensionType)
++ return { m_extensionType, NotExtension };
++ if (m_flags & ExtensionIsJavaScript)
++ return { m_extensionType, ExtensionJavaScript };
++ if (m_flags & ExtensionIsNamespace)
++ return { m_extensionType, ExtensionNamespace };
++ return { m_extensionType, ExtensionType };
++ }
++
++ void QQmlJSScope::addOwnRuntimeFunctionIndex(QQmlJSMetaMethod::AbsoluteFunctionIndex index)
++ {
++ m_runtimeFunctionIndices.emplaceBack(index);
++ }
++
++ bool QQmlJSScope::isResolved() const
++ {
++ const bool nameIsEmpty = (m_scopeType == ScopeType::AttachedPropertyScope
++ || m_scopeType == ScopeType::GroupedPropertyScope)
++ ? m_internalName.isEmpty()
++ : m_baseTypeNameOrError.isEmpty();
++ if (nameIsEmpty)
++ return true;
++ if (m_baseType.scope.isNull())
++ return false;
++ if (isComposite() && !nonCompositeBaseType(baseType()))
++ return false;
++ return true;
++ }
++
++ QString QQmlJSScope::defaultPropertyName() const
++ {
++ QString name;
++ QQmlJSUtils::searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
++ name = scope->ownDefaultPropertyName();
++ return !name.isEmpty();
++ });
++ return name;
++ }
++
++ QString QQmlJSScope::parentPropertyName() const
++ {
++ QString name;
++ QQmlJSUtils::searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
++ name = scope->ownParentPropertyName();
++ return !name.isEmpty();
++ });
++ return name;
++ }
++
++ bool QQmlJSScope::isFullyResolved() const
++ {
++ bool baseResolved = true;
++ QQmlJSUtils::searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
++ if (!scope->isResolved()) {
++ baseResolved = false;
++ return true;
++ }
++ return false;
++ });
++
++ return baseResolved;
++ }
++
++ QQmlJSScope::Export::Export(
++ QString package, QString type, QTypeRevision version, QTypeRevision revision)
++ : m_package(std::move(package))
++ , m_type(std::move(type))
++ , m_version(std::move(version))
++ , m_revision(std::move(revision))
++ {
++ }
++
++ bool QQmlJSScope::Export::isValid() const
++ {
++ return m_version.isValid() || !m_package.isEmpty() || !m_type.isEmpty();
++ }
++
++ QDeferredFactory<QQmlJSScope>::QDeferredFactory(QQmlJSImporter *importer,
++ const QQmlJS::TypeReader &typeReader,
++ const QString &filePath, const QString &moduleName,
++ bool isSingleton)
++ : m_importer(importer),
++ m_typeReader(typeReader ? typeReader : QQmlJS::TypeReader{ QQmlJS::defaultTypeReader }),
++ m_filePath(filePath),
++ m_moduleName(moduleName),
++ m_isSingleton(isSingleton)
++ {
++ }
++
++ void QDeferredFactory<QQmlJSScope>::populate(const QSharedPointer<QQmlJSScope> &scope) const
++ {
++ scope->setOwnModuleName(m_moduleName);
++ scope->setIsSingleton(m_isSingleton);
++ scope->setInternalName(internalName());
++
++ m_typeReader(m_importer, m_filePath, scope);
++ }
++
++ /*!
++ \internal
++ Checks whether \a derived type can be assigned to this type. Returns \c
++ true if the type hierarchy of \a derived contains a type equal to this.
++
++ \note Assigning \a derived to "QVariant" or "QJSValue" is always possible and
++ the function returns \c true in this case. In addition any "QObject" based \a derived type
++ can be assigned to a this type if that type is derived from "QQmlComponent".
++ */
++ bool QQmlJSScope::canAssign(const QQmlJSScope::ConstPtr &derived) const
++ {
++ if (!derived)
++ return false;
++
++ // expect this and derived types to have non-composite bases
++ Q_ASSERT(!isComposite() || nonCompositeBaseType(baseType()));
++ Q_ASSERT(nonCompositeBaseType(derived));
++
++ // the logic with isBaseComponent (as well as the way we set this flag)
++ // feels wrong - QTBUG-101940
++ const bool isBaseComponent = [this]() {
++ if (internalName() == u"QQmlComponent")
++ return true;
++ else if (isComposite())
++ return false;
++ for (auto cppBase = nonCompositeBaseType(baseType()); cppBase;
++ cppBase = cppBase->baseType()) {
++ if (cppBase->internalName() == u"QQmlAbstractDelegateComponent")
++ return true;
++ }
++ return false;
++ }();
++
++ QDuplicateTracker<QQmlJSScope::ConstPtr> seen;
++ for (auto scope = derived; !scope.isNull() && !seen.hasSeen(scope);
++ scope = scope->baseType()) {
++ if (isSameType(scope))
++ return true;
++ if (isBaseComponent && scope->internalName() == u"QObject"_s)
++ return true;
++ }
++
++ if (internalName() == u"QVariant"_s || internalName() == u"QJSValue"_s)
++ return true;
++
++ return isListProperty() && elementType()->canAssign(derived);
++ }
++
++ /*!
++ \internal
++ Checks whether this type or its parents have a custom parser.
++ */
++ bool QQmlJSScope::isInCustomParserParent() const
++ {
++ for (const auto *scope = this; scope; scope = scope->parentScope().get()) {
++ if (!scope->baseType().isNull() && scope->baseType()->hasCustomParser())
++ return true;
++ }
++
++ return false;
++ }
++
++ /*!
++ * \internal
++ * if this->isInlineComponent(), then this getter returns the name of the inline
++ * component.
++ */
++ std::optional<QString> QQmlJSScope::inlineComponentName() const
++ {
++ Q_ASSERT(isInlineComponent() == m_inlineComponentName.has_value());
++ return m_inlineComponentName;
++ }
++
++ /*!
++ * \internal
++ * If this type is part of an inline component, return its name. Otherwise, if this type
++ * is part of the document root, return the document root name.
++ */
++ QQmlJSScope::InlineComponentOrDocumentRootName QQmlJSScope::enclosingInlineComponentName() const
++ {
++ for (auto *type = this; type; type = type->parentScope().get()) {
++ if (type->isInlineComponent())
++ return *type->inlineComponentName();
++ }
++ return RootDocumentNameType();
++ }
++
++ QList<QQmlJSScope::ConstPtr> QQmlJSScope::childScopes() const
++ {
++ QList<QQmlJSScope::ConstPtr> result;
++ result.reserve(m_childScopes.size());
++ for (const auto &child : m_childScopes)
++ result.append(child);
++ return result;
++ }
++
++ /*!
++ \internal
++
++ Returns true if this type or any base type of it has the "EnforcesScopedEnums" flag.
++ The rationale is that you can turn on enforcement of scoped enums, but you cannot turn
++ it off explicitly.
++ */
++ bool QQmlJSScope::enforcesScopedEnums() const
++ {
++ for (const QQmlJSScope *scope = this; scope; scope = scope->baseType().get()) {
++ if (scope->hasEnforcesScopedEnumsFlag())
++ return true;
++ }
++ return false;
++ }
++
++ /*!
++ \internal
++ Returns true if the current type is creatable by checking all the required base classes.
++ "Uncreatability" is only inherited from base types for composite types (in qml) and not for non-composite types (c++).
++
++ For the exact definition:
++ A type is uncreatable if and only if one of its composite base type or its first non-composite base type matches
++ following criteria:
++ \list
++ \li the base type is a singleton, or
++ \li the base type is an attached type, or
++ \li the base type is a C++ type with the QML_UNCREATABLE or QML_ANONYMOUS macro, or
++ \li the base type is a type without default constructor (in that case, it really needs QML_UNCREATABLE or QML_ANONYMOUS)
++ \endlist
++ */
++ bool QQmlJSScope::isCreatable() const
++ {
++ auto isCreatableNonRecursive = [](const QQmlJSScope *scope) {
++ return scope->hasCreatableFlag() && !scope->isSingleton()
++ && scope->scopeType() == QQmlSA::ScopeType::QMLScope;
++ };
++
++ for (const QQmlJSScope* scope = this; scope; scope = scope->baseType().get()) {
++ if (!scope->isComposite()) {
++ // just check the first nonComposite (c++) base for isCreatableNonRecursive() and then stop
++ return isCreatableNonRecursive(scope);
++ } else {
++ // check all composite (qml) bases for isCreatableNonRecursive().
++ if (isCreatableNonRecursive(scope))
++ return true;
++ }
++ }
++ // no uncreatable bases found
++ return false;
++ }
++
++ bool QQmlJSScope::isStructured() const
++ {
++ for (const QQmlJSScope *scope = this; scope; scope = scope->baseType().get()) {
++ if (!scope->isComposite())
++ return scope->hasStructuredFlag();
++ }
++ return false;
++ }
++
++ QQmlSA::Element QQmlJSScope::createQQmlSAElement(const ConstPtr &ptr)
++ {
++ QQmlSA::Element element;
++ *reinterpret_cast<QQmlJSScope::ConstPtr *>(element.m_data) = ptr;
++ return element;
++ }
++
++ QQmlSA::Element QQmlJSScope::createQQmlSAElement(ConstPtr &&ptr)
++ {
++ QQmlSA::Element element;
++ *reinterpret_cast<QQmlJSScope::ConstPtr *>(element.m_data) = std::move(ptr);
++ return element;
++ }
++
++ const QQmlJSScope::ConstPtr &QQmlJSScope::scope(const QQmlSA::Element &element)
++ {
++ return *reinterpret_cast<const QQmlJSScope::ConstPtr *>(element.m_data);
++ }
++
++ QTypeRevision
++ QQmlJSScope::nonCompositeBaseRevision(const ImportedScope<QQmlJSScope::ConstPtr> &scope)
++ {
++ for (auto base = scope; base.scope;
++ base = { base.scope->m_baseType.scope, base.scope->m_baseType.revision }) {
++ if (!base.scope->isComposite())
++ return base.revision;
++ }
++ return {};
++ }
++
++ /*!
++ \internal
++ Checks whether \a otherScope is the same type as this.
++
++ In addition to checking whether the scopes are identical, we also cover duplicate scopes with
++ the same internal name.
++ */
++ bool QQmlJSScope::isSameType(const ConstPtr &otherScope) const
++ {
++ return this == otherScope.get()
++ || (!this->internalName().isEmpty()
++ && this->internalName() == otherScope->internalName());
++ }
++
++ bool QQmlJSScope::inherits(const ConstPtr &base) const
++ {
++ if (!base)
++ return false;
++ for (const QQmlJSScope *scope = this; scope; scope = scope->baseType().get()) {
++ if (scope->isSameType(base))
++ return true;
++ }
++ return false;
++ }
++
++
++ QT_END_NAMESPACE