1// Copyright (C) 2019 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 "qqmltypesclassdescription_p.h"
5
6#include "qqmltypescreator_p.h"
7#include "qmetatypesjsonprocessor_p.h"
8
9#include <QtCore/qjsonarray.h>
10
11QT_BEGIN_NAMESPACE
12
13static QString qualifiedClassName(const QJsonObject &classDef)
14{
15 return classDef.value(key: QLatin1String("qualifiedClassName")).toString();
16}
17
18static void collectExtraVersions(const QJsonObject *component, const QString &key,
19 QList<QTypeRevision> &extraVersions)
20{
21 const QJsonArray &items = component->value(key).toArray();
22 for (const QJsonValue item : items) {
23 const QJsonObject obj = item.toObject();
24 const auto revision = obj.find(key: QLatin1String("revision"));
25 if (revision != obj.end()) {
26 const auto extraVersion = QTypeRevision::fromEncodedVersion(value: revision.value().toInt());
27 if (!extraVersions.contains(t: extraVersion))
28 extraVersions.append(t: extraVersion);
29 }
30 }
31}
32
33const QJsonObject *QmlTypesClassDescription::findType(
34 const QVector<QJsonObject> &types, const QVector<QJsonObject> &foreign,
35 const QString &name, const QStringList &namespaces)
36{
37 const auto compare = [](const QJsonObject &type, const QString &typeName) {
38 return qualifiedClassName(classDef: type) < typeName;
39 };
40 const auto tryFindType = [&](const QString &qualifiedName) -> const QJsonObject * {
41 for (const QVector<QJsonObject> &t : {types, foreign}) {
42 const auto it = std::lower_bound(first: t.begin(), last: t.end(), val: qualifiedName, comp: compare);
43 if (it != t.end() && qualifiedClassName(classDef: *it) == qualifiedName)
44 return &(*it);
45 }
46 return nullptr;
47 };
48 if (name.startsWith(s: QLatin1String("::")))
49 return tryFindType(name.mid(position: 2));
50 QString qualified;
51 for (int i = 0, end = namespaces.length(); i != end; ++i) {
52 for (int j = 0; j < end - i; ++j) {
53 qualified.append(s: namespaces[j]);
54 qualified.append(s: QLatin1String("::"));
55 }
56 qualified.append(s: name);
57 if (const QJsonObject *found = tryFindType(qualified))
58 return found;
59 qualified.truncate(pos: 0);
60 }
61 return tryFindType(name);
62}
63
64void QmlTypesClassDescription::collectSuperClasses(
65 const QJsonObject *classDef, const QVector<QJsonObject> &types,
66 const QVector<QJsonObject> &foreign, CollectMode mode, QTypeRevision defaultRevision)
67{
68 const QStringList namespaces = MetaTypesJsonProcessor::namespaces(classDef: *classDef);
69 const auto supers = classDef->value(key: QLatin1String("superClasses")).toArray();
70 for (const QJsonValue superValue : supers) {
71 const QJsonObject superObject = superValue.toObject();
72 if (superObject[QLatin1String("access")].toString() == QLatin1String("public")) {
73 const QString superName = superObject[QLatin1String("name")].toString();
74
75 const CollectMode superMode = (mode == TopLevel) ? SuperClass : RelatedType;
76 if (const QJsonObject *other = findType(types, foreign, name: superName, namespaces)) {
77 collect(classDef: other, types, foreign, mode: superMode, defaultRevision);
78 if (mode == TopLevel && superClass.isEmpty())
79 superClass = qualifiedClassName(classDef: *other);
80 }
81
82 // If we cannot locate a type for it, there is no point in recording the superClass
83 }
84 }
85}
86
87void QmlTypesClassDescription::collectInterfaces(const QJsonObject *classDef)
88{
89 if (classDef->contains(key: QLatin1String("interfaces"))) {
90 const QJsonArray array = classDef->value(key: QLatin1String("interfaces")).toArray();
91 for (const QJsonValue value : array) {
92 auto object = value.toArray()[0].toObject();
93 implementsInterfaces << object[QLatin1String("className")].toString();
94 }
95 }
96}
97
98void QmlTypesClassDescription::collectLocalAnonymous(
99 const QJsonObject *classDef, const QVector<QJsonObject> &types,
100 const QVector<QJsonObject> &foreign, QTypeRevision defaultRevision)
101{
102 file = classDef->value(key: QLatin1String("inputFile")).toString();
103
104 resolvedClass = classDef;
105 className = qualifiedClassName(classDef: *classDef);
106
107 if (classDef->value(QStringLiteral("object")).toBool())
108 accessSemantics = QStringLiteral("reference");
109 else if (classDef->value(QStringLiteral("gadget")).toBool())
110 accessSemantics = QStringLiteral("value");
111 else
112 accessSemantics = QStringLiteral("none");
113
114 const auto classInfos = classDef->value(key: QLatin1String("classInfos")).toArray();
115 for (const QJsonValue classInfo : classInfos) {
116 const QJsonObject obj = classInfo.toObject();
117 if (obj[QStringLiteral("name")].toString() == QStringLiteral("DefaultProperty"))
118 defaultProp = obj[QStringLiteral("value")].toString();
119 if (obj[QStringLiteral("name")].toString() == QStringLiteral("ParentProperty"))
120 parentProp = obj[QStringLiteral("value")].toString();
121 }
122
123 collectInterfaces(classDef);
124 collectSuperClasses(classDef, types, foreign, mode: TopLevel, defaultRevision);
125}
126
127void QmlTypesClassDescription::collect(
128 const QJsonObject *classDef, const QVector<QJsonObject> &types,
129 const QVector<QJsonObject> &foreign, CollectMode mode, QTypeRevision defaultRevision)
130{
131 if (file.isEmpty())
132 file = classDef->value(key: QLatin1String("inputFile")).toString();
133
134 const auto classInfos = classDef->value(key: QLatin1String("classInfos")).toArray();
135 const QString classDefName = classDef->value(key: QLatin1String("className")).toString();
136 const QStringList namespaces = MetaTypesJsonProcessor::namespaces(classDef: *classDef);
137 QString foreignTypeName;
138 bool explicitCreatable = false;
139 for (const QJsonValue classInfo : classInfos) {
140 const QJsonObject obj = classInfo.toObject();
141 const QString name = obj[QLatin1String("name")].toString();
142 const QString value = obj[QLatin1String("value")].toString();
143
144 if (name == QLatin1String("DefaultProperty")) {
145 if (mode != RelatedType && defaultProp.isEmpty())
146 defaultProp = value;
147 } else if (name == QLatin1String("ParentProperty")) {
148 if (mode != RelatedType && parentProp.isEmpty())
149 parentProp = value;
150 } else if (name == QLatin1String("QML.AddedInVersion")) {
151 const QTypeRevision revision = QTypeRevision::fromEncodedVersion(value: value.toInt());
152 if (mode == TopLevel) {
153 addedInRevision = revision;
154 revisions.append(t: revision);
155 } else if (!elementName.isEmpty()) {
156 revisions.append(t: revision);
157 }
158 }
159
160 if (mode != TopLevel)
161 continue;
162
163 // These only apply to the original class
164 if (name == QLatin1String("QML.Element")) {
165 if (value == QLatin1String("auto"))
166 elementName = classDefName;
167 else if (value != QLatin1String("anonymous"))
168 elementName = value;
169 } else if (name == QLatin1String("QML.RemovedInVersion")) {
170 removedInRevision = QTypeRevision::fromEncodedVersion(value: value.toInt());
171 } else if (name == QLatin1String("QML.Creatable")) {
172 isCreatable = (value != QLatin1String("false"));
173 explicitCreatable = true;
174 } else if (name == QLatin1String("QML.Attached")) {
175 if (const QJsonObject *attached = collectRelated(
176 related: value, types, foreign, defaultRevision, namespaces)) {
177 attachedType = qualifiedClassName(classDef: *attached);
178 }
179 } else if (name == QLatin1String("QML.Extended")) {
180 if (const QJsonObject *extension = collectRelated(
181 related: value, types, foreign, defaultRevision, namespaces)) {
182 extensionType = qualifiedClassName(classDef: *extension);
183 }
184 } else if (name == QLatin1String("QML.ExtensionIsNamespace")) {
185 if (value == QLatin1String("true"))
186 extensionIsNamespace = true;
187 } else if (name == QLatin1String("QML.Sequence")) {
188 if (const QJsonObject *element = collectRelated(
189 related: value, types, foreign, defaultRevision, namespaces)) {
190 sequenceValueType = qualifiedClassName(classDef: *element);
191 } else {
192 // TODO: get rid of this once we have JSON data for the builtins.
193 sequenceValueType = value;
194 }
195 } else if (name == QLatin1String("QML.Singleton")) {
196 if (value == QLatin1String("true"))
197 isSingleton = true;
198 } else if (name == QLatin1String("QML.Foreign")) {
199 foreignTypeName = value;
200 } else if (name == QLatin1String("QML.OmitFromQmlTypes")) {
201 if (value == QLatin1String("true"))
202 omitFromQmlTypes = true;
203 } else if (name == QLatin1String("QML.HasCustomParser")) {
204 if (value == QLatin1String("true"))
205 hasCustomParser = true;
206 } else if (name == QLatin1String("DeferredPropertyNames")) {
207 deferredNames = value.split(sep: u',');
208 } else if (name == QLatin1String("ImmediatePropertyNames")) {
209 immediateNames = value.split(sep: u',');
210 }
211 }
212
213 // If the local type is a namespace the result can only be a namespace,
214 // no matter what the foreign type is.
215 const bool isNamespace = classDef->value(key: QLatin1String("namespace")).toBool();
216
217 if (!foreignTypeName.isEmpty()) {
218 // We can re-use a type with own QML.* macros as target of QML.Foreign
219 if (const QJsonObject *other = findType(types: foreign, foreign: types, name: foreignTypeName, namespaces)) {
220 classDef = other;
221
222 // Default properties are always local.
223 defaultProp.clear();
224
225 // Foreign type can have a default property or an attached types
226 const auto classInfos = classDef->value(key: QLatin1String("classInfos")).toArray();
227 for (const QJsonValue classInfo : classInfos) {
228 const QJsonObject obj = classInfo.toObject();
229 const QString foreignName = obj[QLatin1String("name")].toString();
230 const QString foreignValue = obj[QLatin1String("value")].toString();
231 if (defaultProp.isEmpty() && foreignName == QLatin1String("DefaultProperty")) {
232 defaultProp = foreignValue;
233 } else if (parentProp.isEmpty() && foreignName == QLatin1String("ParentProperty")) {
234 parentProp = foreignValue;
235 } else if (foreignName == QLatin1String("QML.Attached")) {
236 if (const QJsonObject *attached = collectRelated(
237 related: foreignValue, types, foreign, defaultRevision, namespaces)) {
238 attachedType = qualifiedClassName(classDef: *attached);
239 }
240 } else if (foreignName == QLatin1String("QML.Extended")) {
241 if (const QJsonObject *extension = collectRelated(
242 related: foreignValue, types, foreign, defaultRevision, namespaces)) {
243 extensionType = qualifiedClassName(classDef: *extension);
244 }
245 } else if (foreignName == QLatin1String("QML.ExtensionIsNamespace")) {
246 if (foreignValue == QLatin1String("true"))
247 extensionIsNamespace = true;
248 } else if (foreignName == QLatin1String("QML.Sequence")) {
249 if (const QJsonObject *element = collectRelated(
250 related: foreignValue, types, foreign, defaultRevision, namespaces)) {
251 sequenceValueType = qualifiedClassName(classDef: *element);
252 }
253 }
254 }
255 } else {
256 className = foreignTypeName;
257 classDef = nullptr;
258 }
259 }
260
261 if (classDef) {
262 if (mode == RelatedType || !elementName.isEmpty()) {
263 collectExtraVersions(component: classDef, key: QString::fromLatin1(ba: "properties"), extraVersions&: revisions);
264 collectExtraVersions(component: classDef, key: QString::fromLatin1(ba: "slots"), extraVersions&: revisions);
265 collectExtraVersions(component: classDef, key: QString::fromLatin1(ba: "methods"), extraVersions&: revisions);
266 collectExtraVersions(component: classDef, key: QString::fromLatin1(ba: "signals"), extraVersions&: revisions);
267 }
268
269 collectSuperClasses(classDef, types, foreign, mode, defaultRevision);
270 }
271
272 if (mode != TopLevel)
273 return;
274
275 if (classDef)
276 collectInterfaces(classDef);
277
278 if (!addedInRevision.isValid()) {
279 revisions.append(t: defaultRevision);
280 addedInRevision = defaultRevision;
281 } else if (addedInRevision < defaultRevision) {
282 revisions.append(t: defaultRevision);
283 }
284
285 std::sort(first: revisions.begin(), last: revisions.end());
286 const auto end = std::unique(first: revisions.begin(), last: revisions.end());
287 revisions.erase(abegin: QList<QTypeRevision>::const_iterator(end), aend: revisions.constEnd());
288
289 resolvedClass = classDef;
290 if (className.isEmpty() && classDef)
291 className = qualifiedClassName(classDef: *classDef);
292
293 if (!sequenceValueType.isEmpty()) {
294 isCreatable = false;
295 accessSemantics = QLatin1String("sequence");
296 } else if (isNamespace) {
297 isCreatable = false;
298 accessSemantics = QLatin1String("none");
299 } else if (classDef && classDef->value(key: QLatin1String("object")).toBool()) {
300 accessSemantics = QLatin1String("reference");
301 } else {
302 if (!explicitCreatable)
303 isCreatable = false;
304
305 if (!classDef) {
306 if (elementName.isEmpty() || elementName[0].isLower()) {
307 // If no classDef, we generally assume it's a value type defined by the
308 // foreign/extended trick.
309 accessSemantics = QLatin1String("value");
310 } else {
311 // Objects and namespaces always have metaobjects and therefore classDefs.
312 // However, we may not be able to resolve the metaobject at compile time. See
313 // the "Invisible" test case. In that case, we must not assume anything about
314 // access semantics.
315
316 qWarning() << "Warning: Refusing to generate non-lowercase name"
317 << elementName << "for unknown foreign type";
318 elementName.clear();
319
320 // Make it completely inaccessible.
321 // We cannot get enums from anonymous types after all.
322 accessSemantics = QLatin1String("none");
323 }
324 } else if (classDef->value(key: QLatin1String("gadget")).toBool()) {
325 accessSemantics = QLatin1String("value");
326 } else {
327 accessSemantics = QLatin1String("none");
328 }
329 }
330}
331
332const QJsonObject *QmlTypesClassDescription::collectRelated(
333 const QString &related, const QVector<QJsonObject> &types,
334 const QVector<QJsonObject> &foreign, QTypeRevision defaultRevision,
335 const QStringList &namespaces)
336{
337 if (const QJsonObject *other = findType(types, foreign, name: related, namespaces)) {
338 collect(classDef: other, types, foreign, mode: RelatedType, defaultRevision);
339 return other;
340 }
341 return nullptr;
342}
343
344
345QT_END_NAMESPACE
346

source code of qtdeclarative/src/qmltyperegistrar/qqmltypesclassdescription.cpp