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:
parent
63783a21ab
commit
f5aecbde91
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>()));
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue