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
10QT_BEGIN_NAMESPACE
11
12QMultiMap<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 */
20QmlTypeNode::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 */
31void 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 */
40void 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 */
51void 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 */
64QString 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 */
78QString 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 */
88QString 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 */
98QString 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 */
106bool 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*/
123void 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*/
162void 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
194QT_END_NAMESPACE
195

source code of qttools/src/qdoc/qdoc/src/qdoc/qmltypenode.cpp