// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qqmljsregistercontent_p.h" #include QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; struct QQmlJSRegisterContentPrivate { public: using ContentVariant = QQmlJSRegisterContent::ContentVariant; enum class Kind : size_t { Type, Property, Enum, Method, ImportNamespace, Conversion, MethodCall }; struct ConvertedTypes { QList origins; QQmlJSScope::ConstPtr result; QQmlJSRegisterContent resultScope; friend size_t qHash(const ConvertedTypes &types, size_t seed = 0) { return qHashMulti(seed, types.origins, types.result, types.resultScope); } friend bool operator==(const ConvertedTypes &a, const ConvertedTypes &b) { return a.origins == b.origins && a.result == b.result && a.resultScope == b.resultScope; } friend bool operator!=(const ConvertedTypes &a, const ConvertedTypes &b) { return !(a == b); } }; struct PropertyLookup { QQmlJSMetaProperty property; int baseLookupIndex = QQmlJSRegisterContent::InvalidLookupIndex; int resultLookupIndex = QQmlJSRegisterContent::InvalidLookupIndex; friend size_t qHash(const PropertyLookup &property, size_t seed = 0) { return qHashMulti( seed, property.property, property.baseLookupIndex, property.resultLookupIndex); } friend bool operator==(const PropertyLookup &a, const PropertyLookup &b) { return a.baseLookupIndex == b.baseLookupIndex && a.resultLookupIndex == b.resultLookupIndex && a.property == b.property; } friend bool operator!=(const PropertyLookup &a, const PropertyLookup &b) { return !(a == b); } }; using Content = std::variant< std::pair, PropertyLookup, std::pair, std::pair, QQmlJSScope::ConstPtr>, std::pair, ConvertedTypes, QQmlJSMetaMethod >; friend size_t qHash(const QQmlJSRegisterContentPrivate ®isterContent, size_t seed = 0) { seed = qHashMulti( seed, registerContent.m_storage, registerContent.m_content.index(), registerContent.m_variant, registerContent.m_scope); switch (Kind(registerContent.m_content.index())) { case Kind::Type: return qHash(std::get>(registerContent.m_content), seed); case Kind::Property: return qHash(std::get(registerContent.m_content), seed); case Kind::Enum: return qHash(std::get>(registerContent.m_content), seed); case Kind::Method: return qHash(std::get, QQmlJSScope::ConstPtr>>( registerContent.m_content), seed); case Kind::ImportNamespace: return qHash(std::get>( registerContent.m_content), seed); case Kind::Conversion: return qHash(std::get(registerContent.m_content), seed); case Kind::MethodCall: return qHash(std::get(registerContent.m_content), seed); } Q_UNREACHABLE_RETURN(seed); } friend bool operator==( const QQmlJSRegisterContentPrivate &a, const QQmlJSRegisterContentPrivate &b) { return a.m_storage == b.m_storage && a.m_variant == b.m_variant && a.m_scope == b.m_scope && a.m_content == b.m_content; } friend bool operator!=( const QQmlJSRegisterContentPrivate &a, const QQmlJSRegisterContentPrivate &b) { return !(a == b); } QQmlJSRegisterContent m_storage; QQmlJSRegisterContent m_scope; Content m_content; ContentVariant m_variant = ContentVariant::Unknown; QQmlJSRegisterContent m_original; QQmlJSRegisterContent m_shadowed; int resultLookupIndex() const { switch (Kind(m_content.index())) { case Kind::Type: return std::get>(m_content).second; case Kind::Property: return std::get(m_content).resultLookupIndex; default: return QQmlJSRegisterContent::InvalidLookupIndex; } } void setType(const QQmlJSScope::ConstPtr &type) { switch (Kind(m_content.index())) { case Kind::Type: std::get>(m_content).first = type; return; case Kind::Property: std::get(m_content).property.setType(type); return; case Kind::Enum: std::get>(m_content).first.setType(type); return; case Kind::Method: std::get, QQmlJSScope::ConstPtr>>(m_content) .second = type; return; case Kind::ImportNamespace: std::get>(m_content).second = type; return; case Kind::Conversion: std::get(m_content).result = type; return; case Kind::MethodCall: std::get(m_content).setReturnType({ type }); return; } Q_UNREACHABLE_RETURN(); } private: friend class QQmlJSRegisterContentPool; QQmlJSRegisterContentPrivate() = default; ~QQmlJSRegisterContentPrivate() = default; QQmlJSRegisterContentPrivate(const QQmlJSRegisterContentPrivate &) = default; QQmlJSRegisterContentPrivate(QQmlJSRegisterContentPrivate &&) = default; QQmlJSRegisterContentPrivate &operator=(const QQmlJSRegisterContentPrivate &) = default; QQmlJSRegisterContentPrivate &operator=(QQmlJSRegisterContentPrivate &&) = default; }; bool QQmlJSRegisterContent::isValid() const { return !containedType().isNull(); }; QString QQmlJSRegisterContent::descriptiveName() const { using Kind = QQmlJSRegisterContentPrivate::Kind; if (!d || (!d->m_storage.isValid() && containedType().isNull())) return u"(invalid type)"_s; const auto scope = [this]() -> QString { if (!d->m_scope.isValid()) return u"(invalid type)::"_s; const QQmlJSScope::ConstPtr scopeContained = d->m_scope.containedType(); if (scopeContained.isNull()) return u"(invalid type)::"_s; return (scopeContained->internalName().isEmpty() ? (scopeContained->filePath().isEmpty() ? u"??"_s : (u"(component in "_s + scopeContained->filePath() + u")"_s)) : scopeContained->internalName()) + u"::"_s; }; QString result; switch (Kind(d->m_content.index())) { case Kind::Type: { const QQmlJSScope::ConstPtr contained = type(); result += contained->internalName(); const QQmlJSScope::ConstPtr stored = d->m_storage.containedType(); if (stored && stored->internalName() != contained->internalName()) result += u" stored as "_s + stored->internalName(); return result; } case Kind::Property: { const QQmlJSMetaProperty prop = property(); result += scope() + prop.propertyName() + u" with type "_s + prop.typeName(); QStringList details; if (original().isValid() && !prop.type()->internalName().isEmpty()) details.append(u"adjusted to " + prop.type()->internalName()); const QQmlJSScope::ConstPtr stored = d->m_storage.containedType(); if (stored && stored->internalName() != prop.typeName()) details.append(u"stored as "_s + stored->internalName()); if (!details.isEmpty()) result += u" (%1)"_s.arg(details.join(u", ")); return result; } case Kind::Method: { const auto methods = method(); if (methods.isEmpty()) result = scope() + u"(unknown method)"_s; else result = scope() + methods[0].methodName() + u"(...)"_s; if (d->m_storage.isValid()) return result + u" (stored as "_s + d->m_storage.containedType()->internalName() + u")"; return result; } case Kind::Enum: { const QString enumName = enumeration().name(); const QString memberName = enumMember(); if (memberName.isEmpty()) result = scope() + enumName; else result = scope() + enumName + u"::"_s + memberName; if (d->m_storage.isValid()) return result + u" (stored as "_s + d->m_storage.containedType()->internalName() + u")"; return result; } case Kind::ImportNamespace: { return u"import namespace %1"_s.arg(importNamespace()); } case Kind::Conversion: { return u"conversion to %1"_s.arg(conversionResultType()->internalName()); } case Kind::MethodCall: { const QQmlJSMetaMethod &method = std::get(d->m_content); return u"call to method %1, returning %2"_s.arg( method.methodName(), method.returnTypeName()); } } Q_UNREACHABLE_RETURN(result + u"wat?"_s); } QString QQmlJSRegisterContent::containedTypeName() const { QQmlJSScope::ConstPtr type; switch (variant()) { case QQmlJSRegisterContent::MetaType: type = scopeType(); break; default: type = containedType(); break; } return QQmlJSScope::prettyName( type->internalName().isEmpty() ? type->baseTypeName() : type->internalName()); } bool QQmlJSRegisterContent::isType() const { return d && d->m_content.index() == size_t(QQmlJSRegisterContentPrivate::Kind::Type); } bool QQmlJSRegisterContent::isProperty() const { return d && d->m_content.index() == size_t(QQmlJSRegisterContentPrivate::Kind::Property); } bool QQmlJSRegisterContent::isEnumeration() const { return d && d->m_content.index() == size_t(QQmlJSRegisterContentPrivate::Kind::Enum); } bool QQmlJSRegisterContent::isMethod() const { return d && d->m_content.index() == size_t(QQmlJSRegisterContentPrivate::Kind::Method); } bool QQmlJSRegisterContent::isImportNamespace() const { return d && d->m_content.index() == size_t(QQmlJSRegisterContentPrivate::Kind::ImportNamespace); } bool QQmlJSRegisterContent::isConversion() const { return d && d->m_content.index() == size_t(QQmlJSRegisterContentPrivate::Kind::Conversion); } bool QQmlJSRegisterContent::isMethodCall() const { return d && d->m_content.index() == size_t(QQmlJSRegisterContentPrivate::Kind::MethodCall); } bool QQmlJSRegisterContent::isList() const { using Kind = QQmlJSRegisterContentPrivate::Kind; if (!d) return false; switch (Kind(d->m_content.index())) { case Kind::Type: return std::get>(d->m_content).first ->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence; case Kind::Property: return std::get(d->m_content).property.type() ->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence; case Kind::Conversion: return std::get(d->m_content).result ->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence; case Kind::MethodCall: return std::get(d->m_content).returnType() ->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence; default: return false; } } bool QQmlJSRegisterContent::isWritable() const { using Kind = QQmlJSRegisterContentPrivate::Kind; if (!d) return false; switch (Kind(d->m_content.index())) { case Kind::Property: return std::get(d->m_content) .property.isWritable(); // TODO: What can we actually write? default: break; } return true; } bool QQmlJSRegisterContent::isJavaScriptReturnValue() const { return d && isMethodCall() && std::get(d->m_content).isJavaScriptFunction(); } /*! * \internal * Precondition: This is an attachment. * Return the type that does the attaching. */ QQmlJSRegisterContent QQmlJSRegisterContent::attacher() const { Q_ASSERT(d); Q_ASSERT(d->m_variant == Attachment); return scope(); } /*! * \internal * Precondition: This is an attachment. * Return the type of the object the attachment is attached to. */ QQmlJSRegisterContent QQmlJSRegisterContent::attachee() const { Q_ASSERT(d); Q_ASSERT(d->m_variant == Attachment); QQmlJSRegisterContent attachee = attacher().scope(); while (attachee.variant() == ModulePrefix) attachee = attachee.scope(); return attachee; } QQmlJSScope::ConstPtr QQmlJSRegisterContent::storedType() const { return d ? d->m_storage.containedType() : QQmlJSScope::ConstPtr(); } QQmlJSScope::ConstPtr QQmlJSRegisterContent::containedType() const { if (!d) return QQmlJSScope::ConstPtr(); if (isType()) return type(); if (isProperty()) return std::get(d->m_content).property.type(); if (isEnumeration()) return std::get>(d->m_content).first.type(); if (isMethod()) return methodType(); if (isImportNamespace()) return importNamespaceType(); if (isConversion()) return conversionResultType(); if (isMethodCall()) return std::get(d->m_content).returnType(); Q_UNREACHABLE_RETURN({}); } QQmlJSScope::ConstPtr QQmlJSRegisterContent::scopeType() const { return d ? d->m_scope.containedType() : QQmlJSScope::ConstPtr(); } QQmlJSScope::ConstPtr QQmlJSRegisterContent::type() const { Q_ASSERT(isType()); return std::get>(d->m_content).first; } QQmlJSMetaProperty QQmlJSRegisterContent::property() const { Q_ASSERT(isProperty()); return std::get(d->m_content).property; } int QQmlJSRegisterContent::baseLookupIndex() const { Q_ASSERT(isProperty()); return std::get(d->m_content).baseLookupIndex; } int QQmlJSRegisterContent::resultLookupIndex() const { return d ? d->resultLookupIndex() : InvalidLookupIndex; } QQmlJSMetaEnum QQmlJSRegisterContent::enumeration() const { Q_ASSERT(isEnumeration()); return std::get>(d->m_content).first; } QString QQmlJSRegisterContent::enumMember() const { Q_ASSERT(isEnumeration()); return std::get>(d->m_content).second; } QList QQmlJSRegisterContent::method() const { Q_ASSERT(isMethod()); return std::get, QQmlJSScope::ConstPtr>>(d->m_content).first; } QQmlJSScope::ConstPtr QQmlJSRegisterContent::methodType() const { Q_ASSERT(isMethod()); return std::get, QQmlJSScope::ConstPtr>>(d->m_content).second; } uint QQmlJSRegisterContent::importNamespace() const { Q_ASSERT(isImportNamespace()); return std::get>(d->m_content).first; } QQmlJSScope::ConstPtr QQmlJSRegisterContent::importNamespaceType() const { Q_ASSERT(isImportNamespace()); return std::get>(d->m_content).second; } QQmlJSScope::ConstPtr QQmlJSRegisterContent::conversionResultType() const { Q_ASSERT(isConversion()); return std::get(d->m_content).result; } QQmlJSRegisterContent QQmlJSRegisterContent::conversionResultScope() const { Q_ASSERT(isConversion()); return std::get(d->m_content).resultScope; } QList QQmlJSRegisterContent::conversionOrigins() const { Q_ASSERT(isConversion()); return std::get(d->m_content).origins; } QQmlJSMetaMethod QQmlJSRegisterContent::methodCall() const { Q_ASSERT(isMethodCall()); return std::get(d->m_content); } QQmlJSRegisterContent::ContentVariant QQmlJSRegisterContent::variant() const { return d ? d->m_variant : Unknown; } QQmlJSRegisterContent QQmlJSRegisterContent::scope() const { return d ? d->m_scope : QQmlJSRegisterContent(); } QQmlJSRegisterContent QQmlJSRegisterContent::storage() const { return d ? d->m_storage : QQmlJSRegisterContent(); } QQmlJSRegisterContent QQmlJSRegisterContent::original() const { return d ? d->m_original : QQmlJSRegisterContent(); } QQmlJSRegisterContent QQmlJSRegisterContent::shadowed() const { return d ? d->m_shadowed : QQmlJSRegisterContent(); } QQmlJSRegisterContentPool::QQmlJSRegisterContentPool() = default; QQmlJSRegisterContentPool::~QQmlJSRegisterContentPool() = default; QQmlJSRegisterContent QQmlJSRegisterContentPool::createType( const QQmlJSScope::ConstPtr &type, int resultLookupIndex, QQmlJSRegisterContent::ContentVariant variant, QQmlJSRegisterContent scope) { QQmlJSRegisterContentPrivate *result = create(scope, variant); result->m_content = std::make_pair(type, resultLookupIndex); return result; } QQmlJSRegisterContent QQmlJSRegisterContentPool::createProperty( const QQmlJSMetaProperty &property, int baseLookupIndex, int resultLookupIndex, QQmlJSRegisterContent::ContentVariant variant, QQmlJSRegisterContent scope) { QQmlJSRegisterContentPrivate *result = create(scope, variant); result->m_content = QQmlJSRegisterContentPrivate::PropertyLookup { property, baseLookupIndex, resultLookupIndex }; return result; } QQmlJSRegisterContent QQmlJSRegisterContentPool::createEnumeration( const QQmlJSMetaEnum &enumeration, const QString &enumMember, QQmlJSRegisterContent::ContentVariant variant, QQmlJSRegisterContent scope) { QQmlJSRegisterContentPrivate *result = create(scope, variant); result->m_content = std::make_pair(enumeration, enumMember); return result; } QQmlJSRegisterContent QQmlJSRegisterContentPool::createMethod( const QList &methods, const QQmlJSScope::ConstPtr &methodType, QQmlJSRegisterContent::ContentVariant variant, QQmlJSRegisterContent scope) { // Methods can only be stored in QJSValue. Q_ASSERT(methodType->internalName() == u"QJSValue"_s); QQmlJSRegisterContentPrivate *result = create(scope, variant); result->m_content = std::make_pair(methods, methodType); return result; } QQmlJSRegisterContent QQmlJSRegisterContentPool::createMethodCall( const QQmlJSMetaMethod &method, const QQmlJSScope::ConstPtr &returnType, QQmlJSRegisterContent scope) { QQmlJSRegisterContentPrivate *result = create(scope, ContentVariant::MethodCall); QQmlJSMetaMethod resultMethod = method; resultMethod.setReturnType({ returnType }); resultMethod.setReturnTypeName(returnType->internalName()); result->m_content = std::move(resultMethod); return result; } QQmlJSRegisterContent QQmlJSRegisterContentPool::createImportNamespace( uint importNamespaceStringId, const QQmlJSScope::ConstPtr &importNamespaceType, QQmlJSRegisterContent::ContentVariant variant, QQmlJSRegisterContent scope) { QQmlJSRegisterContentPrivate *result = create(scope, variant); result->m_content = std::make_pair(importNamespaceStringId, importNamespaceType); return result; } QQmlJSRegisterContent QQmlJSRegisterContentPool::createConversion( const QList &origins, const QQmlJSScope::ConstPtr &conversion, QQmlJSRegisterContent conversionScope, ContentVariant variant, QQmlJSRegisterContent scope) { QQmlJSRegisterContentPrivate *result = create(scope, variant); result->m_content = QQmlJSRegisterContentPrivate::ConvertedTypes { origins, conversion, conversionScope }; return result; } QQmlJSRegisterContent QQmlJSRegisterContentPool::storedIn( QQmlJSRegisterContent content, const QQmlJSScope::ConstPtr &newStoredType) { Q_ASSERT(content.d); QQmlJSRegisterContentPrivate *result = clone(content.d); result->m_storage = createType( newStoredType, QQmlJSRegisterContent::InvalidLookupIndex, ContentVariant::Storage); return result; } QQmlJSRegisterContent QQmlJSRegisterContentPool::castTo( QQmlJSRegisterContent content, const QQmlJSScope::ConstPtr &newContainedType) { // This is not a conversion but a run time cast. It may result in null or undefined. QQmlJSRegisterContentPrivate *result = create(content, ContentVariant::Cast); result->m_content = std::make_pair(newContainedType, result->resultLookupIndex()); return result; } void QQmlJSRegisterContentPool::storeType( QQmlJSRegisterContent content, const QQmlJSScope::ConstPtr &stored) { QQmlJSRegisterContentPrivate *d = content.d; Q_ASSERT(d); Q_ASSERT(d->m_storage.isNull()); d->m_storage = createType( stored, QQmlJSRegisterContent::InvalidLookupIndex, ContentVariant::Storage); } void QQmlJSRegisterContentPool::adjustType( QQmlJSRegisterContent content, const QQmlJSScope::ConstPtr &adjusted) { QQmlJSRegisterContentPrivate *d = content.d; Q_ASSERT(d); Q_ASSERT(d->m_original.isNull()); d->m_original = clone(d); d->setType(adjusted); } void QQmlJSRegisterContentPool::generalizeType( QQmlJSRegisterContent content, const QQmlJSScope::ConstPtr &generalized) { QQmlJSRegisterContentPrivate *d = content.d; Q_ASSERT(d); Q_ASSERT(d->m_shadowed.isNull()); d->m_shadowed = clone(d); d->setType(generalized); } void QQmlJSRegisterContentPool::setAllocationMode(AllocationMode mode) { m_checkpoint = (mode == Temporary) ? m_pool.size() : -1; } void QQmlJSRegisterContentPool::clearTemporaries() { if (m_checkpoint != -1) m_pool.resize(m_checkpoint); } QQmlJSRegisterContentPrivate *QQmlJSRegisterContentPool::clone( const QQmlJSRegisterContentPrivate *from) { m_pool.push_back(std::unique_ptr(from ? new QQmlJSRegisterContentPrivate(*from) : new QQmlJSRegisterContentPrivate)); return m_pool.back().get(); } QQmlJSRegisterContentPrivate *QQmlJSRegisterContentPool::create( QQmlJSRegisterContent scope, ContentVariant variant) { QQmlJSRegisterContentPrivate *result = create(); result->m_scope = scope; result->m_variant = variant; return result; } QT_END_NAMESPACE