// Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qqmltyperegistrarutils_p.h" #include "qqmltypesclassdescription_p.h" #include "qanystringviewutils_p.h" #include "qmetatypesjsonprocessor_p.h" #include "qqmltyperegistrarconstants_p.h" #include #include QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; using namespace Constants; using namespace Constants::MetatypesDotJson; using namespace Constants::MetatypesDotJson::Qml; using namespace QAnyStringViewUtils; template static void collectExtraVersions(const Container &items, QList &extraVersions) { for (const auto &obj : items) { if (obj.revision.isValid() && !extraVersions.contains(obj.revision)) extraVersions.append(obj.revision); } } struct Compare { bool operator()(const QAnyStringView &typeName, const MetaType &type) const { return typeName < type.qualifiedClassName(); } bool operator()(const MetaType &type, const QAnyStringView &typeName) const { return type.qualifiedClassName() < typeName; } }; FoundType::FoundType(const MetaType &single, FoundType::Origin origin) { if (single.inputFile().isEmpty()) { javaScript = single; javaScriptOrigin = origin; } else { native = single; nativeOrigin = origin; } } MetaType FoundType::select(const MetaType &category, QAnyStringView relation) const { if (category.inputFile().isEmpty()) { if (javaScript.isEmpty()) { warning(category) << relation << "type of" << category.qualifiedClassName() << "is not a JavaScript type"; } return javaScript; } if (native.isEmpty()) { warning(category) << relation << "of" << category.qualifiedClassName() << "is not a native type"; } return native; } FoundType QmlTypesClassDescription::findType( const QVector &types, const QVector &foreign, const QAnyStringView &name, const QList &namespaces) { const auto tryFindType = [&](QAnyStringView qualifiedName) -> FoundType { FoundType result; for (const QVector &t : {types, foreign}) { const auto [first, last] = std::equal_range( t.begin(), t.end(), qualifiedName, Compare()); for (auto it = first; it != last; ++it) { Q_ASSERT(it->qualifiedClassName() == qualifiedName); if (it->inputFile().isEmpty()) { if (result.javaScript.isEmpty()) { result.javaScript = *it; result.javaScriptOrigin = (&t == &types) ? FoundType::OwnTypes : FoundType::ForeignTypes; } else { warning(result.javaScript) << "Multiple JavaScript types called" << qualifiedName << "found!"; } } else if (result.native.isEmpty()) { result.native = *it; result.nativeOrigin = (&t == &types) ? FoundType::OwnTypes : FoundType::ForeignTypes; } else { warning(result.native) << "Multiple C++ types called" << qualifiedName << "found!\n" << "(other occurrence in :" << it->inputFile() << ":" << it->lineNumber() << ")\n" << "This violates the One Definition Rule!"; } } } return result; }; if (startsWith(name, QLatin1String("::"))) return tryFindType(name.mid(2)); QString qualified; for (int i = 0, end = namespaces.length(); i != end; ++i) { for (int j = 0; j < end - i; ++j) { namespaces[j].visit([&](auto data) { qualified.append(data); }); qualified.append(QLatin1String("::")); } name.visit([&](auto data) { qualified.append(data); }); if (const FoundType found = tryFindType(qualified)) return found; qualified.truncate(0); } return tryFindType(name); } void QmlTypesClassDescription::collectSuperClasses( const MetaType &classDef, const QVector &types, const QVector &foreign, CollectMode mode, QTypeRevision defaultRevision) { const QList namespaces = MetaTypesJsonProcessor::namespaces(classDef); QAnyStringView superClassCandidate; for (const BaseType &superObject : std::as_const(classDef.superClasses())) { if (superObject.access == Access::Public) { const QAnyStringView superName = superObject.name; const CollectMode superMode = (mode == TopLevel) ? SuperClass : RelatedType; if (const FoundType found = findType(types, foreign, superName, namespaces)) { const MetaType other = found.select(classDef, "Base"); collect(other, types, foreign, superMode, defaultRevision); if (mode == TopLevel && superClass.isEmpty()) superClass = other.qualifiedClassName(); } else { // If we cannot resolve anything but find a correctly formed superObject, // we can at least populate the name. Further tooling might locate the type // in a different module. superClassCandidate = superName; } } } if (mode == TopLevel && superClass.isEmpty()) superClass = superClassCandidate; } void QmlTypesClassDescription::collectInterfaces(const MetaType &classDef) { for (const Interface &iface : classDef.ifaces()) implementsInterfaces << interfaceName(iface); } void QmlTypesClassDescription::handleRegisterEnumClassesUnscoped( const MetaType &classDef, QAnyStringView value) { if (value == S_FALSE) enforcesScopedEnums = true; else if (value == S_TRUE) warning(classDef) << "Setting RegisterEnumClassesUnscoped to true has no effect."; else warning(classDef) << "Unrecognized value for RegisterEnumClassesUnscoped:" << value; } void QmlTypesClassDescription::collectLocalAnonymous( const MetaType &classDef, const QVector &types, const QVector &foreign, QTypeRevision defaultRevision) { file = classDef.inputFile(); lineNumber = classDef.lineNumber(); resolvedClass = classDef; className = classDef.qualifiedClassName(); switch (classDef.kind()) { case MetaType::Kind::Object: accessSemantics = DotQmltypes::S_REFERENCE; break; case MetaType::Kind::Gadget: accessSemantics = DotQmltypes::S_VALUE; break; case MetaType::Kind::Namespace: case MetaType::Kind::Unknown: accessSemantics = DotQmltypes::S_NONE; break; } for (const ClassInfo &obj : classDef.classInfos()) { if (obj.name == S_DEFAULT_PROPERTY) defaultProp = obj.value; else if (obj.name == S_PARENT_PROPERTY) parentProp = obj.value; else if (obj.name == S_REGISTER_ENUM_CLASSES_UNSCOPED) handleRegisterEnumClassesUnscoped(classDef, obj.value); } collectInterfaces(classDef); collectSuperClasses(classDef, types, foreign, TopLevel, defaultRevision); } void QmlTypesClassDescription::collect( const MetaType &classDef, const QVector &types, const QVector &foreign, CollectMode mode, QTypeRevision defaultRevision) { if (file.isEmpty()) { file = classDef.inputFile(); lineNumber = classDef.lineNumber(); } const QAnyStringView classDefName = classDef.className(); const QList namespaces = MetaTypesJsonProcessor::namespaces(classDef); QAnyStringView foreignTypeName; bool foreignIsNamespace = false; bool isConstructible = false; for (const ClassInfo &obj : classDef.classInfos()) { const QAnyStringView name = obj.name; const QAnyStringView value = obj.value; if (name == S_DEFAULT_PROPERTY) { if (mode != RelatedType && defaultProp.isEmpty()) defaultProp = value; continue; } if (name == S_PARENT_PROPERTY) { if (mode != RelatedType && parentProp.isEmpty()) parentProp = value; continue; } if (name == S_REGISTER_ENUM_CLASSES_UNSCOPED) { if (mode != RelatedType) handleRegisterEnumClassesUnscoped(classDef, value); continue; } if (name == S_ADDED_IN_VERSION) { const QTypeRevision revision = handleInMinorVersion( QTypeRevision::fromEncodedVersion(toInt(value)), defaultRevision.majorVersion()); revisions.append(revision); if (mode == TopLevel) addedInRevision = revision; continue; } if (mode != TopLevel) continue; if (name == S_REMOVED_IN_VERSION) { removedInRevision = handleInMinorVersion( QTypeRevision::fromEncodedVersion(toInt(value)), defaultRevision.majorVersion()); continue; } // These only apply to the original class if (name == S_ELEMENT) { if (value == S_AUTO) elementNames.append(classDefName); else if (value != S_ANONYMOUS) elementNames.append(value); } else if (name == S_CREATABLE) { isCreatable = (value != S_FALSE); } else if (name == S_CREATION_METHOD) { isStructured = (value == S_STRUCTURED); isConstructible = isStructured || (value == S_CONSTRUCT); } else if (name == S_ATTACHED) { if (const FoundType attached = collectRelated( value, types, foreign, defaultRevision, namespaces)) { attachedType = attached.select(classDef, "Attached").qualifiedClassName(); } } else if (name == S_EXTENDED) { if (const FoundType extension = collectRelated( value, types, foreign, defaultRevision, namespaces)) { javaScriptExtensionType = extension.javaScript.qualifiedClassName(); nativeExtensionType = extension.native.qualifiedClassName(); } } else if (name == S_EXTENSION_IS_JAVA_SCRIPT) { if (value == S_TRUE) extensionIsJavaScript = true; } else if (name == S_EXTENSION_IS_NAMESPACE) { if (value == S_TRUE) extensionIsNamespace = true; } else if (name == S_SEQUENCE) { if (const FoundType element = collectRelated( value, types, foreign, defaultRevision, namespaces)) { sequenceValueType = element.select(classDef, "Sequence value").qualifiedClassName(); } else { // TODO: get rid of this once we have JSON data for the builtins. sequenceValueType = value; } } else if (name == S_SINGLETON) { if (value == S_TRUE) isSingleton = true; } else if (name == S_FOREIGN) { foreignTypeName = value; } else if (name == S_FOREIGN_IS_NAMESPACE) { foreignIsNamespace = (value == S_TRUE); } else if (name == S_PRIMITIVE_ALIAS) { primitiveAliases.append(value); } else if (name == S_ROOT) { isRootClass = (value == S_TRUE); } else if (name == S_HAS_CUSTOM_PARSER) { if (value == S_TRUE) hasCustomParser = true; } else if (name == S_DEFERRED_PROPERTY_NAMES) { deferredNames = split(value, QLatin1StringView(",")); } else if (name == S_IMMEDIATE_PROPERTY_NAMES) { immediateNames = split(value, QLatin1StringView(",")); } } if (addedInRevision.isValid() && !elementNames.isEmpty()) revisions.append(addedInRevision); // If the local type is a namespace the result can only be a namespace, // no matter what the foreign type is. const bool isNamespace = foreignIsNamespace || classDef.kind() == MetaType::Kind::Namespace; MetaType resolved = classDef; if (!foreignTypeName.isEmpty()) { // We can re-use a type with own QML.* macros as target of QML.Foreign if (const FoundType found = findType(foreign, types, foreignTypeName, namespaces)) { resolved = found.select(classDef, "Foreign"); // Default properties and enum classes are always local. defaultProp = {}; enforcesScopedEnums = false; // Foreign type can have a default property or an attached type, // or RegisterEnumClassesUnscoped classinfo. for (const ClassInfo &obj : resolved.classInfos()) { const QAnyStringView foreignName = obj.name; const QAnyStringView foreignValue = obj.value; if (defaultProp.isEmpty() && foreignName == S_DEFAULT_PROPERTY) { defaultProp = foreignValue; } else if (parentProp.isEmpty() && foreignName == S_PARENT_PROPERTY) { parentProp = foreignValue; } else if (foreignName == S_REGISTER_ENUM_CLASSES_UNSCOPED) { handleRegisterEnumClassesUnscoped(resolved, foreignValue); } else if (foreignName == S_ATTACHED) { if (const FoundType attached = collectRelated( foreignValue, types, foreign, defaultRevision, namespaces)) { attachedType = attached.select(resolved, "Attached").qualifiedClassName(); } } else if (foreignName == S_EXTENDED) { if (const FoundType extension = collectRelated( foreignValue, types, foreign, defaultRevision, namespaces)) { nativeExtensionType = extension.native.qualifiedClassName(); javaScriptExtensionType = extension.javaScript.qualifiedClassName(); } } else if (foreignName == S_EXTENSION_IS_JAVA_SCRIPT) { if (foreignValue == S_TRUE) extensionIsJavaScript = true; } else if (foreignName == S_EXTENSION_IS_NAMESPACE) { if (foreignValue == S_TRUE) extensionIsNamespace = true; } else if (foreignName == S_SEQUENCE) { if (const FoundType element = collectRelated( foreignValue, types, foreign, defaultRevision, namespaces)) { sequenceValueType = element.select(resolved, "Sequence value").qualifiedClassName(); } } } } else { className = foreignTypeName; resolved = MetaType(); } } if (!resolved.isEmpty()) { if (mode == RelatedType || !elementNames.isEmpty()) { collectExtraVersions(resolved.properties(), revisions); collectExtraVersions(resolved.methods(), revisions); collectExtraVersions(resolved.sigs(), revisions); } collectSuperClasses(resolved, types, foreign, mode, defaultRevision); } if (mode != TopLevel) return; if (!resolved.isEmpty()) collectInterfaces(resolved); if (!addedInRevision.isValid()) { addedInRevision = defaultRevision; } if (addedInRevision <= defaultRevision && (!removedInRevision.isValid() || defaultRevision < removedInRevision)) { revisions.append(defaultRevision); } std::sort(revisions.begin(), revisions.end()); const auto end = std::unique(revisions.begin(), revisions.end()); revisions.erase(QList::const_iterator(end), revisions.constEnd()); resolvedClass = resolved; if (className.isEmpty() && !resolved.isEmpty()) className = resolved.qualifiedClassName(); if (!sequenceValueType.isEmpty()) { isCreatable = false; accessSemantics = DotQmltypes::S_SEQUENCE; } else if (isNamespace) { isCreatable = false; accessSemantics = DotQmltypes::S_NONE; } else if (resolved.kind() == MetaType::Kind::Object) { accessSemantics = DotQmltypes::S_REFERENCE; } else { isCreatable = isConstructible; if (resolved.isEmpty()) { if (elementNames.isEmpty()) { // If no resolved, we generally assume it's a value type defined by the // foreign/extended trick. accessSemantics = DotQmltypes::S_VALUE; } for (auto elementName = elementNames.begin(); elementName != elementNames.end();) { if (elementName->isEmpty() || elementName->front().isLower()) { // If no resolved, 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. warning(classDef) << "Refusing to generate non-lowercase name" << *elementName << "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 (resolved.kind() == MetaType::Kind::Gadget) { accessSemantics = DotQmltypes::S_VALUE; } else { accessSemantics = DotQmltypes::S_NONE; } } } FoundType QmlTypesClassDescription::collectRelated( QAnyStringView related, const QVector &types, const QVector &foreign, QTypeRevision defaultRevision, const QList &namespaces) { if (FoundType other = findType(types, foreign, related, namespaces)) { if (!other.native.isEmpty()) collect(other.native, types, foreign, RelatedType, defaultRevision); if (!other.javaScript.isEmpty()) collect(other.javaScript, types, foreign, RelatedType, defaultRevision); return other; } return FoundType(); } struct UsingCompare { bool operator()(const UsingDeclaration &a, QAnyStringView b) const { return a.alias < b; } bool operator()(QAnyStringView a, const UsingDeclaration &b) const { return a < b.alias; } }; ResolvedTypeAlias::ResolvedTypeAlias( QAnyStringView alias, const QList &usingDeclarations) : type(alias) { handleVoid(); if (type.isEmpty()) return; handleList(); if (!isList) { handlePointer(); handleConst(); } while (true) { const auto usingDeclaration = std::equal_range( usingDeclarations.begin(), usingDeclarations.end(), type, UsingCompare()); if (usingDeclaration.first == usingDeclaration.second) break; type = usingDeclaration.first->original; handleVoid(); if (type.isEmpty()) return; if (isPointer) { handleConst(); continue; } if (!isList) { handleList(); if (!isList) { handlePointer(); handleConst(); } } } } void ResolvedTypeAlias::handleVoid() { if (!isPointer && type == "void") type = ""; } void ResolvedTypeAlias::handleList() { for (QLatin1StringView list : {"QQmlListProperty<"_L1, "QList<"_L1}) { if (!startsWith(type, list) || type.back() != '>'_L1) continue; const int listSize = list.size(); const QAnyStringView elementType = trimmed(type.mid(listSize, type.size() - listSize - 1)); // QQmlListProperty internally constructs the pointer. Passing an explicit '*' will // produce double pointers. QList is only for value types. We can't handle QLists // of pointers (unless specially registered, but then they're not isList). if (elementType.back() == '*'_L1) continue; isList = true; type = elementType; return; } } void ResolvedTypeAlias::handlePointer() { if (type.back() == '*'_L1) { isPointer = true; type = type.chopped(1); } } void ResolvedTypeAlias::handleConst() { if (startsWith(type, "const "_L1)) { isConstant = true; type = type.sliced(strlen("const ")); } } QT_END_NAMESPACE