Revision control
Copy as Markdown
// Copyright (C) 2020 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 "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/qdir.h>
#include <QtCore/qfile.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) {
if (m_seenMetaTypesFiles.hasSeen(QDir::cleanPath(source)))
continue;
QCborValue metaObjects;
{
QFile f(source);
if (!f.open(QIODevice::ReadOnly)) {
error(source) << "Cannot open file for reading";
return false;
}
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 QCborArray metaObjectsArray = metaObjects.toArray();
for (const QCborValue &metaObject : metaObjectsArray) {
if (!metaObject.isMap()) {
error(source) << "JSON is not an object";
return false;
}
processTypes(metaObject.toMap());
}
} else if (metaObjects.isMap()) {
processTypes(metaObjects.toMap());
} else {
error(source) << "JSON is not an object or an array";
return false;
}
}
return true;
}
bool MetaTypesJsonProcessor::processForeignTypes(const QString &types)
{
QFile typesFile(types);
if (!typesFile.open(QIODevice::ReadOnly)) {
error(types) << "Cannot open foreign types file";
return false;
}
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;
}
const QCborArray foreignObjectsArray = foreignMetaObjects.toArray();
for (const QCborValue &metaObject : foreignObjectsArray) {
if (!metaObject.isMap()) {
error(types) << "JSON is not an object";
return false;
}
processForeignTypes(metaObject.toMap());
}
return true;
}
bool MetaTypesJsonProcessor::processForeignTypes(const QStringList &foreignTypesFiles)
{
bool success = true;
for (const QString &types : foreignTypesFiles) {
if (m_seenMetaTypesFiles.hasSeen(QDir::cleanPath(types)))
continue;
if (!processForeignTypes(types))
success = false;
}
return success;
}
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(typename QList<String>::const_iterator(newEnd), list->constEnd());
}
void MetaTypesJsonProcessor::postProcessTypes()
{
sortTypes(m_types);
}
void MetaTypesJsonProcessor::postProcessForeignTypes()
{
sortTypes(m_foreignTypes);
sortStringList(&m_primitiveTypes);
sortStringList(&m_usingDeclarations);
addRelatedTypes();
sortStringList(&m_referencedTypes);
sortStringList(&m_includes);
}
QString MetaTypesJsonProcessor::extractRegisteredTypes() const
{
QString registrationHelper;
for (const auto &obj: m_types) {
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;
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 {
qmlElements.append(u"QML_NAMED_ELEMENT("_s + value.toString() + u")");
}
} else if (name == S_CREATABLE && value == S_FALSE) {
isExplicitlyUncreatable = true;
} 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 (qmlElements.isEmpty())
continue; // no relevant entries found
const QString spaces = u" "_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) {
if (qmlUncreatable.isEmpty())
registrationHelper += spaces + uR"(QML_UNCREATABLE(""))" + u"n";
else
registrationHelper += spaces + qmlUncreatable + u"\n";
}
if (!qmlAttached.isEmpty())
registrationHelper += spaces + qmlAttached + u"\n";
registrationHelper += u"}";
if (!isNamespace)
registrationHelper += u";";
registrationHelper += u"\n";
}
return registrationHelper;
}
MetaTypesJsonProcessor::PreProcessResult MetaTypesJsonProcessor::preProcess(
const MetaType &classDef, PopulateMode populateMode)
{
// 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 PreProcessResult {
std::move(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();
}
enum RemoveResult : bool { Removed, NotFound };
static RemoveResult removeFromSortedMetaTypeList(QList<MetaType> &typeList, const MetaType &toBeRemoved)
{
const auto remove = std::equal_range(
typeList.constBegin(), typeList.constEnd(), toBeRemoved,
qualifiedClassNameLessThan);
if (remove.first == remove.second)
return NotFound; // type used in two non-opaque usages; e.g. Base + Extension
for (auto it = remove.first; it != remove.second; ++it) {
if (*it == toBeRemoved) {
typeList.erase(it);
return Removed;
}
}
return NotFound;
}
// inserts toBeAdded if it's not in the list
static void insertIntoSortedMetaTypeList(QList<MetaType> &typeList, const MetaType &toBeAdded)
{
const auto [lower, upper] = std::equal_range(
typeList.constBegin(), typeList.constEnd(), toBeAdded,
qualifiedClassNameLessThan);
if (lower == upper)
typeList.insert(lower, toBeAdded);
}
static bool sortedMetaTypeListContains(const QList<MetaType> &typeList, const MetaType &type)
{
const auto [first, last] = std::equal_range(
typeList.constBegin(), typeList.constEnd(), type, qualifiedClassNameLessThan);
for (auto it = first; it != last; ++it) {
if (*it == type)
return true;
}
return false;
}
enum class TypeRelation
{
Base, Property, Argument, Return, Enum, Attached, SequenceValue, Extension
};
// Whether the type whose related types are currently being processed is itself opaque.
enum class ContextOpacity
{
FullyRegistered, Opaque
};
static QLatin1StringView typeRelationString(TypeRelation relation)
{
switch (relation) {
case TypeRelation::Property: return "property"_L1;
case TypeRelation::Argument: return "argument"_L1;
case TypeRelation::Return: return "return"_L1;
case TypeRelation::Enum: return "enum"_L1;
case TypeRelation::Attached: return "attached"_L1;
case TypeRelation::SequenceValue: return "sequence value"_L1;
case TypeRelation::Extension: return "extension"_L1;
default:
break;
}
Q_UNREACHABLE_RETURN(QLatin1StringView());
}
void MetaTypesJsonProcessor::addRelatedTypes()
{
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 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;
}
}
}
// Then mark all classes registered from other modules as already processed.
// We don't want to generate them again for this module.
for (const MetaType &foreignType : std::as_const(m_foreignTypes)) {
bool seenQmlPrefix = false;
for (const ClassInfo &obj : foreignType.classInfos()) {
const QAnyStringView name = obj.name;
if (!seenQmlPrefix && startsWith(name, "QML."_L1)) {
addRelatedType(foreignType);
seenQmlPrefix = true;
}
if (name == S_FOREIGN
|| name == S_EXTENDED
|| name == S_ATTACHED
|| name == S_SEQUENCE) {
ResolvedTypeAlias foreign(obj.value, m_usingDeclarations);
if (!addRelatedName(foreign.type, namespaces(foreignType)))
unresolvedForeignNames.insert(foreign.type);
}
}
}
const auto addReference
= [&](const MetaType &type, QSet<QAnyStringView> *processedRelatedNames,
FoundType::Origin origin, TypeRelation relation,
ContextOpacity contextOpacity = ContextOpacity::FullyRegistered) {
if (type.isEmpty())
return;
QAnyStringView qualifiedName = type.qualifiedClassName();
m_referencedTypes.append(qualifiedName);
const qsizetype size = processedRelatedNames->size();
processedRelatedNames->insert(qualifiedName);
if (processedRelatedNames->size() == size) {
// a type might change from opaque to non-opaque
// Remove from opaque types - might happen if the type is used as both a property
// and e.g. as a base type
switch (relation) {
case TypeRelation::Property:
case TypeRelation::Return:
case TypeRelation::Argument:
// still only opaque, we can return
return;
case TypeRelation::Base:
// A base type only stays opaque if the type it is a base of is opaque, too.
// Otherwise it has to be properly registered and we fall through to the regular
// handling below.
if (contextOpacity == ContextOpacity::Opaque)
return;
break;
default:
break;
}
if (removeFromSortedMetaTypeList(m_opaqueTypes, type) == NotFound)
return; // type used in two non-opaque usages; e.g. Base + Extension
} else {
typeQueue.enqueue(type);
}
if (origin == FoundType::OwnTypes)
return;
switch (relation) {
case TypeRelation::Property:
case TypeRelation::Return:
case TypeRelation::Argument:
insertIntoSortedMetaTypeList(m_opaqueTypes, type);
return;
case TypeRelation::Base:
// Only the bases of opaque types are themselves opaque. The bases of regular types
// are registered normally, just like any other related type.
if (contextOpacity == ContextOpacity::Opaque) {
insertIntoSortedMetaTypeList(m_opaqueTypes, type);
return;
}
break;
default:
break;
}
// Add to own types since we need it for our registrations.
insertIntoSortedMetaTypeList(m_types, type);
// We only add types to m_types of which we know we can reach them via the existing
// m_includes. We do not add to m_includes, because any further headers may not be
// #include'able.
// Remove from the foreign types to avoid the ODR warning.
removeFromSortedMetaTypeList(m_foreignTypes, type);
};
const auto addInterface
= [&](QAnyStringView typeName, const QList<QAnyStringView> &namespaces,
TypeRelation relation, ContextOpacity contextOpacity = ContextOpacity::FullyRegistered) {
if (const FoundType other = QmlTypesClassDescription::findType(
m_types, m_foreignTypes, typeName, namespaces)) {
if (!other.native.isEmpty()) {
addReference(
other.native, &processedRelatedNativeNames, other.nativeOrigin, relation,
contextOpacity);
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 doAddReferences = [&](QAnyStringView typeName,
const QList<QAnyStringView> &namespaces,
TypeRelation relation,
ContextOpacity contextOpacity = ContextOpacity::FullyRegistered) {
if (const FoundType other = QmlTypesClassDescription::findType(
m_types, m_foreignTypes, typeName, namespaces)) {
addReference(
other.native, &processedRelatedNativeNames, other.nativeOrigin, relation,
contextOpacity);
addReference(
other.javaScript, &processedRelatedJavaScriptNames, other.javaScriptOrigin,
relation, contextOpacity);
return true;
}
return false;
};
const auto addType = [&](const MetaType &context, QAnyStringView typeName,
const QList<QAnyStringView> &namespaces, TypeRelation relation) {
if (doAddReferences(typeName, namespaces, relation))
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,
relation);
addReference(
other.javaScript, &processedRelatedJavaScriptNames,
other.javaScriptOrigin, relation);
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)) {
// Also, if the type is used as a property/method argument, we want to still make it
// known to tooling as an opaque type, and don't warn about it
switch (relation) {
case TypeRelation::Property:
case TypeRelation::Return:
case TypeRelation::Argument: {
MetaType type = MetaType::createOpaqueType(typeName);
insertIntoSortedMetaTypeList(m_opaqueTypes, type);
break;
}
default:
warning(context) << typeName << "is used as" << typeRelationString(relation)
<< "type but cannot be found.";
}
}
processedRelatedNativeNames.insert(typeName);
processedRelatedJavaScriptNames.insert(typeName);
return false;
};
const auto addSupers = [&](const MetaType &context, const QList<QAnyStringView> &namespaces,
ContextOpacity contextOpacity) {
for (const Interface &iface : context.ifaces())
addInterface(interfaceName(iface), namespaces, TypeRelation::Base, contextOpacity);
// 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, TypeRelation::Base, contextOpacity))
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, TypeRelation::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) {
if (methodObject.access != Access::Public)
continue;
for (const Argument &argument : std::as_const(methodObject.arguments)) {
ResolvedTypeAlias resolved(argument.type, m_usingDeclarations);
if (!resolved.type.isEmpty())
addType(context, resolved.type, namespaces, TypeRelation::Argument);
}
ResolvedTypeAlias resolved(methodObject.returnType, m_usingDeclarations);
if (!resolved.type.isEmpty())
addType(context, resolved.type, namespaces, TypeRelation::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, TypeRelation::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, TypeRelation::Attached);
return true;
} else if (objNameValue == S_SEQUENCE) {
ResolvedTypeAlias value(obj.value, m_usingDeclarations);
addType(classDef, value.type, namespaces, TypeRelation::SequenceValue);
return true;
} else if (objNameValue == S_EXTENDED) {
const QAnyStringView value = obj.value;
addType(classDef, value, namespaces, TypeRelation::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()) {
QAnyStringView unresolvedForeign;
const MetaType classDef = typeQueue.dequeue();
const QList<QAnyStringView> namespaces = MetaTypesJsonProcessor::namespaces(classDef);
const ContextOpacity classDefOpacity = sortedMetaTypeListContains(m_opaqueTypes, classDef)
? ContextOpacity::Opaque
: ContextOpacity::FullyRegistered;
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, classDefOpacity);
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;
}
}
if (!unresolvedForeign.isEmpty() && !isPrimitive(unresolvedForeign)) {
warning(classDef)
<< unresolvedForeign
<< "is declared as foreign type, but cannot be found.";
}
addSupers(classDef, namespaces, classDefOpacity);
addProperties(classDef, namespaces);
addMethods(classDef, namespaces);
addEnums(classDef, namespaces);
}
}
void MetaTypesJsonProcessor::sortTypes(QList<MetaType> &types)
{
std::sort(types.begin(), types.end(), qualifiedClassNameLessThan);
}
QString MetaTypesJsonProcessor::resolvedInclude(QAnyStringView 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 QCborMap &types)
{
const QString include = resolvedInclude(types[S_INPUT_FILE].toStringView());
const QCborArray classes = types[S_CLASSES].toArray();
for (const QCborValue &cls : classes) {
const MetaType classDef(cls.toMap(), include);
const PreProcessResult preprocessed = preProcess(classDef, PopulateMode::Yes);
switch (preprocessed.mode) {
case NamespaceRegistration:
case GadgetRegistration:
case ObjectRegistration: {
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.emplaceBack(classDef);
break;
}
case NoRegistration:
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(types[S_INPUT_FILE].toStringView());
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);
}
}
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 = cbor[S_ACCESS].toStringView();
if (access == S_PUBLIC)
return Access::Public;
if (access == S_PROTECTED)
return Access::Protected;
return Access::Private;
}
BaseType::BaseType(const QCborMap &cbor)
: name(cbor[S_NAME].toStringView())
, access(getAccess(cbor))
{
}
ClassInfo::ClassInfo(const QCborMap &cbor)
: name(cbor[S_NAME].toStringView())
, value(cbor[S_VALUE].toStringView())
{
}
Interface::Interface(const QCborValue &cbor)
{
if (cbor.isArray()) {
QCborArray needlessWrapping = cbor.toArray();
className = needlessWrapping.size() > 0
? needlessWrapping[0].toMap()[S_CLASS_NAME].toStringView()
: QAnyStringView();
} else {
className = cbor.toMap()[S_CLASS_NAME].toStringView();
}
}
Property::Property(const QCborMap &cbor)
: name(cbor[S_NAME].toStringView())
, type(cbor[S_TYPE].toStringView())
, member(cbor[S_MEMBER].toStringView())
, read(cbor[S_READ].toStringView())
, write(cbor[S_WRITE].toStringView())
, reset(cbor[S_RESET].toStringView())
, notify(cbor[S_NOTIFY].toStringView())
, bindable(cbor[S_BINDABLE].toStringView())
, privateClass(cbor[S_PRIVATE_CLASS].toStringView())
, index(cbor[S_INDEX].toInteger(-1))
, lineNumber(cbor[S_LINENUMBER].toInteger(0))
, revision(getRevision(cbor))
, isFinal(cbor[S_FINAL].toBool())
, isVirtual(cbor[S_VIRTUAL].toBool())
, isOverride(cbor[S_OVERRIDE].toBool())
, isConstant(cbor[S_CONSTANT].toBool())
, isRequired(cbor[S_REQUIRED].toBool())
{
}
Argument::Argument(const QCborMap &cbor)
: name(cbor[S_NAME].toStringView())
, type(cbor[S_TYPE].toStringView())
{
}
Method::Method(const QCborMap &cbor, bool isConstructor)
: name(cbor[S_NAME].toStringView())
, returnType(cbor[S_RETURN_TYPE].toStringView())
, index(cbor[S_INDEX].toInteger(InvalidIndex))
, lineNumber(cbor[S_LINENUMBER].toInteger(0))
, 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())
, isConst(cbor[S_IS_CONST].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;
arguments.clear();
}
}
}
Enum::Enum(const QCborMap &cbor)
: name(cbor[S_NAME].toStringView())
, alias(cbor[S_ALIAS].toStringView())
, type(cbor[S_TYPE].toStringView())
, lineNumber(cbor[S_LINENUMBER].toInteger(0))
, 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(value.toStringView());
}
MetaTypePrivate::MetaTypePrivate(const QCborMap &cbor, const QString &inputFile)
: cbor(cbor)
, inputFile(inputFile)
{
className = cbor[S_CLASS_NAME].toStringView();
lineNumber = cbor[S_LINENUMBER].toInteger(0);
qualifiedClassName = cbor[S_QUALIFIED_CLASS_NAME].toStringView();
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_SLOTS].toArray(), cbor[S_METHODS].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())
{}
/* metatype for a type about which we don't know anything
Caller is expected to track duplicates, as we otherwise end up with two opaque types
of the same name
*/
MetaType MetaType::createOpaqueType(QAnyStringView typeName)
{
auto priv = s_pool.emplace_back(std::make_unique<MetaTypePrivate>()).get();
priv->className = typeName;
priv->qualifiedClassName = typeName;
priv->kind = MetaType::Kind::Gadget;
MetaType type;
type.d = priv;
return type;
}
QT_END_NAMESPACE