qtdeclarative/src/qmlcompiler/qqmljsscope.cpp

702 lines
24 KiB
C++

/****************************************************************************
**
** Copyright (C) 2019 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qqmljsscope_p.h"
#include "qqmljstypereader_p.h"
#include "qqmljsimporter_p.h"
#include <QtCore/qqueue.h>
#include <QtCore/qsharedpointer.h>
#include <QtCore/qfileinfo.h>
#include <private/qduplicatetracker_p.h>
#include <algorithm>
#include <type_traits>
QT_BEGIN_NAMESPACE
/*! \internal
Utility method that returns proper value according to the type To. This
version returns From.
*/
template<typename To, typename From, typename std::enable_if_t<!std::is_pointer_v<To>, int> = 0>
static auto getQQmlJSScopeFromSmartPtr(const From &p) -> From
{
static_assert(!std::is_pointer_v<From>, "From has to be a smart pointer holding QQmlJSScope");
return p;
}
/*! \internal
Utility method that returns proper value according to the type To. This
version returns From::get(), which is a raw pointer. The returned type is
not necessary equal to To (e.g. To might be `QQmlJSScope *` while returned
is `const QQmlJSScope *`).
*/
template<typename To, typename From, typename std::enable_if_t<std::is_pointer_v<To>, int> = 0>
static auto getQQmlJSScopeFromSmartPtr(const From &p) -> decltype(p.get())
{
static_assert(!std::is_pointer_v<From>, "From has to be a smart pointer holding QQmlJSScope");
return p.get();
}
template<typename QQmlJSScopePtr, typename Action>
static bool searchBaseAndExtensionTypes(QQmlJSScopePtr type, const Action &check)
{
// NB: among other things, getQQmlJSScopeFromSmartPtr() also resolves const
// vs non-const pointer issue, so use it's return value as the type
using T = decltype(
getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(std::declval<QQmlJSScope::ConstPtr>()));
QDuplicateTracker<T> seen;
for (T scope = type; scope && !seen.hasSeen(scope);
scope = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(scope->baseType())) {
// Extensions override their base types
for (T extension = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(scope->extensionType());
extension && !seen.hasSeen(extension);
extension = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(extension->baseType())) {
if (check(extension))
return true;
}
if (check(scope))
return true;
}
return false;
}
QQmlJSScope::QQmlJSScope(ScopeType type, const QQmlJSScope::Ptr &parentScope)
: m_parentScope(parentScope), m_scopeType(type) {}
QQmlJSScope::Ptr QQmlJSScope::create(ScopeType type, const QQmlJSScope::Ptr &parentScope)
{
QSharedPointer<QQmlJSScope> childScope(new QQmlJSScope{type, parentScope});
if (parentScope)
parentScope->m_childScopes.push_back(childScope);
return childScope;
}
void QQmlJSScope::insertJSIdentifier(const QString &name, const JavaScriptIdentifier &identifier)
{
Q_ASSERT(m_scopeType != QQmlJSScope::QMLScope);
if (identifier.kind == JavaScriptIdentifier::LexicalScoped
|| identifier.kind == JavaScriptIdentifier::Injected
|| m_scopeType == QQmlJSScope::JSFunctionScope) {
m_jsIdentifiers.insert(name, identifier);
} else {
auto targetScope = parentScope();
while (targetScope->m_scopeType != QQmlJSScope::JSFunctionScope)
targetScope = targetScope->parentScope();
targetScope->m_jsIdentifiers.insert(name, identifier);
}
}
void QQmlJSScope::insertPropertyIdentifier(const QQmlJSMetaProperty &property)
{
addOwnProperty(property);
QQmlJSMetaMethod method(property.propertyName() + QLatin1String("Changed"), QLatin1String("void"));
addOwnMethod(method);
}
bool QQmlJSScope::isIdInCurrentScope(const QString &id) const
{
return isIdInCurrentQmlScopes(id) || isIdInCurrentJSScopes(id);
}
bool QQmlJSScope::hasMethod(const QString &name) const
{
return searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
return scope->m_methods.contains(name);
});
}
QList<QQmlJSMetaMethod> QQmlJSScope::methods(const QString &name) const
{
QList<QQmlJSMetaMethod> results;
searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
results.append(scope->ownMethods(name));
return false;
});
return results;
}
bool QQmlJSScope::hasEnumeration(const QString &name) const
{
return searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
return scope->m_enumerations.contains(name);
});
}
bool QQmlJSScope::hasEnumerationKey(const QString &name) const
{
return searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
for (const auto &e : scope->m_enumerations) {
if (e.keys().contains(name))
return true;
}
return false;
});
}
QQmlJSMetaEnum QQmlJSScope::enumeration(const QString &name) const
{
QQmlJSMetaEnum result;
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;
}
/*!
Returns if assigning to a property of this type would cause
implicit component wrapping for non-Component types.
\note This method can also be used to check whether a type needs
to be implicitly wrapped: A type for which this function returns true
doesn't need to be actually wrapped.
*/
bool QQmlJSScope::causesImplicitComponentWrapping() const {
if (internalName() == u"QQmlComponent")
return true;
else if (isComposite()) // composite types are never treated as Component
return false;
// A class which is derived from component is not treated as a Component
// However isUsableComponent considers also QQmlAbstractDelegateComponent
// See isUsableComponent in qqmltypecompiler.cpp
for (auto cppBase = nonCompositeBaseType(baseType()); cppBase; cppBase = cppBase->baseType())
if (cppBase->internalName() == u"QQmlAbstractDelegateComponent")
return true;
return false;
}
/*!
\internal
Returns true if the scope is the outermost element of a separate Component
Either because it has been implicitly wrapped, e.g. due to an assignment to
a Component property, or because it is the first (and only) child of a
Component.
*/
bool QQmlJSScope::isComponentRootElement() const {
if (m_flags.testFlag(WrappedInImplicitComponent))
return true;
auto base = nonCompositeBaseType(parentScope()); // handles null parentScope()
if (!base)
return false;
return base->internalName() == u"QQmlComponent";
}
bool QQmlJSScope::isIdInCurrentQmlScopes(const QString &id) const
{
if (m_scopeType == QQmlJSScope::QMLScope)
return m_properties.contains(id) || m_methods.contains(id) || m_enumerations.contains(id);
const auto qmlScope = findCurrentQMLScope(parentScope());
return qmlScope->m_properties.contains(id)
|| qmlScope->m_methods.contains(id)
|| qmlScope->m_enumerations.contains(id);
}
bool QQmlJSScope::isIdInCurrentJSScopes(const QString &id) const
{
if (m_scopeType != QQmlJSScope::QMLScope && m_jsIdentifiers.contains(id))
return true;
for (auto jsScope = parentScope(); jsScope; jsScope = jsScope->parentScope()) {
if (jsScope->m_scopeType != QQmlJSScope::QMLScope && jsScope->m_jsIdentifiers.contains(id))
return true;
}
return false;
}
bool QQmlJSScope::isIdInjectedFromSignal(const QString &id) const
{
const auto found = findJSIdentifier(id);
return found.has_value() && found->kind == JavaScriptIdentifier::Injected;
}
std::optional<QQmlJSScope::JavaScriptIdentifier>
QQmlJSScope::findJSIdentifier(const QString &id) const
{
for (const auto *scope = this; scope; scope = scope->parentScope().data()) {
if (scope->m_scopeType == QQmlJSScope::JSFunctionScope
|| scope->m_scopeType == QQmlJSScope::JSLexicalScope) {
auto it = scope->m_jsIdentifiers.find(id);
if (it != scope->m_jsIdentifiers.end())
return *it;
}
}
return std::optional<JavaScriptIdentifier>{};
}
QQmlJSScope::ConstPtr
QQmlJSScope::findType(const QString &name,
const QHash<QString, QQmlJSScope::ConstPtr> &contextualTypes,
QSet<QString> *usedTypes)
{
auto type = contextualTypes.constFind(name);
if (type != contextualTypes.constEnd()) {
if (usedTypes != nullptr)
usedTypes->insert(name);
return *type;
}
const auto colonColon = name.indexOf(QStringLiteral("::"));
if (colonColon > 0) {
const QString outerTypeName = name.left(colonColon);
const auto outerType = contextualTypes.constFind(outerTypeName);
if (outerType != contextualTypes.constEnd()) {
for (const auto &innerType : qAsConst((*outerType)->m_childScopes)) {
if (innerType->m_internalName == name) {
if (usedTypes != nullptr)
usedTypes->insert(name);
return innerType;
}
}
}
}
return QQmlJSScope::ConstPtr();
}
void QQmlJSScope::resolveType(const QQmlJSScope::Ptr &self,
const QHash<QString, ConstPtr> &contextualTypes,
QSet<QString> *usedTypes)
{
if (!self->m_baseType && !self->m_baseTypeName.isEmpty())
self->m_baseType = findType(self->m_baseTypeName, contextualTypes, usedTypes);
if (!self->m_attachedType && !self->m_attachedTypeName.isEmpty())
self->m_attachedType = findType(self->m_attachedTypeName, contextualTypes, usedTypes);
if (!self->m_valueType && !self->m_valueTypeName.isEmpty())
self->m_valueType = findType(self->m_valueTypeName, contextualTypes, usedTypes);
if (!self->m_extensionType && !self->m_extensionTypeName.isEmpty())
self->m_extensionType = findType(self->m_extensionTypeName, contextualTypes, usedTypes);
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, contextualTypes, usedTypes)) {
it->setType(type);
continue;
}
const auto enumeration = self->m_enumerations.find(typeName);
if (enumeration != self->m_enumerations.end())
it->setType(enumeration->type());
}
for (auto it = self->m_methods.begin(), end = self->m_methods.end(); it != end; ++it) {
const QString returnTypeName = it->returnTypeName();
if (!it->returnType() && !returnTypeName.isEmpty())
it->setReturnType(findType(returnTypeName, contextualTypes, usedTypes));
const auto paramTypeNames = it->parameterTypeNames();
QList<QSharedPointer<const QQmlJSScope>> paramTypes = it->parameterTypes();
if (paramTypes.length() < paramTypeNames.length())
paramTypes.resize(paramTypeNames.length());
for (int i = 0, length = paramTypes.length(); i < length; ++i) {
auto &paramType = paramTypes[i];
const auto paramTypeName = paramTypeNames[i];
if (!paramType && !paramTypeName.isEmpty())
paramType = findType(paramTypeName, contextualTypes, usedTypes);
}
it->setParameterTypes(paramTypes);
}
}
void QQmlJSScope::updateChildScope(const QQmlJSScope::Ptr &childScope, const QQmlJSScope::Ptr &self,
const QHash<QString, QQmlJSScope::ConstPtr> &contextualTypes,
QSet<QString> *usedTypes)
{
switch (childScope->scopeType()) {
case QQmlJSScope::GroupedPropertyScope:
searchBaseAndExtensionTypes(self.data(), [&](const QQmlJSScope *type) {
const auto propertyIt = type->m_properties.find(childScope->internalName());
if (propertyIt != type->m_properties.end()) {
childScope->m_baseType = QQmlJSScope::ConstPtr(propertyIt->type());
childScope->m_baseTypeName = propertyIt->typeName();
return true;
}
return false;
});
break;
case QQmlJSScope::AttachedPropertyScope:
if (const auto attachedBase =
findType(childScope->internalName(), contextualTypes, usedTypes)) {
childScope->m_baseType = attachedBase->attachedType();
childScope->m_baseTypeName = attachedBase->attachedTypeName();
}
break;
default:
break;
}
}
template<typename Resolver, typename ChildScopeUpdater>
static void resolveTypesInternal(Resolver resolve, ChildScopeUpdater update,
const QQmlJSScope::Ptr &self,
const QHash<QString, QQmlJSScope::ConstPtr> &contextualTypes,
QSet<QString> *usedTypes)
{
resolve(self, contextualTypes, usedTypes);
// NB: constness ensures no detach
const QVector<QQmlJSScope::Ptr> childScopes = self->childScopes();
for (auto it = childScopes.begin(), end = childScopes.end(); it != end; ++it) {
const QQmlJSScope::Ptr childScope = *it;
update(childScope, self, contextualTypes, usedTypes);
resolveTypesInternal(resolve, update, childScope, contextualTypes, usedTypes); // recursion
}
}
void QQmlJSScope::resolveTypes(const QQmlJSScope::Ptr &self,
const QHash<QString, QQmlJSScope::ConstPtr> &contextualTypes,
QSet<QString> *usedTypes)
{
const auto resolveAll = [](const QQmlJSScope::Ptr &self,
const QHash<QString, QQmlJSScope::ConstPtr> &contextualTypes,
QSet<QString> *usedTypes) {
resolveEnums(self, contextualTypes, usedTypes);
resolveType(self, contextualTypes, usedTypes);
};
resolveTypesInternal(resolveAll, updateChildScope, self, contextualTypes, usedTypes);
}
void QQmlJSScope::resolveNonEnumTypes(const QQmlJSScope::Ptr &self,
const QHash<QString, ConstPtr> &contextualTypes,
QSet<QString> *usedTypes)
{
resolveTypesInternal(resolveType, updateChildScope, self, contextualTypes, usedTypes);
}
void QQmlJSScope::resolveEnums(const QQmlJSScope::Ptr &self,
const QHash<QString, QQmlJSScope::ConstPtr> &contextualTypes,
QSet<QString> *usedTypes)
{
const auto intType = findType(QStringLiteral("int"), contextualTypes, usedTypes);
Q_ASSERT(intType); // There always has to be a builtin "int" type
for (auto it = self->m_enumerations.begin(), end = self->m_enumerations.end(); it != end;
++it) {
if (it->type())
continue;
auto enumScope = QQmlJSScope::create(EnumScope, self);
enumScope->m_baseTypeName = QStringLiteral("int");
enumScope->m_baseType = intType;
enumScope->m_semantics = AccessSemantics::Value;
enumScope->m_internalName = self->internalName() + QStringLiteral("::") + it->name();
it->setType(ConstPtr(enumScope));
}
}
void QQmlJSScope::resolveGeneralizedGroup(const Ptr &self, const ConstPtr &baseType,
const QHash<QString, ConstPtr> &contextualTypes,
QSet<QString> *usedTypes)
{
self->m_baseType = baseType;
resolveNonEnumTypes(self, contextualTypes, usedTypes);
}
QQmlJSScope::ConstPtr QQmlJSScope::findCurrentQMLScope(const QQmlJSScope::ConstPtr &scope)
{
auto qmlScope = scope;
while (qmlScope && qmlScope->m_scopeType != QQmlJSScope::QMLScope)
qmlScope = qmlScope->parentScope();
return qmlScope;
}
void QQmlJSScope::addExport(const QString &name, const QString &package,
const QTypeRevision &version)
{
m_exports.append(Export(package, name, version));
}
bool QQmlJSScope::hasProperty(const QString &name) const
{
return searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
return scope->m_properties.contains(name);
});
}
QQmlJSMetaProperty QQmlJSScope::property(const QString &name) const
{
QQmlJSMetaProperty prop;
searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
const auto it = scope->m_properties.find(name);
if (it == scope->m_properties.end())
return false;
prop = *it;
return true;
});
return prop;
}
QQmlJSScope::ConstPtr QQmlJSScope::ownerOfProperty(const QQmlJSScope::ConstPtr &self,
const QString &name)
{
QQmlJSScope::ConstPtr owner;
searchBaseAndExtensionTypes(self, [&](const QQmlJSScope::ConstPtr &scope) {
if (scope->hasOwnProperty(name)) {
owner = scope;
return true;
}
return false;
});
return owner;
}
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;
searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
if (scope->isPropertyLocallyRequired(name)) {
isRequired = true;
return true;
}
// 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);
}
bool QQmlJSScope::hasPropertyBinding(const QString &name) const
{
return searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
return scope->m_propertyBindings.contains(name);
});
}
QQmlJSMetaPropertyBinding QQmlJSScope::propertyBinding(const QString &name) const
{
QQmlJSMetaPropertyBinding binding;
searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
const auto it = scope->m_propertyBindings.find(name);
if (it == scope->m_propertyBindings.end())
return false;
binding = *it;
return true;
});
return binding;
}
QList<QQmlJSMetaPropertyBinding> QQmlJSScope::propertyBindings(const QString &name) const
{
QList<QQmlJSMetaPropertyBinding> bindings;
searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
const auto range = scope->m_propertyBindings.equal_range(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 searchBaseAndExtensionTypes(
this, [&](const QQmlJSScope *scope) { return scope->m_interfaceNames.contains(name); });
}
bool QQmlJSScope::isNameDeferred(const QString &name) const
{
bool isDeferred = false;
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;
}
QString QQmlJSScope::attachedTypeName() const
{
QString name;
searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
if (scope->ownAttachedType().isNull())
return false;
name = scope->ownAttachedTypeName();
return true;
});
return name;
}
QQmlJSScope::ConstPtr QQmlJSScope::attachedType() const
{
QQmlJSScope::ConstPtr ptr;
searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
if (scope->ownAttachedType().isNull())
return false;
ptr = scope->ownAttachedType();
return true;
});
return ptr;
}
bool QQmlJSScope::isResolved() const
{
if (m_scopeType == ScopeType::AttachedPropertyScope
|| m_scopeType == ScopeType::GroupedPropertyScope) {
return m_internalName.isEmpty() || !m_baseType.isNull();
}
return m_baseTypeName.isEmpty() || !m_baseType.isNull();
}
QString QQmlJSScope::defaultPropertyName() const
{
QString name;
searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
name = scope->ownDefaultPropertyName();
return !name.isEmpty();
});
return name;
}
QString QQmlJSScope::parentPropertyName() const
{
QString name;
searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
name = scope->ownParentPropertyName();
return !name.isEmpty();
});
return name;
}
bool QQmlJSScope::isFullyResolved() const
{
bool baseResolved = true;
searchBaseAndExtensionTypes(this, [&](const QQmlJSScope *scope) {
if (!scope->isResolved()) {
baseResolved = false;
return true;
}
return false;
});
return baseResolved;
}
QQmlJSScope::Export::Export(QString package, QString type, const QTypeRevision &version) :
m_package(std::move(package)),
m_type(std::move(type)),
m_version(version)
{
}
bool QQmlJSScope::Export::isValid() const
{
return m_version.isValid() || !m_package.isEmpty() || !m_type.isEmpty();
}
QQmlJSScope QDeferredFactory<QQmlJSScope>::create() const
{
QQmlJSTypeReader typeReader(m_importer, m_filePath);
QQmlJSScope::Ptr result = typeReader();
m_importer->m_warnings.append(typeReader.errors());
result->setInternalName(QFileInfo(m_filePath).baseName());
return std::move(*result);
}
bool QQmlJSScope::canAssign(const QQmlJSScope::ConstPtr &derived) const
{
if (!derived)
return false;
bool isBaseComponent = causesImplicitComponentWrapping();
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"_qs)
return true;
}
return internalName() == u"QVariant"_qs || internalName() == u"QJSValue"_qs;
}
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;
}
QT_END_NAMESPACE