| 1 | // Copyright (C) 2021 The Qt Company Ltd. |
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
| 3 | |
| 4 | #include "qmltypenode.h" |
| 5 | #include "collectionnode.h" |
| 6 | #include "qdocdatabase.h" |
| 7 | |
| 8 | #include <QtCore/qdebug.h> |
| 9 | |
| 10 | QT_BEGIN_NAMESPACE |
| 11 | |
| 12 | QMultiMap<const Node *, Node *> QmlTypeNode::s_inheritedBy; |
| 13 | |
| 14 | /*! |
| 15 | Constructs a Qml type. |
| 16 | |
| 17 | The new node has the given \a parent, name \a name, and a specific node |
| 18 | \a type. Valid types are NodeType::QmlType and NodeType::QmlValueType. |
| 19 | */ |
| 20 | QmlTypeNode::QmlTypeNode(Aggregate *parent, const QString &name, NodeType type) |
| 21 | : Aggregate(type, parent, name) |
| 22 | { |
| 23 | Q_ASSERT(type == NodeType::QmlType || type == NodeType::QmlValueType); |
| 24 | setTitle(name); |
| 25 | } |
| 26 | |
| 27 | /*! |
| 28 | Clear the static maps so that subsequent runs don't try to use |
| 29 | contents from a previous run. |
| 30 | */ |
| 31 | void QmlTypeNode::terminate() |
| 32 | { |
| 33 | s_inheritedBy.clear(); |
| 34 | } |
| 35 | |
| 36 | /*! |
| 37 | Record the fact that QML class \a base is inherited by |
| 38 | QML class \a sub. |
| 39 | */ |
| 40 | void QmlTypeNode::addInheritedBy(const Node *base, Node *sub) |
| 41 | { |
| 42 | if (sub->isInternal()) |
| 43 | return; |
| 44 | if (!s_inheritedBy.contains(key: base, value: sub)) |
| 45 | s_inheritedBy.insert(key: base, value: sub); |
| 46 | } |
| 47 | |
| 48 | /*! |
| 49 | Loads the list \a subs with the nodes of all the subclasses of \a base. |
| 50 | */ |
| 51 | void QmlTypeNode::subclasses(const Node *base, NodeList &subs) |
| 52 | { |
| 53 | subs.clear(); |
| 54 | if (s_inheritedBy.count(key: base) > 0) { |
| 55 | subs = s_inheritedBy.values(key: base); |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | /*! |
| 60 | If this QML type node has a base type node, |
| 61 | return the fully qualified name of that QML |
| 62 | type, i.e. <QML-module-name>::<QML-type-name>. |
| 63 | */ |
| 64 | QString QmlTypeNode::qmlFullBaseName() const |
| 65 | { |
| 66 | QString result; |
| 67 | if (m_qmlBaseNode) { |
| 68 | result = m_qmlBaseNode->logicalModuleName() + "::" + m_qmlBaseNode->name(); |
| 69 | } |
| 70 | return result; |
| 71 | } |
| 72 | |
| 73 | /*! |
| 74 | If the QML type's QML module pointer is set, return the QML |
| 75 | module name from the QML module node. Otherwise, return the |
| 76 | empty string. |
| 77 | */ |
| 78 | QString QmlTypeNode::logicalModuleName() const |
| 79 | { |
| 80 | return (m_logicalModule ? m_logicalModule->logicalModuleName() : QString()); |
| 81 | } |
| 82 | |
| 83 | /*! |
| 84 | If the QML type's QML module pointer is set, return the QML |
| 85 | module version from the QML module node. Otherwise, return |
| 86 | the empty string. |
| 87 | */ |
| 88 | QString QmlTypeNode::logicalModuleVersion() const |
| 89 | { |
| 90 | return (m_logicalModule ? m_logicalModule->logicalModuleVersion() : QString()); |
| 91 | } |
| 92 | |
| 93 | /*! |
| 94 | If the QML type's QML module pointer is set, return the QML |
| 95 | module identifier from the QML module node. Otherwise, return |
| 96 | the empty string. |
| 97 | */ |
| 98 | QString QmlTypeNode::logicalModuleIdentifier() const |
| 99 | { |
| 100 | return (m_logicalModule ? m_logicalModule->logicalModuleIdentifier() : QString()); |
| 101 | } |
| 102 | |
| 103 | /*! |
| 104 | Returns true if this QML type inherits \a type. |
| 105 | */ |
| 106 | bool QmlTypeNode::inherits(Aggregate *type) |
| 107 | { |
| 108 | QmlTypeNode *qtn = qmlBaseNode(); |
| 109 | while (qtn != nullptr) { |
| 110 | if (qtn == type) |
| 111 | return true; |
| 112 | qtn = qtn->qmlBaseNode(); |
| 113 | } |
| 114 | return false; |
| 115 | } |
| 116 | |
| 117 | /*! |
| 118 | Recursively resolves the base node for this QML type when only the name of |
| 119 | the base type is known. |
| 120 | |
| 121 | \a previousSearches is used for speeding up the process. |
| 122 | */ |
| 123 | void QmlTypeNode::resolveInheritance(NodeMap &previousSearches) |
| 124 | { |
| 125 | if (m_qmlBaseNode || m_qmlBaseName.isEmpty()) |
| 126 | return; |
| 127 | |
| 128 | auto *base = static_cast<QmlTypeNode *>(previousSearches.value(key: m_qmlBaseName)); |
| 129 | if (!previousSearches.contains(key: m_qmlBaseName)) { |
| 130 | for (const auto &imp : std::as_const(t&: m_importList)) { |
| 131 | base = QDocDatabase::qdocDB()->findQmlType(import: imp, name: m_qmlBaseName); |
| 132 | if (base) |
| 133 | break; |
| 134 | } |
| 135 | if (!base) { |
| 136 | if (m_qmlBaseName.contains(c: ':')) |
| 137 | base = QDocDatabase::qdocDB()->findQmlType(name: m_qmlBaseName); |
| 138 | else |
| 139 | base = QDocDatabase::qdocDB()->findQmlType(qmid: QString(), name: m_qmlBaseName); |
| 140 | } |
| 141 | previousSearches.insert(key: m_qmlBaseName, value: base); |
| 142 | } |
| 143 | |
| 144 | if (base) { |
| 145 | if (base != this) { |
| 146 | m_qmlBaseNode = base; |
| 147 | QmlTypeNode::addInheritedBy(base, sub: this); |
| 148 | // Base types read from the index need resolving as they only have the name set |
| 149 | if (base->isIndexNode()) |
| 150 | base->resolveInheritance(previousSearches); |
| 151 | } else |
| 152 | location().report(QStringLiteral("Type is its own base type: '%1'" ).arg(a: name())); |
| 153 | } |
| 154 | |
| 155 | if (!base) |
| 156 | location().report(QStringLiteral("Unknown base '%1' for QML type '%2'" ).arg(args: qmlBaseName(), args: name())); |
| 157 | } |
| 158 | |
| 159 | /*! |
| 160 | Checks and warns about problems with the inheritance of this QML type. |
| 161 | */ |
| 162 | void QmlTypeNode::checkInheritance() |
| 163 | { |
| 164 | /* Use Floyd's cycle-finding algorithm (tortoise and hare) to detect base |
| 165 | types that inherit from their descendants. */ |
| 166 | const QmlTypeNode *qtn = this; |
| 167 | const QmlTypeNode *hare = qtn; |
| 168 | |
| 169 | // Record the previous type found by the hare for reporting. |
| 170 | QmlTypeNode *previous; |
| 171 | |
| 172 | while (qtn && hare) { |
| 173 | // Examine the base node. |
| 174 | qtn = qtn->qmlBaseNode(); |
| 175 | |
| 176 | /* The hare node moves two nodes up the inheritance tree to increase |
| 177 | the cycle detection distance, recording the previous type in case |
| 178 | it needs to be reported. */ |
| 179 | for (int i = 0; i < 2; i++) |
| 180 | if (hare) { |
| 181 | previous = const_cast<QmlTypeNode *>(hare); |
| 182 | hare = hare->qmlBaseNode(); |
| 183 | } |
| 184 | |
| 185 | // Only report a cycle if both nodes are non-null and identical. |
| 186 | if (qtn && hare && qtn == hare) { |
| 187 | location().report(QStringLiteral("Circular type inheritance: '%1'" ).arg(a: previous->name())); |
| 188 | previous->m_qmlBaseNode = nullptr; |
| 189 | break; |
| 190 | } |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | QT_END_NAMESPACE |
| 195 | |