Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmltypesclassdescription.cpp
Go to the documentation of this file.
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// Qt-Security score:significant
4
7
11
12#include <QtCore/qcborarray.h>
13#include <QtCore/qcbormap.h>
15
16using namespace Qt::StringLiterals;
17using namespace Constants;
18using namespace Constants::MetatypesDotJson;
19using namespace Constants::MetatypesDotJson::Qml;
20using namespace QAnyStringViewUtils;
21
22template<typename Container>
23static void collectExtraVersions(const Container &items, QList<QTypeRevision> &extraVersions)
24{
25 for (const auto &obj : items) {
26 if (obj.revision.isValid() && !extraVersions.contains(obj.revision))
27 extraVersions.append(obj.revision);
28 }
29}
30
31struct Compare {
32 bool operator()(const QAnyStringView &typeName, const MetaType &type) const
33 {
34 return typeName < type.qualifiedClassName();
35 }
36
37 bool operator()(const MetaType &type, const QAnyStringView &typeName) const
38 {
39 return type.qualifiedClassName() < typeName;
40 }
41};
42
44{
45 if (single.inputFile().isEmpty()) {
46 javaScript = single;
47 javaScriptOrigin = origin;
48 } else {
49 native = single;
50 nativeOrigin = origin;
51 }
52}
53
54MetaType FoundType::select(const MetaType &category, QAnyStringView relation) const
55{
56 if (category.inputFile().isEmpty()) {
58 warning(category)
59 << relation << "type of" << category.qualifiedClassName()
60 << "is not a JavaScript type";
61 }
62 return javaScript;
63 }
64
65 if (native.isEmpty()) {
66 warning(category)
67 << relation << "of" << category.qualifiedClassName()
68 << "is not a native type";
69 }
70 return native;
71}
72
74 const QList<MetaType> &types, const QList<MetaType> &foreign,
75 const QAnyStringView &name, const QList<QAnyStringView> &namespaces)
76{
77 const auto tryFindType = [&](QAnyStringView qualifiedName) -> FoundType {
78 FoundType result;
79 for (const QList<MetaType> &t : {types, foreign}) {
80 const auto [first, last] = std::equal_range(
81 t.begin(), t.end(), qualifiedName, Compare());
82 for (auto it = first; it != last; ++it) {
83 Q_ASSERT(it->qualifiedClassName() == qualifiedName);
84
85 if (it->inputFile().isEmpty()) {
86 if (result.javaScript.isEmpty()) {
87 result.javaScript = *it;
88 result.javaScriptOrigin = (&t == &types)
89 ? FoundType::OwnTypes
90 : FoundType::ForeignTypes;
91 } else {
92 warning(result.javaScript)
93 << "Multiple JavaScript types called" << qualifiedName << "found!";
94 }
95 } else if (result.native.isEmpty()) {
96 result.native = *it;
97 result.nativeOrigin = (&t == &types)
98 ? FoundType::OwnTypes
99 : FoundType::ForeignTypes;
100 } else {
101 warning(result.native)
102 << "Multiple C++ types called" << qualifiedName << "found!\n"
103 << "(other occurrence in :"
104 << it->inputFile() << ":" << it->lineNumber() << ")\n"
105 << "This violates the One Definition Rule!";
106 }
107 }
108 }
109
110 return result;
111 };
112
113 if (startsWith(name, QLatin1String("::")))
114 return tryFindType(name.mid(2));
115
116 QString qualified;
117 for (int i = 0, end = namespaces.length(); i != end; ++i) {
118 for (int j = 0; j < end - i; ++j) {
119 namespaces[j].visit([&](auto data) { qualified.append(data); });
120 qualified.append(QLatin1String("::"));
121 }
122 name.visit([&](auto data) { qualified.append(data); });
123 if (const FoundType found = tryFindType(qualified))
124 return found;
125
126 qualified.truncate(0);
127 }
128
129 return tryFindType(name);
130}
131
132void QmlTypesClassDescription::collectSuperClasses(
133 const MetaType &classDef, const QList<MetaType> &types,
134 const QList<MetaType> &foreign, CollectMode mode, QTypeRevision defaultRevision)
135{
136 const QList<QAnyStringView> namespaces = MetaTypesJsonProcessor::namespaces(classDef);
137 QAnyStringView superClassCandidate;
138 for (const BaseType &superObject : std::as_const(classDef.superClasses())) {
139 if (superObject.access == Access::Public) {
140 const QAnyStringView superName = superObject.name;
141
142 const CollectMode superMode = (mode == TopLevel) ? SuperClass : RelatedType;
143 if (const FoundType found = findType(types, foreign, superName, namespaces)) {
144 const MetaType other = found.select(classDef, "Base");
145 collect(other, types, foreign, superMode, defaultRevision);
146 if (mode == TopLevel && superClass.isEmpty())
147 superClass = other.qualifiedClassName();
148 } else {
149 // If we cannot resolve anything but find a correctly formed superObject,
150 // we can at least populate the name. Further tooling might locate the type
151 // in a different module.
152 superClassCandidate = superName;
153 }
154 }
155 }
156
157 if (mode == TopLevel && superClass.isEmpty())
158 superClass = superClassCandidate;
159}
160
161void QmlTypesClassDescription::collectInterfaces(const MetaType &classDef)
162{
163 for (const Interface &iface : classDef.ifaces())
164 implementsInterfaces << interfaceName(iface);
165}
166
167void QmlTypesClassDescription::handleRegisterEnumClassesUnscoped(
168 const MetaType &classDef, QAnyStringView value)
169{
170 if (value == S_FALSE)
171 enforcesScopedEnums = true;
172 else if (value == S_TRUE)
173 warning(classDef) << "Setting RegisterEnumClassesUnscoped to true has no effect.";
174 else
175 warning(classDef) << "Unrecognized value for RegisterEnumClassesUnscoped:" << value;
176}
177
179 const MetaType &classDef, const QList<MetaType> &types,
180 const QList<MetaType> &foreign, QTypeRevision defaultRevision)
181{
182 file = classDef.inputFile();
183 lineNumber = classDef.lineNumber();
184
185 resolvedClass = classDef;
186 className = classDef.qualifiedClassName();
187
188 switch (classDef.kind()) {
189 case MetaType::Kind::Object:
190 accessSemantics = DotQmltypes::S_REFERENCE;
191 break;
192 case MetaType::Kind::Gadget:
193 accessSemantics = DotQmltypes::S_VALUE;
194 break;
195 case MetaType::Kind::Namespace:
196 case MetaType::Kind::Unknown:
197 accessSemantics = DotQmltypes::S_NONE;
198 break;
199 }
200
201 for (const ClassInfo &obj : classDef.classInfos()) {
202 if (obj.name == S_DEFAULT_PROPERTY)
203 defaultProp = obj.value;
204 else if (obj.name == S_PARENT_PROPERTY)
205 parentProp = obj.value;
206 else if (obj.name == S_REGISTER_ENUM_CLASSES_UNSCOPED)
207 handleRegisterEnumClassesUnscoped(classDef, obj.value);
208 }
209
210 collectInterfaces(classDef);
211 collectSuperClasses(classDef, types, foreign, TopLevel, defaultRevision);
212}
213
215 const MetaType &classDef, const QList<MetaType> &types,
216 const QList<MetaType> &foreign, CollectMode mode, QTypeRevision defaultRevision)
217{
218 if (file.isEmpty()) {
219 file = classDef.inputFile();
220 lineNumber = classDef.lineNumber();
221 }
222
223 if (metaObjectHash.isEmpty())
224 metaObjectHash = classDef.metaObjectHash();
225
226 const QAnyStringView classDefName = classDef.className();
227 const QList<QAnyStringView> namespaces = MetaTypesJsonProcessor::namespaces(classDef);
228
229 QAnyStringView foreignTypeName;
230 bool foreignIsNamespace = false;
231 bool isConstructible = false;
232 for (const ClassInfo &obj : classDef.classInfos()) {
233 const QAnyStringView name = obj.name;
234 const QAnyStringView value = obj.value;
235
236 if (name == S_DEFAULT_PROPERTY) {
237 if (mode != RelatedType && defaultProp.isEmpty())
238 defaultProp = value;
239 continue;
240 }
241
242 if (name == S_PARENT_PROPERTY) {
243 if (mode != RelatedType && parentProp.isEmpty())
244 parentProp = value;
245 continue;
246 }
247
248 if (name == S_REGISTER_ENUM_CLASSES_UNSCOPED) {
249 if (mode != RelatedType)
250 handleRegisterEnumClassesUnscoped(classDef, value);
251 continue;
252 }
253
254 if (name == S_ADDED_IN_VERSION) {
255 const QTypeRevision revision = handleInMinorVersion(
256 QTypeRevision::fromEncodedVersion(toInt(value)),
257 defaultRevision.majorVersion());
258 revisions.append(revision);
259 if (mode == TopLevel)
260 addedInRevision = revision;
261 continue;
262 }
263
264 if (mode != TopLevel)
265 continue;
266
267 if (name == S_REMOVED_IN_VERSION) {
268 removedInRevision = handleInMinorVersion(
269 QTypeRevision::fromEncodedVersion(toInt(value)),
270 defaultRevision.majorVersion());
271 continue;
272 }
273
274 // These only apply to the original class
275 if (name == S_ELEMENT) {
276 if (value == S_AUTO)
277 elementNames.append(classDefName);
278 else if (value != S_ANONYMOUS)
279 elementNames.append(value);
280 } else if (name == S_CREATABLE) {
281 isCreatable = (value != S_FALSE);
282 } else if (name == S_CREATION_METHOD) {
283 isStructured = (value == S_STRUCTURED);
284 isConstructible = isStructured || (value == S_CONSTRUCT);
285 } else if (name == S_ATTACHED) {
286 if (const FoundType attached = collectRelated(
287 value, types, foreign, defaultRevision, namespaces)) {
288 attachedType = attached.select(classDef, "Attached").qualifiedClassName();
289 }
290 } else if (name == S_EXTENDED) {
291 if (const FoundType extension = collectRelated(
292 value, types, foreign, defaultRevision, namespaces)) {
293 javaScriptExtensionType = extension.javaScript.qualifiedClassName();
294 nativeExtensionType = extension.native.qualifiedClassName();
295 }
296 } else if (name == S_EXTENSION_IS_JAVA_SCRIPT) {
297 if (value == S_TRUE)
298 extensionIsJavaScript = true;
299 } else if (name == S_EXTENSION_IS_NAMESPACE) {
300 if (value == S_TRUE)
301 extensionIsNamespace = true;
302 } else if (name == S_SEQUENCE) {
303 if (const FoundType element = collectRelated(
304 value, types, foreign, defaultRevision, namespaces)) {
305 sequenceValueType = element.select(classDef, "Sequence value").qualifiedClassName();
306 } else {
307 // TODO: get rid of this once we have JSON data for the builtins.
308 sequenceValueType = value;
309 }
310 } else if (name == S_SINGLETON) {
311 if (value == S_TRUE)
312 isSingleton = true;
313 } else if (name == S_FOREIGN) {
314 foreignTypeName = value;
315 } else if (name == S_FOREIGN_IS_NAMESPACE) {
316 foreignIsNamespace = (value == S_TRUE);
317 } else if (name == S_PRIMITIVE_ALIAS) {
318 primitiveAliases.append(value);
319 } else if (name == S_ROOT) {
320 isRootClass = (value == S_TRUE);
321 } else if (name == S_HAS_CUSTOM_PARSER) {
322 if (value == S_TRUE)
323 hasCustomParser = true;
324 } else if (name == S_DEFERRED_PROPERTY_NAMES) {
325 deferredNames = split(value, QLatin1StringView(","));
326 } else if (name == S_IMMEDIATE_PROPERTY_NAMES) {
327 immediateNames = split(value, QLatin1StringView(","));
328 }
329 }
330
331 if (addedInRevision.isValid() && !elementNames.isEmpty())
332 revisions.append(addedInRevision);
333
334 // If the local type is a namespace the result can only be a namespace,
335 // no matter what the foreign type is.
336 const bool isNamespace = foreignIsNamespace || classDef.kind() == MetaType::Kind::Namespace;
337
338 MetaType resolved = classDef;
339 if (!foreignTypeName.isEmpty()) {
340 // We can re-use a type with own QML.* macros as target of QML.Foreign
341 if (const FoundType found = findType(foreign, types, foreignTypeName, namespaces)) {
342 resolved = found.select(classDef, "Foreign");
343
344 // Default properties and enum classes are always local.
345 defaultProp = {};
346 enforcesScopedEnums = false;
347
348 // Foreign type can have a default property or an attached type,
349 // or RegisterEnumClassesUnscoped classinfo.
350 for (const ClassInfo &obj : resolved.classInfos()) {
351 const QAnyStringView foreignName = obj.name;
352 const QAnyStringView foreignValue = obj.value;
353 if (defaultProp.isEmpty() && foreignName == S_DEFAULT_PROPERTY) {
354 defaultProp = foreignValue;
355 } else if (parentProp.isEmpty() && foreignName == S_PARENT_PROPERTY) {
356 parentProp = foreignValue;
357 } else if (foreignName == S_REGISTER_ENUM_CLASSES_UNSCOPED) {
358 handleRegisterEnumClassesUnscoped(resolved, foreignValue);
359 } else if (foreignName == S_ATTACHED) {
360 if (const FoundType attached = collectRelated(
361 foreignValue, types, foreign, defaultRevision, namespaces)) {
362 attachedType = attached.select(resolved, "Attached").qualifiedClassName();
363 }
364 } else if (foreignName == S_EXTENDED) {
365 if (const FoundType extension = collectRelated(
366 foreignValue, types, foreign, defaultRevision, namespaces)) {
367 nativeExtensionType = extension.native.qualifiedClassName();
368 javaScriptExtensionType = extension.javaScript.qualifiedClassName();
369 }
370 } else if (foreignName == S_EXTENSION_IS_JAVA_SCRIPT) {
371 if (foreignValue == S_TRUE)
372 extensionIsJavaScript = true;
373 } else if (foreignName == S_EXTENSION_IS_NAMESPACE) {
374 if (foreignValue == S_TRUE)
375 extensionIsNamespace = true;
376 } else if (foreignName == S_SEQUENCE) {
377 if (const FoundType element = collectRelated(
378 foreignValue, types, foreign, defaultRevision, namespaces)) {
379 sequenceValueType
380 = element.select(resolved, "Sequence value").qualifiedClassName();
381 }
382 }
383 }
384 } else {
385 className = foreignTypeName;
386 resolved = MetaType();
387 }
388 }
389
390 if (!resolved.isEmpty()) {
391 if (mode == RelatedType || !elementNames.isEmpty()) {
392 collectExtraVersions(resolved.properties(), revisions);
393 collectExtraVersions(resolved.methods(), revisions);
394 collectExtraVersions(resolved.sigs(), revisions);
395 }
396
397 collectSuperClasses(resolved, types, foreign, mode, defaultRevision);
398 }
399
400 if (mode != TopLevel)
401 return;
402
403 if (!resolved.isEmpty())
404 collectInterfaces(resolved);
405
406 if (!addedInRevision.isValid()) {
407 addedInRevision = defaultRevision;
408 }
409 if (addedInRevision <= defaultRevision
410 && (!removedInRevision.isValid() || defaultRevision < removedInRevision)) {
411 revisions.append(defaultRevision);
412 }
413
414 std::sort(revisions.begin(), revisions.end());
415 const auto end = std::unique(revisions.begin(), revisions.end());
416 revisions.erase(QList<QTypeRevision>::const_iterator(end), revisions.constEnd());
417
418 resolvedClass = resolved;
419 if (className.isEmpty() && !resolved.isEmpty())
420 className = resolved.qualifiedClassName();
421
422 if (!sequenceValueType.isEmpty()) {
423 isCreatable = false;
424 accessSemantics = DotQmltypes::S_SEQUENCE;
425 } else if (isNamespace) {
426 isCreatable = false;
427 accessSemantics = DotQmltypes::S_NONE;
428 } else if (resolved.kind() == MetaType::Kind::Object) {
429 accessSemantics = DotQmltypes::S_REFERENCE;
430 } else {
431 isCreatable = isConstructible;
432
433 if (resolved.isEmpty()) {
434 if (elementNames.isEmpty()) {
435 // If no resolved, we generally assume it's a value type defined by the
436 // foreign/extended trick.
437 accessSemantics = DotQmltypes::S_VALUE;
438 }
439
440 for (auto elementName = elementNames.begin(); elementName != elementNames.end();) {
441 if (elementName->isEmpty() || elementName->front().isLower()) {
442 // If no resolved, we generally assume it's a value type defined by the
443 // foreign/extended trick.
444 accessSemantics = DotQmltypes::S_VALUE;
445 ++elementName;
446 } else {
447 // Objects and namespaces always have metaobjects and therefore classDefs.
448 // However, we may not be able to resolve the metaobject at compile time. See
449 // the "Invisible" test case. In that case, we must not assume anything about
450 // access semantics.
451
452 warning(classDef)
453 << "Refusing to generate non-lowercase name"
454 << *elementName << "for unknown foreign type";
455 elementName = elementNames.erase(elementName);
456
457 if (elementNames.isEmpty()) {
458 // Make it completely inaccessible.
459 // We cannot get enums from anonymous types after all.
460 accessSemantics = DotQmltypes::S_NONE;
461 }
462 }
463 }
464 } else if (resolved.kind() == MetaType::Kind::Gadget) {
465 accessSemantics = DotQmltypes::S_VALUE;
466 } else {
467 accessSemantics = DotQmltypes::S_NONE;
468 }
469 }
470}
471
473 QAnyStringView related, const QList<MetaType> &types, const QList<MetaType> &foreign,
474 QTypeRevision defaultRevision, const QList<QAnyStringView> &namespaces)
475{
476 if (FoundType other = findType(types, foreign, related, namespaces)) {
477 if (!other.native.isEmpty())
478 collect(other.native, types, foreign, RelatedType, defaultRevision);
479 if (!other.javaScript.isEmpty())
480 collect(other.javaScript, types, foreign, RelatedType, defaultRevision);
481 return other;
482 }
483 return FoundType();
484}
485
487 bool operator()(const UsingDeclaration &a, QAnyStringView b) const
488 {
489 return a.alias < b;
490 }
491
492 bool operator()(QAnyStringView a, const UsingDeclaration &b) const
493 {
494 return a < b.alias;
495 }
496};
497
499 QAnyStringView alias, const QList<UsingDeclaration> &usingDeclarations)
500 : type(alias)
501{
502 handleVoid();
503 if (type.isEmpty())
504 return;
505
506 handleList();
507
508 if (!isList) {
509 handlePointer();
510 handleConst();
511 }
512
513 while (true) {
514 const auto usingDeclaration = std::equal_range(
515 usingDeclarations.begin(), usingDeclarations.end(), type, UsingCompare());
516 if (usingDeclaration.first == usingDeclaration.second)
517 break;
518
519 type = usingDeclaration.first->original;
520 handleVoid();
521 if (type.isEmpty())
522 return;
523
524 if (isPointer) {
525 handleConst();
526 continue;
527 }
528
529 if (!isList) {
530 handleList();
531 if (!isList) {
532 handlePointer();
533 handleConst();
534 }
535 }
536 }
537}
538
539void ResolvedTypeAlias::handleVoid()
540{
541 if (!isPointer && type == "void")
542 type = "";
543}
544
545void ResolvedTypeAlias::handleList()
546{
547 for (QLatin1StringView list : {"QQmlListProperty<"_L1, "QList<"_L1}) {
548 if (!startsWith(type, list) || type.back() != '>'_L1)
549 continue;
550
551 const int listSize = list.size();
552 const QAnyStringView elementType = trimmed(type.mid(listSize, type.size() - listSize - 1));
553
554 // QQmlListProperty internally constructs the pointer. Passing an explicit '*' will
555 // produce double pointers. QList is only for value types. We can't handle QLists
556 // of pointers (unless specially registered, but then they're not isList).
557 if (elementType.back() == '*'_L1)
558 continue;
559
560 isList = true;
561 type = elementType;
562 return;
563 }
564}
565
566void ResolvedTypeAlias::handlePointer()
567{
568 if (type.back() == '*'_L1) {
569 isPointer = true;
570 type = type.chopped(1);
571 }
572}
573
574void ResolvedTypeAlias::handleConst()
575{
576 if (startsWith(type, "const "_L1)) {
577 isConstant = true;
578 type = type.sliced(strlen("const "));
579 }
580}
581
582QT_END_NAMESPACE
MetaType()=default
Combined button and popup list for selecting options.
QDebug warning(const MetaType &classDef)
static void collectExtraVersions(const Container &items, QList< QTypeRevision > &extraVersions)
bool operator()(const QAnyStringView &typeName, const MetaType &type) const
bool operator()(const MetaType &type, const QAnyStringView &typeName) const
MetaType select(const MetaType &category, QAnyStringView relation) const
FoundType(const MetaType &single, Origin origin)
FoundType()=default
void collect(const MetaType &classDef, const QList< MetaType > &types, const QList< MetaType > &foreign, CollectMode mode, QTypeRevision defaultRevision)
void collectLocalAnonymous(const MetaType &classDef, const QList< MetaType > &types, const QList< MetaType > &foreign, QTypeRevision defaultRevision)
FoundType collectRelated(QAnyStringView related, const QList< MetaType > &types, const QList< MetaType > &foreign, QTypeRevision defaultRevision, const QList< QAnyStringView > &namespaces)
ResolvedTypeAlias(QAnyStringView alias, const QList< UsingDeclaration > &usingDeclarations)
bool operator()(const UsingDeclaration &a, QAnyStringView b) const
bool operator()(QAnyStringView a, const UsingDeclaration &b) const