QtQml: Allow multiple names for C++-based QML types

[ChangeLog][QtQml] You can now have multiple QML_NAMED_ELEMENT macros in
the same C++ class to make one C++ type available with more than one QML
name.

Task-number: QTBUG-101143
Change-Id: I5aeab00a3cd8b3bb11a5a7d5b87ff2e82f57bd7f
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Ulf Hermann 2023-11-08 17:19:42 +01:00
parent 63783a21ab
commit f5aecbde91
9 changed files with 355 additions and 213 deletions

View File

@ -507,13 +507,250 @@ static int finalizeType(const QQmlType &dtype)
return dtype.index();
}
using ElementNames = QVarLengthArray<const char *, 8>;
static ElementNames classElementNames(const QMetaObject *metaObject)
{
Q_ASSERT(metaObject);
const char *key = "QML.Element";
const int offset = metaObject->classInfoOffset();
const int start = metaObject->classInfoCount() + offset - 1;
ElementNames elementNames;
for (int i = start; i >= offset; --i) {
const QMetaClassInfo classInfo = metaObject->classInfo(i);
if (qstrcmp(key, classInfo.name()) == 0) {
const char *elementName = classInfo.value();
if (qstrcmp(elementName, "auto") == 0) {
const char *strippedClassName = metaObject->className();
for (const char *c = strippedClassName; *c != '\0'; c++) {
if (*c == ':')
strippedClassName = c + 1;
}
elementName = strippedClassName;
} else if (qstrcmp(elementName, "anonymous") == 0) {
if (elementNames.isEmpty())
elementNames.push_back(nullptr);
else if (elementNames[0] != nullptr)
qWarning() << metaObject->className() << "is both anonymous and named";
continue;
}
if (!elementNames.isEmpty() && elementNames[0] == nullptr) {
qWarning() << metaObject->className() << "is both anonymous and named";
elementNames[0] = elementName;
} else {
elementNames.push_back(elementName);
}
}
}
return elementNames;
}
struct AliasRegistrar
{
AliasRegistrar(const ElementNames *elementNames) : elementNames(elementNames) {}
void registerAliases(int typeId)
{
if (elementNames) {
for (int i = 1, end = elementNames->length(); i < end; ++i)
otherNames.append(QString::fromUtf8(elementNames->at(i)));
elementNames = nullptr;
}
for (const QString &otherName : std::as_const(otherNames))
QQmlMetaType::registerTypeAlias(typeId, otherName);
}
private:
const ElementNames *elementNames;
QVarLengthArray<QString, 8> otherNames;
};
static void doRegisterTypeAndRevisions(
const QQmlPrivate::RegisterTypeAndRevisions &type,
const ElementNames &elementNames)
{
using namespace QQmlPrivate;
const bool isValueType = !(type.typeId.flags() & QMetaType::PointerToQObject);
const bool creatable = (elementNames[0] != nullptr || isValueType)
&& boolClassInfo(type.classInfoMetaObject, "QML.Creatable", true);
QString noCreateReason;
ValueTypeCreationMethod creationMethod = ValueTypeCreationMethod::None;
if (!creatable) {
noCreateReason = QString::fromUtf8(
classInfo(type.classInfoMetaObject, "QML.UncreatableReason"));
if (noCreateReason.isEmpty())
noCreateReason = QLatin1String("Type cannot be created in QML.");
} else if (isValueType) {
const char *method = classInfo(type.classInfoMetaObject, "QML.CreationMethod");
if (qstrcmp(method, "structured") == 0)
creationMethod = ValueTypeCreationMethod::Structured;
else if (qstrcmp(method, "construct") == 0)
creationMethod = ValueTypeCreationMethod::Construct;
}
RegisterType typeRevision = {
QQmlPrivate::RegisterType::CurrentVersion,
type.typeId,
type.listId,
creatable ? type.objectSize : 0,
nullptr,
nullptr,
noCreateReason,
type.createValueType,
type.uri,
type.version,
nullptr,
type.metaObject,
type.attachedPropertiesFunction,
type.attachedPropertiesMetaObject,
type.parserStatusCast,
type.valueSourceCast,
type.valueInterceptorCast,
type.extensionObjectCreate,
type.extensionMetaObject,
nullptr,
QTypeRevision(),
type.structVersion > 0 ? type.finalizerCast : -1,
creationMethod
};
QQmlPrivate::RegisterSequentialContainer sequenceRevision = {
0,
type.uri,
type.version,
nullptr,
type.listId,
type.structVersion > 1 ? type.listMetaSequence : QMetaSequence(),
QTypeRevision(),
};
const QTypeRevision added = revisionClassInfo(
type.classInfoMetaObject, "QML.AddedInVersion",
QTypeRevision::fromVersion(type.version.majorVersion(), 0));
const QTypeRevision removed = revisionClassInfo(
type.classInfoMetaObject, "QML.RemovedInVersion");
const QList<QTypeRevision> furtherRevisions = revisionClassInfos(type.classInfoMetaObject,
"QML.ExtraVersion");
auto revisions = prepareRevisions(type.metaObject, added) + furtherRevisions;
if (type.attachedPropertiesMetaObject)
revisions += availableRevisions(type.attachedPropertiesMetaObject);
uniqueRevisions(&revisions, type.version, added);
AliasRegistrar aliasRegistrar(&elementNames);
for (QTypeRevision revision : revisions) {
if (revision.hasMajorVersion() && revision.majorVersion() > type.version.majorVersion())
break;
assignVersions(&typeRevision, revision, type.version);
// When removed or before added, we still add revisions, but anonymous ones
if (typeRevision.version < added
|| (removed.isValid() && !(typeRevision.version < removed))) {
typeRevision.elementName = nullptr;
typeRevision.create = nullptr;
typeRevision.userdata = nullptr;
} else {
typeRevision.elementName = elementNames[0];
typeRevision.create = creatable ? type.create : nullptr;
typeRevision.userdata = type.userdata;
}
typeRevision.customParser = type.customParserFactory();
const int id = qmlregister(TypeRegistration, &typeRevision);
if (type.qmlTypeIds)
type.qmlTypeIds->append(id);
if (typeRevision.elementName)
aliasRegistrar.registerAliases(id);
if (sequenceRevision.metaSequence != QMetaSequence()) {
sequenceRevision.version = typeRevision.version;
sequenceRevision.revision = typeRevision.revision;
const int id = QQmlPrivate::qmlregister(
QQmlPrivate::SequentialContainerRegistration, &sequenceRevision);
if (type.qmlTypeIds)
type.qmlTypeIds->append(id);
}
}
}
static void doRegisterSingletonAndRevisions(
const QQmlPrivate::RegisterSingletonTypeAndRevisions &type,
const ElementNames &elementNames)
{
using namespace QQmlPrivate;
RegisterSingletonType revisionRegistration = {
0,
type.uri,
type.version,
elementNames[0],
nullptr,
type.qObjectApi,
type.instanceMetaObject,
type.typeId,
type.extensionObjectCreate,
type.extensionMetaObject,
QTypeRevision()
};
const QQmlType::SingletonInstanceInfo::ConstPtr siinfo
= singletonInstanceInfo(revisionRegistration);
const QTypeRevision added = revisionClassInfo(
type.classInfoMetaObject, "QML.AddedInVersion",
QTypeRevision::fromVersion(type.version.majorVersion(), 0));
const QTypeRevision removed = revisionClassInfo(
type.classInfoMetaObject, "QML.RemovedInVersion");
const QList<QTypeRevision> furtherRevisions = revisionClassInfos(type.classInfoMetaObject,
"QML.ExtraVersion");
auto revisions = prepareRevisions(type.instanceMetaObject, added) + furtherRevisions;
uniqueRevisions(&revisions, type.version, added);
AliasRegistrar aliasRegistrar(&elementNames);
for (QTypeRevision revision : std::as_const(revisions)) {
if (revision.hasMajorVersion() && revision.majorVersion() > type.version.majorVersion())
break;
assignVersions(&revisionRegistration, revision, type.version);
// When removed or before added, we still add revisions, but anonymous ones
if (revisionRegistration.version < added
|| (removed.isValid() && !(revisionRegistration.version < removed))) {
revisionRegistration.typeName = nullptr;
revisionRegistration.qObjectApi = nullptr;
} else {
revisionRegistration.typeName = elementNames[0];
revisionRegistration.qObjectApi = type.qObjectApi;
}
const int id = finalizeType(
QQmlMetaType::registerSingletonType(revisionRegistration, siinfo));
if (type.qmlTypeIds)
type.qmlTypeIds->append(id);
if (revisionRegistration.typeName)
aliasRegistrar.registerAliases(id);
}
}
/*
This method is "over generalized" to allow us to (potentially) register more types of things in
the future without adding exported symbols.
*/
int QQmlPrivate::qmlregister(RegistrationType type, void *data)
{
switch (type) {
case AutoParentRegistration:
return QQmlMetaType::registerAutoParentFunction(
@ -523,163 +760,29 @@ int QQmlPrivate::qmlregister(RegistrationType type, void *data)
*reinterpret_cast<RegisterQmlUnitCacheHook *>(data));
case TypeAndRevisionsRegistration: {
const RegisterTypeAndRevisions &type = *reinterpret_cast<RegisterTypeAndRevisions *>(data);
const char *elementName = (type.structVersion > 1 && type.forceAnonymous)
? nullptr
: classElementName(type.classInfoMetaObject);
const bool isValueType = !(type.typeId.flags() & QMetaType::PointerToQObject);
const bool creatable = (elementName != nullptr || isValueType)
&& boolClassInfo(type.classInfoMetaObject, "QML.Creatable", true);
QString noCreateReason;
ValueTypeCreationMethod creationMethod = ValueTypeCreationMethod::None;
if (!creatable) {
noCreateReason = QString::fromUtf8(
classInfo(type.classInfoMetaObject, "QML.UncreatableReason"));
if (noCreateReason.isEmpty())
noCreateReason = QLatin1String("Type cannot be created in QML.");
} else if (isValueType) {
const char *method = classInfo(type.classInfoMetaObject, "QML.CreationMethod");
if (qstrcmp(method, "structured") == 0)
creationMethod = ValueTypeCreationMethod::Structured;
else if (qstrcmp(method, "construct") == 0)
creationMethod = ValueTypeCreationMethod::Construct;
}
RegisterType typeRevision = {
QQmlPrivate::RegisterType::CurrentVersion,
type.typeId,
type.listId,
creatable ? type.objectSize : 0,
nullptr,
nullptr,
noCreateReason,
type.createValueType,
type.uri,
type.version,
nullptr,
type.metaObject,
type.attachedPropertiesFunction,
type.attachedPropertiesMetaObject,
type.parserStatusCast,
type.valueSourceCast,
type.valueInterceptorCast,
type.extensionObjectCreate,
type.extensionMetaObject,
nullptr,
QTypeRevision(),
type.structVersion > 0 ? type.finalizerCast : -1,
creationMethod
};
QQmlPrivate::RegisterSequentialContainer sequenceRevision = {
0,
type.uri,
type.version,
nullptr,
type.listId,
type.structVersion > 1 ? type.listMetaSequence : QMetaSequence(),
QTypeRevision(),
};
const QTypeRevision added = revisionClassInfo(
type.classInfoMetaObject, "QML.AddedInVersion",
QTypeRevision::fromVersion(type.version.majorVersion(), 0));
const QTypeRevision removed = revisionClassInfo(
type.classInfoMetaObject, "QML.RemovedInVersion");
const QList<QTypeRevision> furtherRevisions = revisionClassInfos(type.classInfoMetaObject,
"QML.ExtraVersion");
auto revisions = prepareRevisions(type.metaObject, added) + furtherRevisions;
if (type.attachedPropertiesMetaObject)
revisions += availableRevisions(type.attachedPropertiesMetaObject);
uniqueRevisions(&revisions, type.version, added);
for (QTypeRevision revision : revisions) {
if (revision.hasMajorVersion() && revision.majorVersion() > type.version.majorVersion())
break;
assignVersions(&typeRevision, revision, type.version);
// When removed or before added, we still add revisions, but anonymous ones
if (typeRevision.version < added
|| (removed.isValid() && !(typeRevision.version < removed))) {
typeRevision.elementName = nullptr;
typeRevision.create = nullptr;
typeRevision.userdata = nullptr;
if (type.structVersion > 1 && type.forceAnonymous) {
doRegisterTypeAndRevisions(type, {nullptr});
} else {
const ElementNames names = classElementNames(type.classInfoMetaObject);
if (names.isEmpty()) {
qWarning().nospace() << "Missing QML.Element class info for "
<< type.classInfoMetaObject->className();
} else {
typeRevision.elementName = elementName;
typeRevision.create = creatable ? type.create : nullptr;
typeRevision.userdata = type.userdata;
doRegisterTypeAndRevisions(type, names);
}
typeRevision.customParser = type.customParserFactory();
const int id = qmlregister(TypeRegistration, &typeRevision);
if (type.qmlTypeIds)
type.qmlTypeIds->append(id);
if (sequenceRevision.metaSequence != QMetaSequence()) {
sequenceRevision.version = typeRevision.version;
sequenceRevision.revision = typeRevision.revision;
const int id = QQmlPrivate::qmlregister(
QQmlPrivate::SequentialContainerRegistration, &sequenceRevision);
if (type.qmlTypeIds)
type.qmlTypeIds->append(id);
}
}
break;
}
case SingletonAndRevisionsRegistration: {
const RegisterSingletonTypeAndRevisions &type
= *reinterpret_cast<RegisterSingletonTypeAndRevisions *>(data);
const char *elementName = classElementName(type.classInfoMetaObject);
RegisterSingletonType revisionRegistration = {
0,
type.uri,
type.version,
elementName,
nullptr,
type.qObjectApi,
type.instanceMetaObject,
type.typeId,
type.extensionObjectCreate,
type.extensionMetaObject,
QTypeRevision()
};
const QQmlType::SingletonInstanceInfo::ConstPtr siinfo
= singletonInstanceInfo(revisionRegistration);
const QTypeRevision added = revisionClassInfo(
type.classInfoMetaObject, "QML.AddedInVersion",
QTypeRevision::fromVersion(type.version.majorVersion(), 0));
const QTypeRevision removed = revisionClassInfo(
type.classInfoMetaObject, "QML.RemovedInVersion");
const QList<QTypeRevision> furtherRevisions = revisionClassInfos(type.classInfoMetaObject,
"QML.ExtraVersion");
auto revisions = prepareRevisions(type.instanceMetaObject, added) + furtherRevisions;
uniqueRevisions(&revisions, type.version, added);
for (QTypeRevision revision : std::as_const(revisions)) {
if (revision.hasMajorVersion() && revision.majorVersion() > type.version.majorVersion())
break;
assignVersions(&revisionRegistration, revision, type.version);
// When removed or before added, we still add revisions, but anonymous ones
if (revisionRegistration.version < added
|| (removed.isValid() && !(revisionRegistration.version < removed))) {
revisionRegistration.typeName = nullptr;
revisionRegistration.qObjectApi = nullptr;
} else {
revisionRegistration.typeName = elementName;
revisionRegistration.qObjectApi = type.qObjectApi;
}
const int id = finalizeType(
QQmlMetaType::registerSingletonType(revisionRegistration, siinfo));
if (type.qmlTypeIds)
type.qmlTypeIds->append(id);
const ElementNames names = classElementNames(type.classInfoMetaObject);
if (names.isEmpty()) {
qWarning().nospace() << "Missing QML.Element class info for "
<< type.classInfoMetaObject->className();
} else {
doRegisterSingletonAndRevisions(type, names);
}
break;
}

View File

@ -838,29 +838,6 @@ namespace QQmlPrivate
return qstrcmp(metaObject->classInfo(index).value(), "true") == 0;
}
inline const char *classElementName(const QMetaObject *metaObject)
{
const char *elementName = classInfo(metaObject, "QML.Element");
if (qstrcmp(elementName, "auto") == 0) {
const char *strippedClassName = metaObject->className();
for (const char *c = strippedClassName; *c != '\0'; c++) {
if (*c == ':')
strippedClassName = c + 1;
}
return strippedClassName;
}
if (qstrcmp(elementName, "anonymous") == 0)
return nullptr;
if (!elementName) {
qWarning().nospace() << "Missing QML.Element class info \"" << elementName << "\""
<< " for " << metaObject->className();
}
return elementName;
}
template<class T, class = std::void_t<>>
struct QmlExtended
{

View File

@ -152,7 +152,7 @@ QString MetaTypesJsonProcessor::extractRegisteredTypes() const
const QString className = obj[S_CLASS_NAME].toString();
const QString foreignClassName = className + u"Foreign";
const auto classInfos = obj[S_CLASS_INFOS].toArray();
QString qmlElement;
QStringList qmlElements;
QString qmlUncreatable;
QString qmlAttached;
bool isSingleton = false;
@ -163,11 +163,11 @@ QString MetaTypesJsonProcessor::extractRegisteredTypes() const
const auto value = toStringView(entry, S_VALUE);
if (name == S_ELEMENT) {
if (value == S_AUTO) {
qmlElement = u"QML_NAMED_ELEMENT("_s + className + u")"_s;
qmlElements.append(u"QML_NAMED_ELEMENT("_s + className + u")"_s);
} else if (value == S_ANONYMOUS) {
qmlElement = u"QML_ANONYMOUS"_s;
qmlElements.append(u"QML_ANONYMOUS"_s);
} else {
qmlElement = u"QML_NAMED_ELEMENT("_s + value.toString() + u")";
qmlElements.append(u"QML_NAMED_ELEMENT("_s + value.toString() + u")");
}
} else if (name == S_CREATABLE && value == S_FALSE) {
isExplicitlyUncreatable = true;
@ -179,12 +179,12 @@ QString MetaTypesJsonProcessor::extractRegisteredTypes() const
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;
registrationHelper += spaces + qmlElements.join(u"\n"_s) + u"\n"_s;
if (isSingleton)
registrationHelper += spaces + u"QML_SINGLETON\n"_s;
if (isExplicitlyUncreatable) {

View File

@ -222,8 +222,7 @@ void QmlTypeRegistrar::write(QTextStream &output)
bool targetIsNamespace = classDef.value(S_NAMESPACE).toBool();
QAnyStringView extendedName;
bool seenQmlElement = false;
QString qmlElementName;
QStringList qmlElementNames;
QTypeRevision addedIn;
QTypeRevision removedIn;
@ -232,8 +231,7 @@ void QmlTypeRegistrar::write(QTextStream &output)
const QCborMap v = info.toMap();
const QAnyStringView name = toStringView(v, S_NAME);
if (name == S_ELEMENT) {
seenQmlElement = true;
qmlElementName = v[S_VALUE].toString();
qmlElementNames.append(v[S_VALUE].toString());
} else if (name == S_FOREIGN) {
targetName = v[S_VALUE].toString();
} else if (name == S_FOREIGN_IS_NAMESPACE) {
@ -251,7 +249,9 @@ void QmlTypeRegistrar::write(QTextStream &output)
}
}
if (seenQmlElement && qmlElementName != S_ANONYMOUS) {
for (QString &qmlElementName : qmlElementNames) {
if (qmlElementName == S_ANONYMOUS)
continue;
if (qmlElementName == S_AUTO)
qmlElementName = className;
qmlElementInfos[qmlElementName].append({ className, addedIn, removedIn });
@ -300,7 +300,7 @@ void QmlTypeRegistrar::write(QTextStream &output)
return result;
};
if (seenQmlElement) {
if (!qmlElementNames.isEmpty()) {
output << uR"(
qmlRegisterNamespaceAndRevisions(%1, "%2", %3, nullptr, %4, %5);)"_s
.arg(metaObjectPointer(targetName), m_module)
@ -310,7 +310,7 @@ void QmlTypeRegistrar::write(QTextStream &output)
: metaObjectPointer(extendedName));
}
} else {
if (seenQmlElement) {
if (!qmlElementNames.isEmpty()) {
auto checkRevisions = [&](const QCborArray &array, QLatin1StringView type) {
for (auto it = array.constBegin(); it != array.constEnd(); ++it) {
auto object = it->toMap();

View File

@ -179,9 +179,9 @@ void QmlTypesClassDescription::collect(
// These only apply to the original class
if (name == S_ELEMENT) {
if (value == S_AUTO)
elementName = classDefName;
elementNames.append(classDefName);
else if (value != S_ANONYMOUS)
elementName = value;
elementNames.append(value);
} else if (name == S_CREATABLE) {
isCreatable = (value != S_FALSE);
} else if (name == S_CREATION_METHOD) {
@ -228,7 +228,7 @@ void QmlTypesClassDescription::collect(
}
}
if (addedInRevision.isValid() && !elementName.isEmpty())
if (addedInRevision.isValid() && !elementNames.isEmpty())
revisions.append(addedInRevision);
// If the local type is a namespace the result can only be a namespace,
@ -280,7 +280,7 @@ void QmlTypesClassDescription::collect(
}
if (classDef) {
if (mode == RelatedType || !elementName.isEmpty()) {
if (mode == RelatedType || !elementNames.isEmpty()) {
collectExtraVersions(classDef, S_PROPERTIES, revisions);
collectExtraVersions(classDef, S_SLOTS, revisions);
collectExtraVersions(classDef, S_METHODS, revisions);
@ -324,23 +324,34 @@ void QmlTypesClassDescription::collect(
isCreatable = isConstructible;
if (!classDef) {
if (elementName.isEmpty() || elementName.front().isLower()) {
if (elementNames.isEmpty()) {
// If no classDef, we generally assume it's a value type defined by the
// foreign/extended trick.
accessSemantics = DotQmltypes::S_VALUE;
} else {
// Objects and namespaces always have metaobjects and therefore classDefs.
// However, we may not be able to resolve the metaobject at compile time. See
// the "Invisible" test case. In that case, we must not assume anything about
// access semantics.
}
qWarning() << "Warning: Refusing to generate non-lowercase name"
<< elementName.toString() << "for unknown foreign type";
elementName = {};
for (auto elementName = elementNames.begin(); elementName != elementNames.end();) {
if (elementName->isEmpty() || elementName->front().isLower()) {
// If no classDef, we generally assume it's a value type defined by the
// foreign/extended trick.
accessSemantics = DotQmltypes::S_VALUE;
++elementName;
} else {
// Objects and namespaces always have metaobjects and therefore classDefs.
// However, we may not be able to resolve the metaobject at compile time. See
// the "Invisible" test case. In that case, we must not assume anything about
// access semantics.
// Make it completely inaccessible.
// We cannot get enums from anonymous types after all.
accessSemantics = DotQmltypes::S_NONE;
qWarning() << "Warning: Refusing to generate non-lowercase name"
<< elementName->toString() << "for unknown foreign type";
elementName = elementNames.erase(elementName);
if (elementNames.isEmpty()) {
// Make it completely inaccessible.
// We cannot get enums from anonymous types after all.
accessSemantics = DotQmltypes::S_NONE;
}
}
}
} else if (classDef->value(S_GADGET).toBool()) {
accessSemantics = DotQmltypes::S_VALUE;

View File

@ -31,7 +31,7 @@ struct QmlTypesClassDescription
const QCborMap *resolvedClass = nullptr;
QAnyStringView file;
QAnyStringView className;
QAnyStringView elementName;
QList<QAnyStringView> elementNames;
QAnyStringView defaultProp;
QAnyStringView parentProp;
QAnyStringView superClass;

View File

@ -71,11 +71,13 @@ void QmlTypesCreator::writeClassProperties(const QmlTypesClassDescription &colle
if (!collector.immediateNames.isEmpty())
m_qml.writeStringListBinding(S_IMMEDIATE_NAMES, collector.immediateNames);
if (collector.elementName.isEmpty()) // e.g. if QML_ANONYMOUS
if (collector.elementNames.isEmpty()) // e.g. if QML_ANONYMOUS
return;
if (!collector.sequenceValueType.isEmpty()) {
qWarning() << "Ignoring name of sequential container:" << collector.elementName.toString();
qWarning() << "Ignoring names of sequential container:";
for (const QAnyStringView &name : std::as_const(collector.elementNames))
qWarning() << " - " << name.toString();
qWarning() << "Sequential containers are anonymous. Use QML_ANONYMOUS to register them.";
return;
}
@ -92,16 +94,19 @@ void QmlTypesCreator::writeClassProperties(const QmlTypesClassDescription &colle
if (revision.hasMajorVersion() && revision.majorVersion() > m_version.majorVersion())
break;
QByteArray exportEntry = m_module + '/';
collector.elementName.visit([&](auto view) {
processAsUtf8(view, [&](QByteArrayView view) { exportEntry.append(view); });
});
exportEntry += ' ' + QByteArray::number(revision.hasMajorVersion()
? revision.majorVersion()
: m_version.majorVersion());
exportEntry += '.' + QByteArray::number(revision.minorVersion());
for (const QAnyStringView &elementName : std::as_const(collector.elementNames)) {
QByteArray exportEntry = m_module + '/';
exports.append(exportEntry);
elementName.visit([&](auto view) {
processAsUtf8(view, [&](QByteArrayView view) { exportEntry.append(view); });
});
exportEntry += ' ' + QByteArray::number(revision.hasMajorVersion()
? revision.majorVersion()
: m_version.majorVersion());
exportEntry += '.' + QByteArray::number(revision.minorVersion());
exports.append(exportEntry);
}
metaObjects.append(QByteArray::number(revision.toEncodedVersion<quint16>()));
}

View File

@ -804,4 +804,38 @@ void tst_qmltyperegistrar::foreignNamespaceFromGadget()
}
}
void tst_qmltyperegistrar::nameExplosion_data()
{
QTest::addColumn<QByteArray>("qml");
QTest::addRow("Name1") << QByteArray("import QmlTypeRegistrarTest\nName1{}");
QTest::addRow("Name2") << QByteArray("import QmlTypeRegistrarTest\nName2{}");
QTest::addRow("NameExplosion") << QByteArray("import QmlTypeRegistrarTest\nNameExplosion{}");
}
void tst_qmltyperegistrar::nameExplosion()
{
QVERIFY(qmltypesData.contains(R"(Component {
file: "tst_qmltyperegistrar.h"
name: "NameExplosion"
accessSemantics: "reference"
prototype: "QObject"
exports: [
"QmlTypeRegistrarTest/Name1 1.0",
"QmlTypeRegistrarTest/Name2 1.0",
"QmlTypeRegistrarTest/NameExplosion 1.0"
]
exportMetaObjectRevisions: [256]
})"));
QFETCH(QByteArray, qml);
QQmlEngine engine;
QQmlComponent c(&engine);
c.setData(qml, QUrl());
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QObject> o(c.create());
QVERIFY(!o.isNull());
}
QTEST_MAIN(tst_qmltyperegistrar)

View File

@ -702,6 +702,15 @@ struct NotNamespaceForeign {
QML_ELEMENT
};
class NameExplosion : public QObject
{
Q_OBJECT
QML_NAMED_ELEMENT(Name1)
QML_NAMED_ELEMENT(Name2)
QML_ELEMENT
QML_ANONYMOUS
};
class tst_qmltyperegistrar : public QObject
{
Q_OBJECT
@ -763,6 +772,9 @@ private slots:
void valueTypeSelfReference();
void foreignNamespaceFromGadget();
void nameExplosion_data();
void nameExplosion();
private:
QByteArray qmltypesData;
};