1 | // Copyright (C) 2020 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 "qmetatypesjsonprocessor_p.h" |
5 | |
6 | #include "qanystringviewutils_p.h" |
7 | #include "qqmltyperegistrarconstants_p.h" |
8 | #include "qqmltyperegistrarutils_p.h" |
9 | #include "qqmltypesclassdescription_p.h" |
10 | #include "qqmltyperegistrarutils_p.h" |
11 | |
12 | #include <QtCore/qcborarray.h> |
13 | #include <QtCore/qcbormap.h> |
14 | #include <QtCore/qfile.h> |
15 | #include <QtCore/qjsondocument.h> |
16 | #include <QtCore/qqueue.h> |
17 | |
18 | QT_BEGIN_NAMESPACE |
19 | |
20 | using namespace Qt::StringLiterals; |
21 | using namespace Constants; |
22 | using namespace Constants::MetatypesDotJson; |
23 | using namespace Constants::MetatypesDotJson::Qml; |
24 | using namespace QAnyStringViewUtils; |
25 | |
26 | const MetaTypePrivate MetaType::s_empty; |
27 | |
28 | // TODO: This could be optimized to store the objects in a more compact way. |
29 | std::vector<std::unique_ptr<MetaTypePrivate>> s_pool; |
30 | |
31 | static QCborValue fromJson(const QByteArray &json, QJsonParseError *error) |
32 | { |
33 | const QJsonDocument jsonValue = QJsonDocument::fromJson(json, error); |
34 | if (jsonValue.isArray()) |
35 | return QCborValue::fromJsonValue(v: jsonValue.array()); |
36 | if (jsonValue.isObject()) |
37 | return QCborValue::fromJsonValue(v: jsonValue.object()); |
38 | return QCborValue(); |
39 | } |
40 | |
41 | QList<QAnyStringView> MetaTypesJsonProcessor::namespaces(const MetaType &classDef) |
42 | { |
43 | const QAnyStringView unqualified = classDef.className(); |
44 | const QAnyStringView qualified = classDef.qualifiedClassName(); |
45 | |
46 | QList<QAnyStringView> namespaces; |
47 | if (qualified != unqualified) { |
48 | namespaces = split(source: qualified, sep: "::"_L1); |
49 | Q_ASSERT(namespaces.last() == unqualified); |
50 | namespaces.pop_back(); |
51 | } |
52 | |
53 | return namespaces; |
54 | } |
55 | |
56 | bool MetaTypesJsonProcessor::processTypes(const QStringList &files) |
57 | { |
58 | for (const QString &source: files) { |
59 | QCborValue metaObjects; |
60 | { |
61 | QFile f(source); |
62 | if (!f.open(flags: QIODevice::ReadOnly)) { |
63 | error(fileName: source) << "Cannot open file for reading"; |
64 | return false; |
65 | } |
66 | QJsonParseError parseError = {.offset: 0, .error: QJsonParseError::NoError}; |
67 | metaObjects = fromJson(json: f.readAll(), error: &parseError); |
68 | if (parseError.error != QJsonParseError::NoError) { |
69 | error(fileName: source) |
70 | << "Failed to parse JSON:"<< parseError.error |
71 | << parseError.errorString(); |
72 | return false; |
73 | } |
74 | } |
75 | |
76 | if (metaObjects.isArray()) { |
77 | const QCborArray metaObjectsArray = metaObjects.toArray(); |
78 | for (const QCborValue &metaObject : metaObjectsArray) { |
79 | if (!metaObject.isMap()) { |
80 | error(fileName: source) << "JSON is not an object"; |
81 | return false; |
82 | } |
83 | |
84 | processTypes(types: metaObject.toMap()); |
85 | } |
86 | } else if (metaObjects.isMap()) { |
87 | processTypes(types: metaObjects.toMap()); |
88 | } else { |
89 | error(fileName: source) << "JSON is not an object or an array"; |
90 | return false; |
91 | } |
92 | } |
93 | |
94 | return true; |
95 | } |
96 | |
97 | bool MetaTypesJsonProcessor::processForeignTypes(const QString &types) |
98 | { |
99 | QFile typesFile(types); |
100 | if (!typesFile.open(flags: QIODevice::ReadOnly)) { |
101 | error(fileName: types) << "Cannot open foreign types file"; |
102 | return false; |
103 | } |
104 | |
105 | QJsonParseError parseError = {.offset: 0, .error: QJsonParseError::NoError}; |
106 | QCborValue foreignMetaObjects = fromJson(json: typesFile.readAll(), error: &parseError); |
107 | if (parseError.error != QJsonParseError::NoError) { |
108 | error(fileName: types) |
109 | << "Failed to parse JSON:"<< parseError.error |
110 | << parseError.errorString(); |
111 | return false; |
112 | } |
113 | |
114 | const QCborArray foreignObjectsArray = foreignMetaObjects.toArray(); |
115 | for (const QCborValue &metaObject : foreignObjectsArray) { |
116 | if (!metaObject.isMap()) { |
117 | error(fileName: types) << "JSON is not an object"; |
118 | return false; |
119 | } |
120 | |
121 | processForeignTypes(types: metaObject.toMap()); |
122 | } |
123 | |
124 | return true; |
125 | } |
126 | |
127 | bool MetaTypesJsonProcessor::processForeignTypes(const QStringList &foreignTypesFiles) |
128 | { |
129 | bool success = true; |
130 | |
131 | for (const QString &types : foreignTypesFiles) { |
132 | if (!processForeignTypes(types)) |
133 | success = false; |
134 | } |
135 | return success; |
136 | } |
137 | |
138 | template<typename String> |
139 | static void sortStringList(QList<String> *list) |
140 | { |
141 | std::sort(list->begin(), list->end()); |
142 | const auto newEnd = std::unique(list->begin(), list->end()); |
143 | list->erase(typename QList<String>::const_iterator(newEnd), list->constEnd()); |
144 | } |
145 | |
146 | void MetaTypesJsonProcessor::postProcessTypes() |
147 | { |
148 | sortTypes(types&: m_types); |
149 | } |
150 | |
151 | void MetaTypesJsonProcessor::postProcessForeignTypes() |
152 | { |
153 | sortTypes(types&: m_foreignTypes); |
154 | sortStringList(list: &m_primitiveTypes); |
155 | sortStringList(list: &m_usingDeclarations); |
156 | addRelatedTypes(); |
157 | sortStringList(list: &m_referencedTypes); |
158 | sortStringList(list: &m_includes); |
159 | } |
160 | |
161 | QString MetaTypesJsonProcessor::extractRegisteredTypes() const |
162 | { |
163 | QString registrationHelper; |
164 | for (const auto &obj: m_types) { |
165 | const QString className = obj.className().toString(); |
166 | const QString qualifiedClassName = obj.qualifiedClassName().toString(); |
167 | const QString foreignClassName = className + u"Foreign"; |
168 | QStringList qmlElements; |
169 | QString qmlUncreatable; |
170 | QString qmlAttached; |
171 | bool isSingleton = false; |
172 | bool isExplicitlyUncreatable = false; |
173 | bool isNamespace = obj.kind() == MetaType::Kind::Namespace; |
174 | for (const ClassInfo &entry: obj.classInfos()) { |
175 | const auto name = entry.name; |
176 | const auto value = entry.value; |
177 | if (name == S_ELEMENT) { |
178 | if (value == S_AUTO) { |
179 | qmlElements.append(t: u"QML_NAMED_ELEMENT("_s+ className + u ")"_s); |
180 | } else if (value == S_ANONYMOUS) { |
181 | qmlElements.append(t: u"QML_ANONYMOUS"_s); |
182 | } else { |
183 | qmlElements.append(t: u"QML_NAMED_ELEMENT("_s+ value.toString() + u ")"); |
184 | } |
185 | } else if (name == S_CREATABLE && value == S_FALSE) { |
186 | isExplicitlyUncreatable = true; |
187 | } else if (name == S_UNCREATABLE_REASON) { |
188 | qmlUncreatable = u"QML_UNCREATABLE(\""_s+ value.toString() + u "\")"; |
189 | } else if (name == S_ATTACHED) { |
190 | qmlAttached = u"QML_ATTACHED("_s+ value.toString() + u ")"; |
191 | } else if (name == S_SINGLETON) { |
192 | isSingleton = true; |
193 | } |
194 | } |
195 | if (qmlElements.isEmpty()) |
196 | continue; // no relevant entries found |
197 | const QString spaces = u" "_s; |
198 | if (isNamespace) { |
199 | registrationHelper += u"\nnamespace "_s+ foreignClassName + u "{\n Q_NAMESPACE\n"_s; |
200 | registrationHelper += spaces + u"QML_FOREIGN_NAMESPACE("+ qualifiedClassName + u ")\n"_s; |
201 | } else { |
202 | registrationHelper += u"\nstruct "_s+ foreignClassName + u "{\n Q_GADGET\n"_s; |
203 | registrationHelper += spaces + u"QML_FOREIGN("+ qualifiedClassName + u ")\n"_s; |
204 | } |
205 | registrationHelper += spaces + qmlElements.join(sep: u"\n"_s) + u "\n"_s; |
206 | if (isSingleton) |
207 | registrationHelper += spaces + u"QML_SINGLETON\n"_s; |
208 | if (isExplicitlyUncreatable) { |
209 | if (qmlUncreatable.isEmpty()) |
210 | registrationHelper += spaces + uR"(QML_UNCREATABLE(""))"+ u "n"; |
211 | else |
212 | registrationHelper += spaces + qmlUncreatable + u"\n"; |
213 | } |
214 | if (!qmlAttached.isEmpty()) |
215 | registrationHelper += spaces + qmlAttached + u"\n"; |
216 | registrationHelper += u"}"; |
217 | if (!isNamespace) |
218 | registrationHelper += u";"; |
219 | registrationHelper += u"\n"; |
220 | } |
221 | return registrationHelper; |
222 | } |
223 | |
224 | MetaTypesJsonProcessor::PreProcessResult MetaTypesJsonProcessor::preProcess( |
225 | const MetaType &classDef, PopulateMode populateMode) |
226 | { |
227 | // If this type is a self-extending value type or a sequence type or has a JavaScript extension |
228 | // and is not the root object, then it's foreign type has no entry of its own. |
229 | // In that case we need to generate a "primitive" entry. |
230 | |
231 | QList<QAnyStringView> primitiveAliases; |
232 | UsingDeclaration usingDeclaration; |
233 | |
234 | RegistrationMode mode = NoRegistration; |
235 | bool isSelfExtendingValueType = false; |
236 | bool hasJavaScriptExtension = false; |
237 | bool isRootObject = false; |
238 | bool isSequence = false; |
239 | |
240 | for (const ClassInfo &classInfo : classDef.classInfos()) { |
241 | if (classInfo.name == S_FOREIGN) |
242 | usingDeclaration.alias = classInfo.value; |
243 | else if (classInfo.name == S_PRIMITIVE_ALIAS) |
244 | primitiveAliases.append(t: classInfo.value); |
245 | else if (classInfo.name == S_EXTENSION_IS_JAVA_SCRIPT) |
246 | hasJavaScriptExtension = (classInfo.value == S_TRUE); |
247 | else if (classInfo.name == S_EXTENDED && classDef.kind() == MetaType::Kind::Gadget) |
248 | isSelfExtendingValueType = classInfo.value == classDef.className(); |
249 | else if (classInfo.name == S_ROOT) |
250 | isRootObject = (classInfo.value == S_TRUE); |
251 | else if (classInfo.name == S_SEQUENCE) |
252 | isSequence = true; |
253 | else if (classInfo.name == S_USING) |
254 | usingDeclaration.original = classInfo.value; |
255 | else if (populateMode == PopulateMode::Yes && classInfo.name == S_ELEMENT) { |
256 | switch (classDef.kind()) { |
257 | case MetaType::Kind::Object: |
258 | mode = ObjectRegistration; |
259 | break; |
260 | case MetaType::Kind::Gadget: |
261 | mode = GadgetRegistration; |
262 | break; |
263 | case MetaType::Kind::Namespace: |
264 | mode = NamespaceRegistration; |
265 | break; |
266 | default: |
267 | warning(classDef) |
268 | << "Not registering a classInfo which is neither an object," |
269 | << "nor a gadget, nor a namespace:" |
270 | << classInfo.name.toString(); |
271 | break; |
272 | } |
273 | } |
274 | } |
275 | |
276 | return PreProcessResult { |
277 | .primitiveAliases: std::move(primitiveAliases), |
278 | .usingDeclaration: usingDeclaration, |
279 | .foreignPrimitive: (!isRootObject && (isSequence || isSelfExtendingValueType || hasJavaScriptExtension)) |
280 | ? usingDeclaration.alias |
281 | : QAnyStringView(), |
282 | .mode: mode |
283 | }; |
284 | |
285 | } |
286 | |
287 | // TODO: Remove this when QAnyStringView gets a proper qHash() |
288 | static size_t qHash(QAnyStringView string, size_t seed = 0) |
289 | { |
290 | return string.visit(v: [seed](auto view) { |
291 | if constexpr (std::is_same_v<decltype(view), QStringView>) |
292 | return qHash(view, seed); |
293 | if constexpr (std::is_same_v<decltype(view), QLatin1StringView>) |
294 | return qHash(view, seed); |
295 | if constexpr (std::is_same_v<decltype(view), QUtf8StringView>) |
296 | return qHash(key: QByteArrayView(view.data(), view.length()), seed); |
297 | }); |
298 | } |
299 | |
300 | static bool qualifiedClassNameLessThan(const MetaType &a, const MetaType &b) |
301 | { |
302 | return a.qualifiedClassName() < b.qualifiedClassName(); |
303 | } |
304 | |
305 | enum class TypeRelation |
306 | { |
307 | Base, Property, Argument, Return, Enum, Attached, SequenceValue, Extension |
308 | }; |
309 | |
310 | static QLatin1StringView typeRelationString(TypeRelation relation) |
311 | { |
312 | switch (relation) { |
313 | case TypeRelation::Property: return "property"_L1; |
314 | case TypeRelation::Argument: return "argument"_L1; |
315 | case TypeRelation::Return: return "return"_L1; |
316 | case TypeRelation::Enum: return "enum"_L1; |
317 | case TypeRelation::Attached: return "attached"_L1; |
318 | case TypeRelation::SequenceValue: return "sequence value"_L1; |
319 | case TypeRelation::Extension: return "extension"_L1; |
320 | default: |
321 | break; |
322 | } |
323 | |
324 | Q_UNREACHABLE_RETURN(QLatin1StringView()); |
325 | } |
326 | |
327 | void MetaTypesJsonProcessor::addRelatedTypes() |
328 | { |
329 | QSet<QAnyStringView> processedRelatedNativeNames; |
330 | QSet<QAnyStringView> processedRelatedJavaScriptNames; |
331 | QSet<QAnyStringView> unresolvedForeignNames; |
332 | QQueue<MetaType> typeQueue; |
333 | typeQueue.append(l: m_types); |
334 | |
335 | const auto addRelatedName |
336 | = [&](QAnyStringView relatedName, const QList<QAnyStringView> &namespaces) { |
337 | if (const FoundType related = QmlTypesClassDescription::findType( |
338 | types: m_types, foreign: m_foreignTypes, name: relatedName, namespaces)) { |
339 | |
340 | if (!related.javaScript.isEmpty()) |
341 | processedRelatedJavaScriptNames.insert(value: related.javaScript.qualifiedClassName()); |
342 | |
343 | if (!related.native.isEmpty()) |
344 | processedRelatedNativeNames.insert(value: related.native.qualifiedClassName()); |
345 | |
346 | return true; |
347 | } else { |
348 | return false; |
349 | } |
350 | }; |
351 | |
352 | const auto addRelatedType = [&](const MetaType &type) { |
353 | const QAnyStringView qualifiedName = type.qualifiedClassName(); |
354 | if (type.inputFile().isEmpty()) |
355 | processedRelatedJavaScriptNames.insert(value: qualifiedName); |
356 | else |
357 | processedRelatedNativeNames.insert(value: qualifiedName); |
358 | }; |
359 | |
360 | // First mark all classes registered from this module as already processed. |
361 | for (const MetaType &type : std::as_const(t&: m_types)) { |
362 | addRelatedType(type); |
363 | for (const ClassInfo &obj : type.classInfos()) { |
364 | if (obj.name == S_FOREIGN) { |
365 | const QAnyStringView foreign = obj.value; |
366 | if (!addRelatedName(foreign, namespaces(classDef: type))) |
367 | unresolvedForeignNames.insert(value: foreign); |
368 | break; |
369 | } |
370 | } |
371 | } |
372 | |
373 | // Then mark all classes registered from other modules as already processed. |
374 | // We don't want to generate them again for this module. |
375 | for (const MetaType &foreignType : std::as_const(t&: m_foreignTypes)) { |
376 | bool seenQmlPrefix = false; |
377 | for (const ClassInfo &obj : foreignType.classInfos()) { |
378 | const QAnyStringView name = obj.name; |
379 | if (!seenQmlPrefix && startsWith(whole: name, part: "QML."_L1)) { |
380 | addRelatedType(foreignType); |
381 | seenQmlPrefix = true; |
382 | } |
383 | if (name == S_FOREIGN |
384 | || name == S_EXTENDED |
385 | || name == S_ATTACHED |
386 | || name == S_SEQUENCE) { |
387 | ResolvedTypeAlias foreign(obj.value, m_usingDeclarations); |
388 | if (!addRelatedName(foreign.type, namespaces(classDef: foreignType))) |
389 | unresolvedForeignNames.insert(value: foreign.type); |
390 | } |
391 | } |
392 | } |
393 | |
394 | const auto addReference |
395 | = [&](const MetaType &type, QSet<QAnyStringView> *processedRelatedNames, |
396 | FoundType::Origin origin) { |
397 | if (type.isEmpty()) |
398 | return; |
399 | QAnyStringView qualifiedName = type.qualifiedClassName(); |
400 | m_referencedTypes.append(t: qualifiedName); |
401 | const qsizetype size = processedRelatedNames->size(); |
402 | processedRelatedNames->insert(value: qualifiedName); |
403 | |
404 | if (processedRelatedNames->size() == size) |
405 | return; |
406 | |
407 | typeQueue.enqueue(t: type); |
408 | |
409 | if (origin == FoundType::OwnTypes) |
410 | return; |
411 | |
412 | // Add to own types since we need it for our registrations. |
413 | const auto insert = std::lower_bound( |
414 | first: m_types.constBegin(), last: m_types.constEnd(), val: type, |
415 | comp: qualifiedClassNameLessThan); |
416 | m_types.insert(before: insert, t: type); |
417 | |
418 | // We only add types to m_types of which we know we can reach them via the existing |
419 | // m_includes. We do not add to m_includes, because any further headers may not be |
420 | // #include'able. |
421 | |
422 | // Remove from the foreign types to avoid the ODR warning. |
423 | const auto remove = std::equal_range( |
424 | first: m_foreignTypes.constBegin(), last: m_foreignTypes.constEnd(), val: type, |
425 | comp: qualifiedClassNameLessThan); |
426 | for (auto it = remove.first; it != remove.second; ++it) { |
427 | if (*it == type) { |
428 | m_foreignTypes.erase(pos: it); |
429 | break; |
430 | } |
431 | } |
432 | }; |
433 | |
434 | const auto addInterface |
435 | = [&](QAnyStringView typeName, const QList<QAnyStringView> &namespaces) { |
436 | if (const FoundType other = QmlTypesClassDescription::findType( |
437 | types: m_types, foreign: m_foreignTypes, name: typeName, namespaces)) { |
438 | if (!other.native.isEmpty()) { |
439 | addReference(other.native, &processedRelatedNativeNames, other.nativeOrigin); |
440 | return true; |
441 | } |
442 | } else { |
443 | // Do not warn about unresolved interfaces. |
444 | // They don't have to have Q_OBJECT or Q_GADGET. |
445 | unresolvedForeignNames.insert(value: typeName); |
446 | } |
447 | |
448 | processedRelatedNativeNames.insert(value: typeName); |
449 | return false; |
450 | }; |
451 | |
452 | const auto doAddReferences = [&](QAnyStringView typeName, |
453 | const QList<QAnyStringView> &namespaces) { |
454 | if (const FoundType other = QmlTypesClassDescription::findType( |
455 | types: m_types, foreign: m_foreignTypes, name: typeName, namespaces)) { |
456 | addReference(other.native, &processedRelatedNativeNames, other.nativeOrigin); |
457 | addReference( |
458 | other.javaScript, &processedRelatedJavaScriptNames, other.javaScriptOrigin); |
459 | return true; |
460 | } |
461 | |
462 | return false; |
463 | }; |
464 | |
465 | const auto addType = [&](const MetaType &context, QAnyStringView typeName, |
466 | const QList<QAnyStringView> &namespaces, TypeRelation relation) { |
467 | if (doAddReferences(typeName, namespaces)) |
468 | return true; |
469 | |
470 | // If it's an enum, add the surrounding type. |
471 | const QLatin1StringView separator("::"); |
472 | if (const qsizetype index = lastIndexOf(whole: typeName, part: separator); index > 0) { |
473 | if (const FoundType other = QmlTypesClassDescription::findType( |
474 | types: m_types, foreign: m_foreignTypes, name: typeName.left(n: index), namespaces)) { |
475 | |
476 | const QAnyStringView enumName = typeName.mid(pos: index + separator.length()); |
477 | |
478 | for (const Enum &enumerator : other.native.enums()) { |
479 | if (enumerator.name != enumName && enumerator.alias != enumName) |
480 | continue; |
481 | |
482 | addReference(other.native, &processedRelatedNativeNames, other.nativeOrigin); |
483 | addReference( |
484 | other.javaScript, &processedRelatedJavaScriptNames, |
485 | other.javaScriptOrigin); |
486 | return true; |
487 | } |
488 | } |
489 | } |
490 | |
491 | // If it's an enum of the context type itself, we don't have to do anything. |
492 | for (const Enum &enumerator : context.enums()) { |
493 | if (enumerator.name == typeName || enumerator.alias == typeName) |
494 | return true; |
495 | } |
496 | |
497 | // If we've detected this type as unresolved foreign and it actually belongs to this module, |
498 | // we'll get to it again when we process it as foreign type. In that case we'll look at the |
499 | // special cases for sequences and extensions. |
500 | if (!unresolvedForeignNames.contains(value: typeName) && !isPrimitive(type: typeName)) { |
501 | warning(classDef: context) << typeName << "is used as"<< typeRelationString(relation) |
502 | << "type but cannot be found."; |
503 | } |
504 | |
505 | processedRelatedNativeNames.insert(value: typeName); |
506 | processedRelatedJavaScriptNames.insert(value: typeName); |
507 | return false; |
508 | }; |
509 | |
510 | |
511 | |
512 | const auto addSupers = [&](const MetaType &context, const QList<QAnyStringView> &namespaces) { |
513 | for (const Interface &iface : context.ifaces()) |
514 | addInterface(interfaceName(iface), namespaces); |
515 | |
516 | // We don't warn about missing bases for value types. They don't have to be registered. |
517 | bool warnAboutSupers = context.kind() != MetaType::Kind::Gadget; |
518 | |
519 | QList<QAnyStringView> missingSupers; |
520 | |
521 | for (const BaseType &superObject : context.superClasses()) { |
522 | if (superObject.access != Access::Public) |
523 | continue; |
524 | |
525 | QAnyStringView typeName = superObject.name; |
526 | if (doAddReferences(typeName, namespaces)) |
527 | warnAboutSupers = false; |
528 | else |
529 | missingSupers.append(t: typeName); |
530 | } |
531 | |
532 | for (QAnyStringView typeName : std::as_const(t&: missingSupers)) { |
533 | // If we've found one valid base type, don't complain about the others. |
534 | if (warnAboutSupers |
535 | && !unresolvedForeignNames.contains(value: typeName) |
536 | && !isPrimitive(type: typeName)) { |
537 | warning(classDef: context) << typeName << "is used as base type but cannot be found."; |
538 | } |
539 | |
540 | processedRelatedNativeNames.insert(value: typeName); |
541 | processedRelatedJavaScriptNames.insert(value: typeName); |
542 | } |
543 | }; |
544 | |
545 | const auto addEnums = [&](const MetaType &context, |
546 | const QList<QAnyStringView> &namespaces) { |
547 | for (const Enum &enumerator : context.enums()) { |
548 | ResolvedTypeAlias resolved(enumerator.type, m_usingDeclarations); |
549 | if (!resolved.type.isEmpty()) |
550 | addType(context, resolved.type, namespaces, TypeRelation::Enum); |
551 | } |
552 | }; |
553 | |
554 | const auto addRelation = [&](const MetaType &classDef, const ClassInfo &obj, |
555 | const QList<QAnyStringView> &namespaces) { |
556 | const QAnyStringView objNameValue = obj.name; |
557 | if (objNameValue == S_ATTACHED) { |
558 | addType(classDef, obj.value, namespaces, TypeRelation::Attached); |
559 | return true; |
560 | } else if (objNameValue == S_SEQUENCE) { |
561 | ResolvedTypeAlias value(obj.value, m_usingDeclarations); |
562 | addType(classDef, value.type, namespaces, TypeRelation::SequenceValue); |
563 | return true; |
564 | } else if (objNameValue == S_EXTENDED) { |
565 | const QAnyStringView value = obj.value; |
566 | addType(classDef, value, namespaces, TypeRelation::Extension); |
567 | return true; |
568 | } |
569 | return false; |
570 | }; |
571 | |
572 | // Then recursively iterate the super types and attached types, marking the |
573 | // ones we are interested in as related. |
574 | while (!typeQueue.isEmpty()) { |
575 | QAnyStringView unresolvedForeign; |
576 | |
577 | const MetaType classDef = typeQueue.dequeue(); |
578 | const QList<QAnyStringView> namespaces = MetaTypesJsonProcessor::namespaces(classDef); |
579 | |
580 | for (const ClassInfo &obj : classDef.classInfos()) { |
581 | if (addRelation(classDef, obj, namespaces)) |
582 | continue; |
583 | if (obj.name != S_FOREIGN) |
584 | continue; |
585 | |
586 | const QAnyStringView foreignClassName = obj.value; |
587 | |
588 | // A type declared as QML_FOREIGN will usually be a foreign type, but it can |
589 | // actually be an additional registration of a local type, too. |
590 | if (const FoundType found = QmlTypesClassDescription::findType( |
591 | types: m_foreignTypes, foreign: {}, name: foreignClassName, namespaces)) { |
592 | const MetaType other = found.select(category: classDef, relation: "Foreign"); |
593 | const QList<QAnyStringView> otherNamespaces |
594 | = MetaTypesJsonProcessor::namespaces(classDef: other); |
595 | addSupers(other, otherNamespaces); |
596 | addEnums(other, otherNamespaces); |
597 | |
598 | for (const ClassInfo &obj : other.classInfos()) { |
599 | if (addRelation(classDef, obj, otherNamespaces)) |
600 | break; |
601 | // No, you cannot chain S_FOREIGN declarations. Sorry. |
602 | } |
603 | } else if (!QmlTypesClassDescription::findType( |
604 | types: m_types, foreign: {}, name: foreignClassName, namespaces)) { |
605 | unresolvedForeign = foreignClassName; |
606 | } |
607 | } |
608 | |
609 | if (!unresolvedForeign.isEmpty() && !isPrimitive(type: unresolvedForeign)) { |
610 | warning(classDef) |
611 | << unresolvedForeign |
612 | << "is declared as foreign type, but cannot be found."; |
613 | } |
614 | |
615 | addSupers(classDef, namespaces); |
616 | addEnums(classDef, namespaces); |
617 | } |
618 | } |
619 | |
620 | void MetaTypesJsonProcessor::sortTypes(QVector<MetaType> &types) |
621 | { |
622 | std::sort(first: types.begin(), last: types.end(), comp: qualifiedClassNameLessThan); |
623 | } |
624 | |
625 | QString MetaTypesJsonProcessor::resolvedInclude(QAnyStringView include) |
626 | { |
627 | if (!m_privateIncludes) |
628 | return include.toString(); |
629 | |
630 | if (endsWith(whole: include, part: "_p.h"_L1)) |
631 | return QLatin1String("private/") + include.toString(); |
632 | |
633 | if (startsWith(whole: include, part: "qplatform"_L1) || startsWith(whole: include, part: "qwindowsystem"_L1)) |
634 | return QLatin1String("qpa/") + include.toString(); |
635 | |
636 | return include.toString(); |
637 | } |
638 | |
639 | void MetaTypesJsonProcessor::processTypes(const QCborMap &types) |
640 | { |
641 | const QString include = resolvedInclude(include: toStringView(map: types, key: S_INPUT_FILE)); |
642 | const QCborArray classes = types[S_CLASSES].toArray(); |
643 | for (const QCborValue &cls : classes) { |
644 | const MetaType classDef(cls.toMap(), include); |
645 | |
646 | const PreProcessResult preprocessed = preProcess(classDef, populateMode: PopulateMode::Yes); |
647 | switch (preprocessed.mode) { |
648 | case NamespaceRegistration: |
649 | case GadgetRegistration: |
650 | case ObjectRegistration: { |
651 | if (!endsWith(whole: include, part: QLatin1String(".h")) |
652 | && !endsWith(whole: include, part: QLatin1String(".hpp")) |
653 | && !endsWith(whole: include, part: QLatin1String(".hxx")) |
654 | && !endsWith(whole: include, part: QLatin1String(".hh")) |
655 | && !endsWith(whole: include, part: QLatin1String(".py")) |
656 | && contains(whole: include, part: QLatin1Char('.'))) { |
657 | warning(fileName: include) |
658 | << "Class"<< classDef.qualifiedClassName() |
659 | << "is declared in"<< include << "which appears not to be a header." |
660 | << "The compilation of its registration to QML may fail."; |
661 | } |
662 | m_includes.append(t: include); |
663 | m_types.emplaceBack(args: classDef); |
664 | break; |
665 | } |
666 | case NoRegistration: |
667 | m_foreignTypes.emplaceBack(args: classDef); |
668 | break; |
669 | } |
670 | |
671 | if (!preprocessed.foreignPrimitive.isEmpty()) { |
672 | m_primitiveTypes.emplaceBack(args: preprocessed.foreignPrimitive); |
673 | m_primitiveTypes.append(l: preprocessed.primitiveAliases); |
674 | } |
675 | |
676 | if (preprocessed.usingDeclaration.isValid()) |
677 | m_usingDeclarations.append(t: preprocessed.usingDeclaration); |
678 | } |
679 | } |
680 | |
681 | void MetaTypesJsonProcessor::processForeignTypes(const QCborMap &types) |
682 | { |
683 | const QString include = resolvedInclude(include: toStringView(map: types, key: S_INPUT_FILE)); |
684 | const QCborArray classes = types[S_CLASSES].toArray(); |
685 | for (const QCborValue &cls : classes) { |
686 | const MetaType classDef(cls.toMap(), include); |
687 | PreProcessResult preprocessed = preProcess(classDef, populateMode: PopulateMode::No); |
688 | |
689 | m_foreignTypes.emplaceBack(args: classDef); |
690 | if (!preprocessed.foreignPrimitive.isEmpty()) { |
691 | m_primitiveTypes.emplaceBack(args&: preprocessed.foreignPrimitive); |
692 | m_primitiveTypes.append(l: preprocessed.primitiveAliases); |
693 | } |
694 | |
695 | if (preprocessed.usingDeclaration.isValid()) |
696 | m_usingDeclarations.append(t: preprocessed.usingDeclaration); |
697 | } |
698 | } |
699 | |
700 | static QTypeRevision getRevision(const QCborMap &cbor) |
701 | { |
702 | const auto it = cbor.find(key: S_REVISION); |
703 | return it == cbor.end() |
704 | ? QTypeRevision() |
705 | : QTypeRevision::fromEncodedVersion(value: it->toInteger()); |
706 | } |
707 | |
708 | static Access getAccess(const QCborMap &cbor) |
709 | { |
710 | const QAnyStringView access = toStringView(map: cbor, key: S_ACCESS); |
711 | if (access == S_PUBLIC) |
712 | return Access::Public; |
713 | if (access == S_PROTECTED) |
714 | return Access::Protected; |
715 | return Access::Private; |
716 | } |
717 | |
718 | BaseType::BaseType(const QCborMap &cbor) |
719 | : name(toStringView(map: cbor, key: S_NAME)) |
720 | , access(getAccess(cbor)) |
721 | { |
722 | } |
723 | |
724 | ClassInfo::ClassInfo(const QCborMap &cbor) |
725 | : name(toStringView(map: cbor, key: S_NAME)) |
726 | , value(toStringView(map: cbor, key: S_VALUE)) |
727 | { |
728 | } |
729 | |
730 | Interface::Interface(const QCborValue &cbor) |
731 | { |
732 | if (cbor.isArray()) { |
733 | QCborArray needlessWrapping = cbor.toArray(); |
734 | className = needlessWrapping.size() > 0 |
735 | ? toStringView(map: needlessWrapping[0].toMap(), key: S_CLASS_NAME) |
736 | : QAnyStringView(); |
737 | } else { |
738 | className = toStringView(map: cbor.toMap(), key: S_CLASS_NAME); |
739 | } |
740 | } |
741 | |
742 | Property::Property(const QCborMap &cbor) |
743 | : name(toStringView(map: cbor, key: S_NAME)) |
744 | , type(toStringView(map: cbor, key: S_TYPE)) |
745 | , member(toStringView(map: cbor, key: S_MEMBER)) |
746 | , read(toStringView(map: cbor, key: S_READ)) |
747 | , write(toStringView(map: cbor, key: S_WRITE)) |
748 | , reset(toStringView(map: cbor, key: S_RESET)) |
749 | , notify(toStringView(map: cbor, key: S_NOTIFY)) |
750 | , bindable(toStringView(map: cbor, key: S_BINDABLE)) |
751 | , privateClass(toStringView(map: cbor, key: S_PRIVATE_CLASS)) |
752 | , index(cbor[S_INDEX].toInteger(defaultValue: -1)) |
753 | , revision(getRevision(cbor)) |
754 | , isFinal(cbor[S_FINAL].toBool()) |
755 | , isConstant(cbor[S_CONSTANT].toBool()) |
756 | , isRequired(cbor[S_REQUIRED].toBool()) |
757 | { |
758 | } |
759 | |
760 | Argument::Argument(const QCborMap &cbor) |
761 | : name(toStringView(map: cbor, key: S_NAME)) |
762 | , type(toStringView(map: cbor, key: S_TYPE)) |
763 | { |
764 | } |
765 | |
766 | Method::Method(const QCborMap &cbor, bool isConstructor) |
767 | : name(toStringView(map: cbor, key: S_NAME)) |
768 | , returnType(toStringView(map: cbor, key: S_RETURN_TYPE)) |
769 | , index(cbor[S_INDEX].toInteger(defaultValue: InvalidIndex)) |
770 | , revision(getRevision(cbor)) |
771 | , access(getAccess(cbor)) |
772 | , isCloned(cbor[S_IS_CLONED].toBool()) |
773 | , isJavaScriptFunction(cbor[S_IS_JAVASCRIPT_FUNCTION].toBool()) |
774 | , isConstructor(isConstructor || cbor[S_IS_CONSTRUCTOR].toBool()) |
775 | { |
776 | const QCborArray args = cbor[S_ARGUMENTS].toArray(); |
777 | for (const QCborValue &argument : args) |
778 | arguments.emplace_back(args: argument.toMap()); |
779 | |
780 | if (arguments.size() == 1) { |
781 | const QAnyStringView type = arguments[0].type; |
782 | if (type == "QQmlV4FunctionPtr"_L1|| type == "QQmlV4Function*"_L1) { |
783 | isJavaScriptFunction = true; |
784 | arguments.clear(); |
785 | } |
786 | } |
787 | } |
788 | |
789 | Enum::Enum(const QCborMap &cbor) |
790 | : name(toStringView(map: cbor, key: S_NAME)) |
791 | , alias(toStringView(map: cbor, key: S_ALIAS)) |
792 | , type(toStringView(map: cbor, key: S_TYPE)) |
793 | , isFlag(cbor[S_IS_FLAG].toBool()) |
794 | , isClass(cbor[S_IS_CLASS].toBool()) |
795 | { |
796 | const QCborArray vals = cbor[S_VALUES].toArray(); |
797 | for (const QCborValue &value : vals) |
798 | values.emplace_back(args: toStringView(value)); |
799 | } |
800 | |
801 | MetaTypePrivate::MetaTypePrivate(const QCborMap &cbor, const QString &inputFile) |
802 | : cbor(cbor) |
803 | , inputFile(inputFile) |
804 | { |
805 | className = toStringView(map: cbor, key: S_CLASS_NAME); |
806 | lineNumber = cbor[S_LINENUMBER].toInteger(defaultValue: 0); |
807 | qualifiedClassName = toStringView(map: cbor, key: S_QUALIFIED_CLASS_NAME); |
808 | |
809 | const QCborArray cborSuperClasses = cbor[S_SUPER_CLASSES].toArray(); |
810 | for (const QCborValue &superClass : cborSuperClasses) |
811 | superClasses.emplace_back(args: superClass.toMap()); |
812 | |
813 | const QCborArray cborClassInfos = cbor[S_CLASS_INFOS].toArray(); |
814 | for (const QCborValue &classInfo : cborClassInfos) |
815 | classInfos.emplace_back(args: classInfo.toMap()); |
816 | |
817 | const QCborArray cborIfaces = cbor[S_INTERFACES].toArray(); |
818 | for (const QCborValue &iface : cborIfaces) |
819 | ifaces.emplace_back(args: iface); |
820 | |
821 | const QCborArray cborProperties = cbor[S_PROPERTIES].toArray(); |
822 | for (const QCborValue &property : cborProperties) |
823 | properties.emplace_back(args: property.toMap()); |
824 | |
825 | for (const QCborArray &cborMethods : { cbor[S_SLOTS].toArray(), cbor[S_METHODS].toArray() }) { |
826 | for (const QCborValue &method : cborMethods) |
827 | methods.emplace_back(args: method.toMap(), args: false); |
828 | } |
829 | |
830 | const QCborArray cborSigs = cbor[S_SIGNALS].toArray(); |
831 | for (const QCborValue &sig : cborSigs) |
832 | sigs.emplace_back(args: sig.toMap(), args: false); |
833 | |
834 | const QCborArray cborConstructors = cbor[S_CONSTRUCTORS].toArray(); |
835 | for (const QCborValue &constructor : cborConstructors) |
836 | constructors.emplace_back(args: constructor.toMap(), args: true); |
837 | |
838 | const QCborArray cborEnums = cbor[S_ENUMS].toArray(); |
839 | for (const QCborValue &enumerator : cborEnums) |
840 | enums.emplace_back(args: enumerator.toMap()); |
841 | |
842 | if (cbor[S_GADGET].toBool()) |
843 | kind = Kind::Gadget; |
844 | else if (cbor[S_OBJECT].toBool()) |
845 | kind = Kind::Object; |
846 | else if (cbor[S_NAMESPACE].toBool()) |
847 | kind = Kind::Namespace; |
848 | } |
849 | |
850 | MetaType::MetaType(const QCborMap &cbor, const QString &inputFile) |
851 | : d(s_pool.emplace_back(args: std::make_unique<MetaTypePrivate>(args: cbor, args: inputFile)).get()) |
852 | {} |
853 | |
854 | QT_END_NAMESPACE |
855 |
Definitions
- s_empty
- s_pool
- fromJson
- namespaces
- processTypes
- processForeignTypes
- processForeignTypes
- sortStringList
- postProcessTypes
- postProcessForeignTypes
- extractRegisteredTypes
- preProcess
- qHash
- qualifiedClassNameLessThan
- TypeRelation
- typeRelationString
- addRelatedTypes
- sortTypes
- resolvedInclude
- processTypes
- processForeignTypes
- getRevision
- getAccess
- BaseType
- ClassInfo
- Interface
- Property
- Argument
- Method
- Enum
- MetaTypePrivate
Learn Advanced QML with KDAB
Find out more