diff options
Diffstat (limited to 'src/qmltyperegistrar/qmetatypesjsonprocessor.cpp')
-rw-r--r-- | src/qmltyperegistrar/qmetatypesjsonprocessor.cpp | 898 |
1 files changed, 696 insertions, 202 deletions
diff --git a/src/qmltyperegistrar/qmetatypesjsonprocessor.cpp b/src/qmltyperegistrar/qmetatypesjsonprocessor.cpp index 899e8f91bd..5d5d694937 100644 --- a/src/qmltyperegistrar/qmetatypesjsonprocessor.cpp +++ b/src/qmltyperegistrar/qmetatypesjsonprocessor.cpp @@ -3,50 +3,90 @@ #include "qmetatypesjsonprocessor_p.h" +#include "qanystringviewutils_p.h" +#include "qqmltyperegistrarconstants_p.h" +#include "qqmltyperegistrarutils_p.h" +#include "qqmltypesclassdescription_p.h" +#include "qqmltyperegistrarutils_p.h" + +#include <QtCore/qcborarray.h> +#include <QtCore/qcbormap.h> #include <QtCore/qfile.h> -#include <QtCore/qjsonarray.h> #include <QtCore/qjsondocument.h> #include <QtCore/qqueue.h> QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; +using namespace Constants; +using namespace Constants::MetatypesDotJson; +using namespace Constants::MetatypesDotJson::Qml; +using namespace QAnyStringViewUtils; + +const MetaTypePrivate MetaType::s_empty; + +// TODO: This could be optimized to store the objects in a more compact way. +std::vector<std::unique_ptr<MetaTypePrivate>> s_pool; + +static QCborValue fromJson(const QByteArray &json, QJsonParseError *error) +{ + const QJsonDocument jsonValue = QJsonDocument::fromJson(json, error); + if (jsonValue.isArray()) + return QCborValue::fromJsonValue(jsonValue.array()); + if (jsonValue.isObject()) + return QCborValue::fromJsonValue(jsonValue.object()); + return QCborValue(); +} + +QList<QAnyStringView> MetaTypesJsonProcessor::namespaces(const MetaType &classDef) +{ + const QAnyStringView unqualified = classDef.className(); + const QAnyStringView qualified = classDef.qualifiedClassName(); + + QList<QAnyStringView> namespaces; + if (qualified != unqualified) { + namespaces = split(qualified, "::"_L1); + Q_ASSERT(namespaces.last() == unqualified); + namespaces.pop_back(); + } + + return namespaces; +} bool MetaTypesJsonProcessor::processTypes(const QStringList &files) { for (const QString &source: files) { - QJsonDocument metaObjects; + QCborValue metaObjects; { QFile f(source); if (!f.open(QIODevice::ReadOnly)) { - fprintf(stderr, "Error opening %s for reading\n", qPrintable(source)); + error(source) << "Cannot open file for reading"; return false; } - QJsonParseError error = {0, QJsonParseError::NoError}; - metaObjects = QJsonDocument::fromJson(f.readAll(), &error); - if (error.error != QJsonParseError::NoError) { - fprintf(stderr, "Error %d while parsing %s: %s\n", error.error, qPrintable(source), - qPrintable(error.errorString())); + QJsonParseError parseError = {0, QJsonParseError::NoError}; + metaObjects = fromJson(f.readAll(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + error(source) + << "Failed to parse JSON:" << parseError.error + << parseError.errorString(); return false; } } if (metaObjects.isArray()) { - const QJsonArray metaObjectsArray = metaObjects.array(); - for (const QJsonValue metaObject : metaObjectsArray) { - if (!metaObject.isObject()) { - fprintf(stderr, "Error parsing %s: JSON is not an object\n", - qPrintable(source)); + const QCborArray metaObjectsArray = metaObjects.toArray(); + for (const QCborValue &metaObject : metaObjectsArray) { + if (!metaObject.isMap()) { + error(source) << "JSON is not an object"; return false; } - processTypes(metaObject.toObject()); + processTypes(metaObject.toMap()); } - } else if (metaObjects.isObject()) { - processTypes(metaObjects.object()); + } else if (metaObjects.isMap()) { + processTypes(metaObjects.toMap()); } else { - fprintf(stderr, "Error parsing %s: JSON is not an object or an array\n", - qPrintable(source)); + error(source) << "JSON is not an object or an array"; return false; } } @@ -54,102 +94,114 @@ bool MetaTypesJsonProcessor::processTypes(const QStringList &files) return true; } -bool MetaTypesJsonProcessor::processForeignTypes(const QStringList &foreignTypesFiles) +bool MetaTypesJsonProcessor::processForeignTypes(const QString &types) { - bool success = true; + QFile typesFile(types); + if (!typesFile.open(QIODevice::ReadOnly)) { + error(types) << "Cannot open foreign types file"; + return false; + } - for (const QString &types : foreignTypesFiles) { - QFile typesFile(types); - if (!typesFile.open(QIODevice::ReadOnly)) { - fprintf(stderr, "Cannot open foreign types file %s\n", qPrintable(types)); - success = false; - continue; - } + QJsonParseError parseError = {0, QJsonParseError::NoError}; + QCborValue foreignMetaObjects = fromJson(typesFile.readAll(), &parseError); + if (parseError.error != QJsonParseError::NoError) { + error(types) + << "Failed to parse JSON:" << parseError.error + << parseError.errorString(); + return false; + } - QJsonParseError error = {0, QJsonParseError::NoError}; - QJsonDocument foreignMetaObjects = QJsonDocument::fromJson(typesFile.readAll(), &error); - if (error.error != QJsonParseError::NoError) { - fprintf(stderr, "Error %d while parsing %s: %s\n", error.error, qPrintable(types), - qPrintable(error.errorString())); - success = false; - continue; + const QCborArray foreignObjectsArray = foreignMetaObjects.toArray(); + for (const QCborValue &metaObject : foreignObjectsArray) { + if (!metaObject.isMap()) { + error(types) << "JSON is not an object"; + return false; } - const QJsonArray foreignObjectsArray = foreignMetaObjects.array(); - for (const QJsonValue metaObject : foreignObjectsArray) { - if (!metaObject.isObject()) { - fprintf(stderr, "Error parsing %s: JSON is not an object\n", - qPrintable(types)); - success = false; - continue; - } + processForeignTypes(metaObject.toMap()); + } - processForeignTypes(metaObject.toObject()); - } + return true; +} + +bool MetaTypesJsonProcessor::processForeignTypes(const QStringList &foreignTypesFiles) +{ + bool success = true; + + for (const QString &types : foreignTypesFiles) { + if (!processForeignTypes(types)) + success = false; } return success; } -static void sortStringList(QStringList *list) +template<typename String> +static void sortStringList(QList<String> *list) { std::sort(list->begin(), list->end()); const auto newEnd = std::unique(list->begin(), list->end()); - list->erase(QStringList::const_iterator(newEnd), list->constEnd()); + list->erase(typename QList<String>::const_iterator(newEnd), list->constEnd()); } void MetaTypesJsonProcessor::postProcessTypes() { sortTypes(m_types); - sortStringList(&m_includes); } void MetaTypesJsonProcessor::postProcessForeignTypes() { sortTypes(m_foreignTypes); + sortStringList(&m_primitiveTypes); addRelatedTypes(); sortStringList(&m_referencedTypes); - sortTypes(m_types); + sortStringList(&m_includes); } QString MetaTypesJsonProcessor::extractRegisteredTypes() const { QString registrationHelper; for (const auto &obj: m_types) { - const QString className = obj[u"className"].toString(); - const QString foreignClassName = className+ u"Foreign"; - const auto classInfos = obj[u"classInfos"].toArray(); - QString qmlElement; + const QString className = obj.className().toString(); + const QString qualifiedClassName = obj.qualifiedClassName().toString(); + const QString foreignClassName = className + u"Foreign"; + QStringList qmlElements; QString qmlUncreatable; QString qmlAttached; bool isSingleton = false; bool isExplicitlyUncreatable = false; - for (QJsonValue entry: classInfos) { - const auto name = entry[u"name"].toString(); - const auto value = entry[u"value"].toString(); - if (name == u"QML.Element") { - if (value == u"auto") { - qmlElement = u"QML_NAMED_ELEMENT("_s + className + u")"_s; - } else if (value == u"anonymous") { - qmlElement = u"QML_ANONYMOUS"_s; + bool isNamespace = obj.kind() == MetaType::Kind::Namespace; + for (const ClassInfo &entry: obj.classInfos()) { + const auto name = entry.name; + const auto value = entry.value; + if (name == S_ELEMENT) { + if (value == S_AUTO) { + qmlElements.append(u"QML_NAMED_ELEMENT("_s + className + u")"_s); + } else if (value == S_ANONYMOUS) { + qmlElements.append(u"QML_ANONYMOUS"_s); } else { - qmlElement = u"QML_NAMED_ELEMENT(" + value + u")"; + qmlElements.append(u"QML_NAMED_ELEMENT("_s + value.toString() + u")"); } - } else if (name == u"QML.Creatable" && value == u"false") { + } else if (name == S_CREATABLE && value == S_FALSE) { isExplicitlyUncreatable = true; - } else if (name == u"QML.UncreatableReason") { - qmlUncreatable = u"QML_UNCREATABLE(\"" + value + u"\")"; - } else if (name == u"QML.Attached") { - qmlAttached = u"QML_ATTACHED("_s + value + u")"; - } else if (name == u"QML.Singleton") { + } else if (name == S_UNCREATABLE_REASON) { + qmlUncreatable = u"QML_UNCREATABLE(\""_s + value.toString() + u"\")"; + } else if (name == S_ATTACHED) { + qmlAttached = u"QML_ATTACHED("_s + value.toString() + u")"; + } else if (name == S_SINGLETON) { isSingleton = true; } } - if (qmlElement.isEmpty()) + if (qmlElements.isEmpty()) continue; // no relevant entries found const QString spaces = u" "_s; - registrationHelper += u"\nstruct "_s + foreignClassName + u"{\n Q_GADGET\n"_s; - registrationHelper += spaces + u"QML_FOREIGN(" + className + u")\n"_s; - registrationHelper += spaces + qmlElement + u"\n"_s; + if (isNamespace) { + registrationHelper += u"\nnamespace "_s + foreignClassName + u"{\n Q_NAMESPACE\n"_s; + registrationHelper += spaces + u"QML_FOREIGN_NAMESPACE(" + qualifiedClassName + u")\n"_s; + } else { + registrationHelper += u"\nstruct "_s + foreignClassName + u"{\n Q_GADGET\n"_s; + registrationHelper += spaces + u"QML_FOREIGN(" + qualifiedClassName + u")\n"_s; + } + registrationHelper += spaces + qmlElements.join(u"\n"_s) + u"\n"_s; if (isSingleton) registrationHelper += spaces + u"QML_SINGLETON\n"_s; if (isExplicitlyUncreatable) { @@ -160,60 +212,136 @@ QString MetaTypesJsonProcessor::extractRegisteredTypes() const } if (!qmlAttached.isEmpty()) registrationHelper += spaces + qmlAttached + u"\n"; - registrationHelper += u"};\n"; + registrationHelper += u"}"; + if (!isNamespace) + registrationHelper += u";"; + registrationHelper += u"\n"; } return registrationHelper; } -MetaTypesJsonProcessor::RegistrationMode MetaTypesJsonProcessor::qmlTypeRegistrationMode( - const QJsonObject &classDef) +MetaTypesJsonProcessor::PreProcessResult MetaTypesJsonProcessor::preProcess( + const MetaType &classDef, PopulateMode populateMode) { - const QJsonArray classInfos = classDef[QLatin1String("classInfos")].toArray(); - for (const QJsonValue info : classInfos) { - const QString name = info[QLatin1String("name")].toString(); - if (name == QLatin1String("QML.Element")) { - if (classDef[QLatin1String("object")].toBool()) - return ObjectRegistration; - if (classDef[QLatin1String("gadget")].toBool()) - return GadgetRegistration; - if (classDef[QLatin1String("namespace")].toBool()) - return NamespaceRegistration; - qWarning() << "Not registering classInfo which is neither an object, " - "nor a gadget, nor a namespace:" - << name; - break; + // If this type is a self-extending value type or a sequence type or has a JavaScript extension + // and is not the root object, then it's foreign type has no entry of its own. + // In that case we need to generate a "primitive" entry. + + QList<QAnyStringView> primitiveAliases; + UsingDeclaration usingDeclaration; + + RegistrationMode mode = NoRegistration; + bool isSelfExtendingValueType = false; + bool hasJavaScriptExtension = false; + bool isRootObject = false; + bool isSequence = false; + + for (const ClassInfo &classInfo : classDef.classInfos()) { + if (classInfo.name == S_FOREIGN) + usingDeclaration.alias = classInfo.value; + else if (classInfo.name == S_PRIMITIVE_ALIAS) + primitiveAliases.append(classInfo.value); + else if (classInfo.name == S_EXTENSION_IS_JAVA_SCRIPT) + hasJavaScriptExtension = (classInfo.value == S_TRUE); + else if (classInfo.name == S_EXTENDED && classDef.kind() == MetaType::Kind::Gadget) + isSelfExtendingValueType = classInfo.value == classDef.className(); + else if (classInfo.name == S_ROOT) + isRootObject = (classInfo.value == S_TRUE); + else if (classInfo.name == S_SEQUENCE) + isSequence = true; + else if (classInfo.name == S_USING) + usingDeclaration.original = classInfo.value; + else if (populateMode == PopulateMode::Yes && classInfo.name == S_ELEMENT) { + switch (classDef.kind()) { + case MetaType::Kind::Object: + mode = ObjectRegistration; + break; + case MetaType::Kind::Gadget: + mode = GadgetRegistration; + break; + case MetaType::Kind::Namespace: + mode = NamespaceRegistration; + break; + default: + warning(classDef) + << "Not registering a classInfo which is neither an object," + << "nor a gadget, nor a namespace:" + << classInfo.name.toString(); + break; + } } } - return NoRegistration; + + return PreProcessResult { + primitiveAliases, + usingDeclaration, + (!isRootObject && (isSequence || isSelfExtendingValueType || hasJavaScriptExtension)) + ? usingDeclaration.alias + : QAnyStringView(), + mode + }; + +} + +// TODO: Remove this when QAnyStringView gets a proper qHash() +static size_t qHash(QAnyStringView string, size_t seed = 0) +{ + return string.visit([seed](auto view) { + if constexpr (std::is_same_v<decltype(view), QStringView>) + return qHash(view, seed); + if constexpr (std::is_same_v<decltype(view), QLatin1StringView>) + return qHash(view, seed); + if constexpr (std::is_same_v<decltype(view), QUtf8StringView>) + return qHash(QByteArrayView(view.data(), view.length()), seed); + }); +} + +static bool qualifiedClassNameLessThan(const MetaType &a, const MetaType &b) +{ + return a.qualifiedClassName() < b.qualifiedClassName(); } void MetaTypesJsonProcessor::addRelatedTypes() { - const QLatin1String classInfosKey("classInfos"); - const QLatin1String nameKey("name"); - const QLatin1String qualifiedClassNameKey("qualifiedClassName"); - const QLatin1String qmlNamePrefix("QML."); - const QLatin1String qmlForeignName("QML.Foreign"); - const QLatin1String qmlExtendedName("QML.Extended"); - const QLatin1String qmlAttachedName("QML.Attached"); - const QLatin1String qmlSequenceName("QML.Sequence"); - const QLatin1String valueKey("value"); - const QLatin1String superClassesKey("superClasses"); - const QLatin1String accessKey("access"); - const QLatin1String publicAccess("public"); - - QSet<QString> processedRelatedNames; - QQueue<QJsonObject> typeQueue; + QSet<QAnyStringView> processedRelatedNativeNames; + QSet<QAnyStringView> processedRelatedJavaScriptNames; + QSet<QAnyStringView> unresolvedForeignNames; + QQueue<MetaType> typeQueue; typeQueue.append(m_types); + const auto addRelatedName + = [&](QAnyStringView relatedName, const QList<QAnyStringView> &namespaces) { + if (const FoundType related = QmlTypesClassDescription::findType( + m_types, m_foreignTypes, relatedName, namespaces)) { + + if (!related.javaScript.isEmpty()) + processedRelatedJavaScriptNames.insert(related.javaScript.qualifiedClassName()); + + if (!related.native.isEmpty()) + processedRelatedNativeNames.insert(related.native.qualifiedClassName()); + + return true; + } else { + return false; + } + }; + + const auto addRelatedType = [&](const MetaType &type) { + const QAnyStringView qualifiedName = type.qualifiedClassName(); + if (type.inputFile().isEmpty()) + processedRelatedJavaScriptNames.insert(qualifiedName); + else + processedRelatedNativeNames.insert(qualifiedName); + }; + // First mark all classes registered from this module as already processed. - for (const QJsonObject &type : m_types) { - processedRelatedNames.insert(type.value(qualifiedClassNameKey).toString()); - const auto classInfos = type.value(classInfosKey).toArray(); - for (const QJsonValue classInfo : classInfos) { - const QJsonObject obj = classInfo.toObject(); - if (obj.value(nameKey).toString() == qmlForeignName) { - processedRelatedNames.insert(obj.value(valueKey).toString()); + for (const MetaType &type : std::as_const(m_types)) { + addRelatedType(type); + for (const ClassInfo &obj : type.classInfos()) { + if (obj.name == S_FOREIGN) { + const QAnyStringView foreign = obj.value; + if (!addRelatedName(foreign, namespaces(type))) + unresolvedForeignNames.insert(foreign); break; } } @@ -221,143 +349,509 @@ void MetaTypesJsonProcessor::addRelatedTypes() // Then mark all classes registered from other modules as already processed. // We don't want to generate them again for this module. - for (const QJsonObject &foreignType : m_foreignTypes) { - const auto classInfos = foreignType.value(classInfosKey).toArray(); + for (const MetaType &foreignType : std::as_const(m_foreignTypes)) { bool seenQmlPrefix = false; - for (const QJsonValue classInfo : classInfos) { - const QJsonObject obj = classInfo.toObject(); - const QString name = obj.value(nameKey).toString(); - if (!seenQmlPrefix && name.startsWith(qmlNamePrefix)) { - processedRelatedNames.insert(foreignType.value(qualifiedClassNameKey).toString()); + for (const ClassInfo &obj : foreignType.classInfos()) { + const QAnyStringView name = obj.name; + if (!seenQmlPrefix && startsWith(name, "QML."_L1)) { + addRelatedType(foreignType); seenQmlPrefix = true; } - if (name == qmlForeignName) { - processedRelatedNames.insert(obj.value(valueKey).toString()); + if (name == S_FOREIGN) { + const QAnyStringView foreign = obj.value; + if (!addRelatedName(foreign, namespaces(foreignType))) + unresolvedForeignNames.insert(foreign); break; } } } - auto addType = [&](const QString &typeName) { - m_referencedTypes.append(typeName); - if (processedRelatedNames.contains(typeName)) + const auto addReference = [&](const MetaType &type, QSet<QAnyStringView> *processedRelatedNames, + FoundType::Origin origin) { + if (type.isEmpty()) return; - processedRelatedNames.insert(typeName); - if (const QJsonObject *other - = QmlTypesClassDescription::findType(m_foreignTypes, typeName)) { - m_types.append(*other); - typeQueue.enqueue(*other); + QAnyStringView qualifiedName = type.qualifiedClassName(); + m_referencedTypes.append(qualifiedName); + const qsizetype size = processedRelatedNames->size(); + processedRelatedNames->insert(qualifiedName); + + if (processedRelatedNames->size() == size) + return; + + typeQueue.enqueue(type); + + if (origin == FoundType::OwnTypes) + return; + + // Add to own types since we need it for our registrations. + const auto insert = std::lower_bound( + m_types.constBegin(), m_types.constEnd(), type, + qualifiedClassNameLessThan); + m_types.insert(insert, type); + + // Also add its include. We don't want to rely on transitive inclues. + const QString inputFile = type.inputFile(); + if (!inputFile.isEmpty()) + m_includes.append(inputFile); + + // Remove from the foreign types to avoid the ODR warning. + const auto remove = std::equal_range( + m_foreignTypes.constBegin(), m_foreignTypes.constEnd(), type, + qualifiedClassNameLessThan); + for (auto it = remove.first; it != remove.second; ++it) { + if (*it == type) { + m_foreignTypes.erase(it); + break; + } + } + }; + + const auto addInterface + = [&](QAnyStringView typeName, const QList<QAnyStringView> &namespaces) { + if (const FoundType other = QmlTypesClassDescription::findType( + m_types, m_foreignTypes, typeName, namespaces)) { + if (!other.native.isEmpty()) { + addReference(other.native, &processedRelatedNativeNames, other.nativeOrigin); + return true; + } + } else { + // Do not warn about unresolved interfaces. + // They don't have to have Q_OBJECT or Q_GADGET. + unresolvedForeignNames.insert(typeName); + } + + processedRelatedNativeNames.insert(typeName); + return false; + }; + + const auto addType = [&](const MetaType &context, QAnyStringView typeName, + const QList<QAnyStringView> &namespaces, QAnyStringView relation) { + if (const FoundType other = QmlTypesClassDescription::findType( + m_types, m_foreignTypes, typeName, namespaces)) { + addReference(other.native, &processedRelatedNativeNames, other.nativeOrigin); + addReference(other.javaScript, &processedRelatedJavaScriptNames, other.javaScriptOrigin); + return true; } + + // If it's an enum, add the surrounding type. + const QLatin1StringView separator("::"); + if (const qsizetype index = lastIndexOf(typeName, separator); index > 0) { + if (const FoundType other = QmlTypesClassDescription::findType( + m_types, m_foreignTypes, typeName.left(index), namespaces)) { + + const QAnyStringView enumName = typeName.mid(index + separator.length()); + + for (const Enum &enumerator : other.native.enums()) { + if (enumerator.name != enumName && enumerator.alias != enumName) + continue; + + addReference(other.native, &processedRelatedNativeNames, other.nativeOrigin); + addReference( + other.javaScript, &processedRelatedJavaScriptNames, + other.javaScriptOrigin); + return true; + } + } + } + + // If it's an enum of the context type itself, we don't have to do anything. + for (const Enum &enumerator : context.enums()) { + if (enumerator.name == typeName || enumerator.alias == typeName) + return true; + } + + // If we've detected this type as unresolved foreign and it actually belongs to this module, + // we'll get to it again when we process it as foreign type. In that case we'll look at the + // special cases for sequences and extensions. + if (!unresolvedForeignNames.contains(typeName) && !isPrimitive(typeName)) + warning(context) << typeName << "is used as" << relation << "type but cannot be found."; + + processedRelatedNativeNames.insert(typeName); + processedRelatedJavaScriptNames.insert(typeName); + return false; + }; + + const auto doAddReferences = [&](QAnyStringView typeName, + const QList<QAnyStringView> &namespaces) { + if (const FoundType other = QmlTypesClassDescription::findType( + m_types, m_foreignTypes, typeName, namespaces)) { + addReference( + other.native, &processedRelatedNativeNames, other.nativeOrigin); + addReference( + other.javaScript, &processedRelatedJavaScriptNames, other.javaScriptOrigin); + return true; + } + + return false; + }; + + const auto addSupers = [&](const MetaType &context, const QList<QAnyStringView> &namespaces) { + for (const Interface &iface : context.ifaces()) + addInterface(interfaceName(iface), namespaces); + + // We don't warn about missing bases for value types. They don't have to be registered. + bool warnAboutSupers = context.kind() != MetaType::Kind::Gadget; + + QList<QAnyStringView> missingSupers; + + for (const BaseType &superObject : context.superClasses()) { + if (superObject.access != Access::Public) + continue; + + QAnyStringView typeName = superObject.name; + if (doAddReferences(typeName, namespaces)) + warnAboutSupers = false; + else + missingSupers.append(typeName); + } + + for (QAnyStringView typeName : std::as_const(missingSupers)) { + // If we've found one valid base type, don't complain about the others. + if (warnAboutSupers + && !unresolvedForeignNames.contains(typeName) + && !isPrimitive(typeName)) { + warning(context) << typeName << "is used as base type but cannot be found."; + } + + processedRelatedNativeNames.insert(typeName); + processedRelatedJavaScriptNames.insert(typeName); + } + }; + + const auto addProperties = [&](const MetaType &context, + const QList<QAnyStringView> &namespaces) { + for (const Property &property : context.properties()) { + ResolvedTypeAlias resolved(property.type, m_usingDeclarations); + if (!resolved.type.isEmpty()) + addType(context, resolved.type, namespaces, "property"); + } + }; + + const auto addMethods = [&](const MetaType &context, + const QList<QAnyStringView> &namespaces) { + for (const Method::Container &methods + : {context.methods(), context.constructors(), context.sigs() }) { + for (const Method &methodObject : methods) { + for (const Argument &argument : std::as_const(methodObject.arguments)) { + ResolvedTypeAlias resolved(argument.type, m_usingDeclarations); + if (!resolved.type.isEmpty()) + addType(context, resolved.type, namespaces, "argument"); + } + + ResolvedTypeAlias resolved(methodObject.returnType, m_usingDeclarations); + if (!resolved.type.isEmpty()) + addType(context, resolved.type, namespaces, "return"); + } + } + }; + + const auto addEnums = [&](const MetaType &context, + const QList<QAnyStringView> &namespaces) { + for (const Enum &enumerator : context.enums()) { + ResolvedTypeAlias resolved(enumerator.type, m_usingDeclarations); + if (!resolved.type.isEmpty()) + addType(context, resolved.type, namespaces, "enum"); + } + }; + + const auto addRelation = [&](const MetaType &classDef, const ClassInfo &obj, + const QList<QAnyStringView> &namespaces) { + const QAnyStringView objNameValue = obj.name; + if (objNameValue == S_ATTACHED) { + addType(classDef, obj.value, namespaces, "attached"); + return true; + } else if (objNameValue == S_SEQUENCE) { + ResolvedTypeAlias value(obj.value, m_usingDeclarations); + addType(classDef, value.type, namespaces, "sequence value"); + return true; + } else if (objNameValue == S_EXTENDED) { + const QAnyStringView value = obj.value; + addType(classDef, value, namespaces, "extension"); + return true; + } + return false; }; // Then recursively iterate the super types and attached types, marking the // ones we are interested in as related. while (!typeQueue.isEmpty()) { - const QJsonObject classDef = typeQueue.dequeue(); - - const auto classInfos = classDef.value(classInfosKey).toArray(); - for (const QJsonValue classInfo : classInfos) { - const QJsonObject obj = classInfo.toObject(); - const QString objNameValue = obj.value(nameKey).toString(); - if (objNameValue == qmlAttachedName || objNameValue == qmlSequenceName - || objNameValue == qmlExtendedName) { - addType(obj.value(valueKey).toString()); - } else if (objNameValue == qmlForeignName) { - const QString foreignClassName = obj.value(valueKey).toString(); - if (const QJsonObject *other = QmlTypesClassDescription::findType( - m_foreignTypes, foreignClassName)) { - const auto otherSupers = other->value(superClassesKey).toArray(); - if (!otherSupers.isEmpty()) { - const QJsonObject otherSuperObject = otherSupers.first().toObject(); - if (otherSuperObject.value(accessKey).toString() == publicAccess) - addType(otherSuperObject.value(nameKey).toString()); - } - - const auto otherClassInfos = other->value(classInfosKey).toArray(); - for (const QJsonValue otherClassInfo : otherClassInfos) { - const QJsonObject obj = otherClassInfo.toObject(); - const QString objNameValue = obj.value(nameKey).toString(); - if (objNameValue == qmlAttachedName || objNameValue == qmlSequenceName - || objNameValue == qmlExtendedName) { - addType(obj.value(valueKey).toString()); - break; - } - // No, you cannot chain QML_FOREIGN declarations. Sorry. - } + QAnyStringView unresolvedForeign; + + const MetaType classDef = typeQueue.dequeue(); + const QList<QAnyStringView> namespaces = MetaTypesJsonProcessor::namespaces(classDef); + + for (const ClassInfo &obj : classDef.classInfos()) { + if (addRelation(classDef, obj, namespaces)) + continue; + if (obj.name != S_FOREIGN) + continue; + + const QAnyStringView foreignClassName = obj.value; + + // A type declared as QML_FOREIGN will usually be a foreign type, but it can + // actually be an additional registration of a local type, too. + if (const FoundType found = QmlTypesClassDescription::findType( + m_foreignTypes, {}, foreignClassName, namespaces)) { + const MetaType other = found.select(classDef, "Foreign"); + const QList<QAnyStringView> otherNamespaces + = MetaTypesJsonProcessor::namespaces(other); + addSupers(other, otherNamespaces); + addProperties(other, otherNamespaces); + addMethods(other, otherNamespaces); + addEnums(other, otherNamespaces); + + for (const ClassInfo &obj : other.classInfos()) { + if (addRelation(classDef, obj, otherNamespaces)) + break; + // No, you cannot chain S_FOREIGN declarations. Sorry. } + } else if (!QmlTypesClassDescription::findType( + m_types, {}, foreignClassName, namespaces)) { + unresolvedForeign = foreignClassName; } } - const auto supers = classDef.value(superClassesKey).toArray(); - if (!supers.isEmpty()) { - const QJsonObject superObject = supers.first().toObject(); - if (superObject.value(accessKey).toString() == publicAccess) - addType(superObject.value(nameKey).toString()); + if (!unresolvedForeign.isEmpty() && !isPrimitive(unresolvedForeign)) { + warning(classDef) + << unresolvedForeign + << "is declared as foreign type, but cannot be found."; } + + addSupers(classDef, namespaces); + addProperties(classDef, namespaces); + addMethods(classDef, namespaces); + addEnums(classDef, namespaces); } } -void MetaTypesJsonProcessor::sortTypes(QVector<QJsonObject> &types) +void MetaTypesJsonProcessor::sortTypes(QVector<MetaType> &types) { - const QLatin1String qualifiedClassNameKey("qualifiedClassName"); - std::sort(types.begin(), types.end(), [&](const QJsonObject &a, const QJsonObject &b) { - return a.value(qualifiedClassNameKey).toString() < - b.value(qualifiedClassNameKey).toString(); - }); + std::sort(types.begin(), types.end(), qualifiedClassNameLessThan); } -QString MetaTypesJsonProcessor::resolvedInclude(const QString &include) +QString MetaTypesJsonProcessor::resolvedInclude(QAnyStringView include) { - return (m_privateIncludes && include.endsWith(QLatin1String("_p.h"))) - ? QLatin1String("private/") + include - : include; + if (!m_privateIncludes) + return include.toString(); + + if (endsWith(include, "_p.h"_L1)) + return QLatin1String("private/") + include.toString(); + + if (startsWith(include, "qplatform"_L1) || startsWith(include, "qwindowsystem"_L1)) + return QLatin1String("qpa/") + include.toString(); + + return include.toString(); } -void MetaTypesJsonProcessor::processTypes(const QJsonObject &types) +void MetaTypesJsonProcessor::processTypes(const QCborMap &types) { - const QString include = resolvedInclude(types[QLatin1String("inputFile")].toString()); - const QJsonArray classes = types[QLatin1String("classes")].toArray(); - for (const QJsonValue cls : classes) { - QJsonObject classDef = cls.toObject(); - classDef.insert(QLatin1String("inputFile"), include); + const QString include = resolvedInclude(toStringView(types, S_INPUT_FILE)); + const QCborArray classes = types[S_CLASSES].toArray(); + for (const QCborValue &cls : classes) { + const MetaType classDef(cls.toMap(), include); - switch (qmlTypeRegistrationMode(classDef)) { + const PreProcessResult preprocessed = preProcess(classDef, PopulateMode::Yes); + switch (preprocessed.mode) { case NamespaceRegistration: case GadgetRegistration: case ObjectRegistration: { - if (!include.endsWith(QLatin1String(".h")) - && !include.endsWith(QLatin1String(".hpp")) - && !include.endsWith(QLatin1String(".hxx")) - && !include.endsWith(QLatin1String(".hh")) - && !include.endsWith(u".py") - && include.contains(QLatin1Char('.'))) { - fprintf(stderr, - "Class %s is declared in %s, which appears not to be a header.\n" - "The compilation of its registration to QML may fail.\n", - qPrintable(classDef.value(QLatin1String("qualifiedClassName")) - .toString()), - qPrintable(include)); + if (!endsWith(include, QLatin1String(".h")) + && !endsWith(include, QLatin1String(".hpp")) + && !endsWith(include, QLatin1String(".hxx")) + && !endsWith(include, QLatin1String(".hh")) + && !endsWith(include, QLatin1String(".py")) + && contains(include, QLatin1Char('.'))) { + warning(include) + << "Class" << classDef.qualifiedClassName() + << "is declared in" << include << "which appears not to be a header." + << "The compilation of its registration to QML may fail."; } m_includes.append(include); - m_types.append(classDef); + m_types.emplaceBack(classDef); break; } case NoRegistration: - m_foreignTypes.append(classDef); + m_foreignTypes.emplaceBack(classDef); break; } + + if (!preprocessed.foreignPrimitive.isEmpty()) { + m_primitiveTypes.emplaceBack(preprocessed.foreignPrimitive); + m_primitiveTypes.append(preprocessed.primitiveAliases); + } + + if (preprocessed.usingDeclaration.isValid()) + m_usingDeclarations.append(preprocessed.usingDeclaration); + } +} + +void MetaTypesJsonProcessor::processForeignTypes(const QCborMap &types) +{ + const QString include = resolvedInclude(toStringView(types, S_INPUT_FILE)); + const QCborArray classes = types[S_CLASSES].toArray(); + for (const QCborValue &cls : classes) { + const MetaType classDef(cls.toMap(), include); + PreProcessResult preprocessed = preProcess(classDef, PopulateMode::No); + + m_foreignTypes.emplaceBack(classDef); + if (!preprocessed.foreignPrimitive.isEmpty()) { + m_primitiveTypes.emplaceBack(preprocessed.foreignPrimitive); + m_primitiveTypes.append(preprocessed.primitiveAliases); + } + + if (preprocessed.usingDeclaration.isValid()) + m_usingDeclarations.append(preprocessed.usingDeclaration); } } -void MetaTypesJsonProcessor::processForeignTypes(const QJsonObject &types) +static QTypeRevision getRevision(const QCborMap &cbor) +{ + const auto it = cbor.find(S_REVISION); + return it == cbor.end() + ? QTypeRevision() + : QTypeRevision::fromEncodedVersion(it->toInteger()); +} + +static Access getAccess(const QCborMap &cbor) +{ + const QAnyStringView access = toStringView(cbor, S_ACCESS); + if (access == S_PUBLIC) + return Access::Public; + if (access == S_PROTECTED) + return Access::Protected; + return Access::Private; +} + +BaseType::BaseType(const QCborMap &cbor) + : name(toStringView(cbor, S_NAME)) + , access(getAccess(cbor)) { - const QString include = resolvedInclude(types[QLatin1String("inputFile")].toString()); - const QJsonArray classes = types[QLatin1String("classes")].toArray(); - for (const QJsonValue cls : classes) { - QJsonObject classDef = cls.toObject(); - classDef.insert(QLatin1String("inputFile"), include); - m_foreignTypes.append(classDef); +} + +ClassInfo::ClassInfo(const QCborMap &cbor) + : name(toStringView(cbor, S_NAME)) + , value(toStringView(cbor, S_VALUE)) +{ +} + +Interface::Interface(const QCborValue &cbor) +{ + if (cbor.isArray()) { + QCborArray needlessWrapping = cbor.toArray(); + className = needlessWrapping.size() > 0 + ? toStringView(needlessWrapping[0].toMap(), S_CLASS_NAME) + : QAnyStringView(); + } else { + className = toStringView(cbor.toMap(), S_CLASS_NAME); } } +Property::Property(const QCborMap &cbor) + : name(toStringView(cbor, S_NAME)) + , type(toStringView(cbor, S_TYPE)) + , member(toStringView(cbor, S_MEMBER)) + , read(toStringView(cbor, S_READ)) + , write(toStringView(cbor, S_WRITE)) + , reset(toStringView(cbor, S_RESET)) + , notify(toStringView(cbor, S_NOTIFY)) + , bindable(toStringView(cbor, S_BINDABLE)) + , privateClass(toStringView(cbor, S_PRIVATE_CLASS)) + , index(cbor[S_INDEX].toInteger(-1)) + , revision(getRevision(cbor)) + , isFinal(cbor[S_FINAL].toBool()) + , isConstant(cbor[S_CONSTANT].toBool()) + , isRequired(cbor[S_REQUIRED].toBool()) +{ +} + +Argument::Argument(const QCborMap &cbor) + : name(toStringView(cbor, S_NAME)) + , type(toStringView(cbor, S_TYPE)) +{ +} + +Method::Method(const QCborMap &cbor, bool isConstructor) + : name(toStringView(cbor, S_NAME)) + , returnType(toStringView(cbor, S_RETURN_TYPE)) + , revision(getRevision(cbor)) + , access(getAccess(cbor)) + , isCloned(cbor[S_IS_CLONED].toBool()) + , isJavaScriptFunction(cbor[S_IS_JAVASCRIPT_FUNCTION].toBool()) + , isConstructor(isConstructor || cbor[S_IS_CONSTRUCTOR].toBool()) +{ + const QCborArray args = cbor[S_ARGUMENTS].toArray(); + for (const QCborValue &argument : args) + arguments.emplace_back(argument.toMap()); + + if (arguments.size() == 1) { + const QAnyStringView type = arguments[0].type; + if (type == "QQmlV4FunctionPtr"_L1 || type == "QQmlV4Function*"_L1) + isJavaScriptFunction = true; + } +} + +Enum::Enum(const QCborMap &cbor) + : name(toStringView(cbor, S_NAME)) + , alias(toStringView(cbor, S_ALIAS)) + , type(toStringView(cbor, S_TYPE)) + , isFlag(cbor[S_IS_FLAG].toBool()) + , isClass(cbor[S_IS_CLASS].toBool()) +{ + const QCborArray vals = cbor[S_VALUES].toArray(); + for (const QCborValue &value : vals) + values.emplace_back(toStringView(value)); +} + +MetaTypePrivate::MetaTypePrivate(const QCborMap &cbor, const QString &inputFile) + : cbor(cbor) + , inputFile(inputFile) +{ + className = toStringView(cbor, S_CLASS_NAME); + qualifiedClassName = toStringView(cbor, S_QUALIFIED_CLASS_NAME); + + const QCborArray cborSuperClasses = cbor[S_SUPER_CLASSES].toArray(); + for (const QCborValue &superClass : cborSuperClasses) + superClasses.emplace_back(superClass.toMap()); + + const QCborArray cborClassInfos = cbor[S_CLASS_INFOS].toArray(); + for (const QCborValue &classInfo : cborClassInfos) + classInfos.emplace_back(classInfo.toMap()); + + const QCborArray cborIfaces = cbor[S_INTERFACES].toArray(); + for (const QCborValue &iface : cborIfaces) + ifaces.emplace_back(iface); + + const QCborArray cborProperties = cbor[S_PROPERTIES].toArray(); + for (const QCborValue &property : cborProperties) + properties.emplace_back(property.toMap()); + + for (const QCborArray &cborMethods : { cbor[S_METHODS].toArray(), cbor[S_SLOTS].toArray() }) { + for (const QCborValue &method : cborMethods) + methods.emplace_back(method.toMap(), false); + } + + const QCborArray cborSigs = cbor[S_SIGNALS].toArray(); + for (const QCborValue &sig : cborSigs) + sigs.emplace_back(sig.toMap(), false); + + const QCborArray cborConstructors = cbor[S_CONSTRUCTORS].toArray(); + for (const QCborValue &constructor : cborConstructors) + constructors.emplace_back(constructor.toMap(), true); + + const QCborArray cborEnums = cbor[S_ENUMS].toArray(); + for (const QCborValue &enumerator : cborEnums) + enums.emplace_back(enumerator.toMap()); + + if (cbor[S_GADGET].toBool()) + kind = Kind::Gadget; + else if (cbor[S_OBJECT].toBool()) + kind = Kind::Object; + else if (cbor[S_NAMESPACE].toBool()) + kind = Kind::Namespace; +} + +MetaType::MetaType(const QCborMap &cbor, const QString &inputFile) + : d(s_pool.emplace_back(std::make_unique<MetaTypePrivate>(cbor, inputFile)).get()) +{} + QT_END_NAMESPACE |