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 const QAnyStringView classDefName = classDef.className();
224 const QList<QAnyStringView> namespaces = MetaTypesJsonProcessor::namespaces(classDef);
225
226 QAnyStringView foreignTypeName;
227 bool foreignIsNamespace = false;
228 bool isConstructible = false;
229 for (const ClassInfo &obj : classDef.classInfos()) {
230 const QAnyStringView name = obj.name;
231 const QAnyStringView value = obj.value;
232
233 if (name == S_DEFAULT_PROPERTY) {
234 if (mode != RelatedType && defaultProp.isEmpty())
235 defaultProp = value;
236 continue;
237 }
238
239 if (name == S_PARENT_PROPERTY) {
240 if (mode != RelatedType && parentProp.isEmpty())
241 parentProp = value;
242 continue;
243 }
244
245 if (name == S_REGISTER_ENUM_CLASSES_UNSCOPED) {
246 if (mode != RelatedType)
247 handleRegisterEnumClassesUnscoped(classDef, value);
248 continue;
249 }
250
251 if (name == S_ADDED_IN_VERSION) {
252 const QTypeRevision revision = handleInMinorVersion(
253 QTypeRevision::fromEncodedVersion(toInt(value)),
254 defaultRevision.majorVersion());
255 revisions.append(revision);
256 if (mode == TopLevel)
257 addedInRevision = revision;
258 continue;
259 }
260
261 if (name == S_EXTRA_VERSION) {
262 const QTypeRevision revision = handleInMinorVersion(
263 QTypeRevision::fromEncodedVersion(toInt(value)),
264 defaultRevision.majorVersion());
265 revisions.append(revision);
266 continue;
267 }
268
269 if (mode != TopLevel)
270 continue;
271
272 if (name == S_REMOVED_IN_VERSION) {
273 removedInRevision = handleInMinorVersion(
274 QTypeRevision::fromEncodedVersion(toInt(value)),
275 defaultRevision.majorVersion());
276 continue;
277 }
278
279 // These only apply to the original class
280 if (name == S_ELEMENT) {
281 if (value == S_AUTO)
282 elementNames.append(classDefName);
283 else if (value != S_ANONYMOUS)
284 elementNames.append(value);
285 } else if (name == S_CREATABLE) {
286 isCreatable = (value != S_FALSE);
287 } else if (name == S_CREATION_METHOD) {
288 isStructured = (value == S_STRUCTURED);
289 isConstructible = isStructured || (value == S_CONSTRUCT);
290 } else if (name == S_ATTACHED) {
291 if (const FoundType attached = collectRelated(
292 value, types, foreign, defaultRevision, namespaces)) {
293 attachedType = attached.select(classDef, "Attached").qualifiedClassName();
294 }
295 } else if (name == S_EXTENDED) {
296 if (const FoundType extension = collectRelated(
297 value, types, foreign, defaultRevision, namespaces)) {
298 javaScriptExtensionType = extension.javaScript.qualifiedClassName();
299 nativeExtensionType = extension.native.qualifiedClassName();
300 }
301 if (value == classDefName)
302 isSelfExtension = true;
303 } else if (name == S_EXTENSION_IS_JAVA_SCRIPT) {
304 if (value == S_TRUE)
305 extensionIsJavaScript = true;
306 } else if (name == S_EXTENSION_IS_NAMESPACE) {
307 if (value == S_TRUE)
308 extensionIsNamespace = true;
309 } else if (name == S_SEQUENCE) {
310 if (const FoundType element = collectRelated(
311 value, types, foreign, defaultRevision, namespaces)) {
312 sequenceValueType = element.select(classDef, "Sequence value").qualifiedClassName();
313 } else {
314 // TODO: get rid of this once we have JSON data for the builtins.
315 sequenceValueType = value;
316 }
317 } else if (name == S_SINGLETON) {
318 if (value == S_TRUE)
319 isSingleton = true;
320 } else if (name == S_FOREIGN) {
321 foreignTypeName = value;
322 } else if (name == S_FOREIGN_IS_NAMESPACE) {
323 foreignIsNamespace = (value == S_TRUE);
324 } else if (name == S_PRIMITIVE_ALIAS) {
325 primitiveAliases.append(value);
326 } else if (name == S_ROOT) {
327 isRootClass = (value == S_TRUE);
328 } else if (name == S_HAS_CUSTOM_PARSER) {
329 if (value == S_TRUE)
330 hasCustomParser = true;
331 } else if (name == S_DEFERRED_PROPERTY_NAMES) {
332 deferredNames = split(value, QLatin1StringView(","));
333 } else if (name == S_IMMEDIATE_PROPERTY_NAMES) {
334 immediateNames = split(value, QLatin1StringView(","));
335 }
336 }
337
338 if (addedInRevision.isValid() && !elementNames.isEmpty())
339 revisions.append(addedInRevision);
340
341 // If the local type is a namespace the result can only be a namespace,
342 // no matter what the foreign type is.
343 const bool isNamespace = foreignIsNamespace || classDef.kind() == MetaType::Kind::Namespace;
344
345 MetaType resolved = classDef;
346 if (!foreignTypeName.isEmpty()) {
347 // We can re-use a type with own QML.* macros as target of QML.Foreign
348 if (const FoundType found = findType(foreign, types, foreignTypeName, namespaces)) {
349 resolved = found.select(classDef, "Foreign");
350
351 // Default properties and enum classes are always local.
352 defaultProp = {};
353 enforcesScopedEnums = false;
354
355 // Foreign type can have a default property or an attached type,
356 // or RegisterEnumClassesUnscoped classinfo.
357 for (const ClassInfo &obj : resolved.classInfos()) {
358 const QAnyStringView foreignName = obj.name;
359 const QAnyStringView foreignValue = obj.value;
360 if (defaultProp.isEmpty() && foreignName == S_DEFAULT_PROPERTY) {
361 defaultProp = foreignValue;
362 } else if (parentProp.isEmpty() && foreignName == S_PARENT_PROPERTY) {
363 parentProp = foreignValue;
364 } else if (foreignName == S_REGISTER_ENUM_CLASSES_UNSCOPED) {
365 handleRegisterEnumClassesUnscoped(resolved, foreignValue);
366 } else if (foreignName == S_ATTACHED) {
367 if (const FoundType attached = collectRelated(
368 foreignValue, types, foreign, defaultRevision, namespaces)) {
369 attachedType = attached.select(resolved, "Attached").qualifiedClassName();
370 }
371 } else if (foreignName == S_EXTENDED) {
372 if (const FoundType extension = collectRelated(
373 foreignValue, types, foreign, defaultRevision, namespaces)) {
374 nativeExtensionType = extension.native.qualifiedClassName();
375 javaScriptExtensionType = extension.javaScript.qualifiedClassName();
376 }
377 } else if (foreignName == S_EXTENSION_IS_JAVA_SCRIPT) {
378 if (foreignValue == S_TRUE)
379 extensionIsJavaScript = true;
380 } else if (foreignName == S_EXTENSION_IS_NAMESPACE) {
381 if (foreignValue == S_TRUE)
382 extensionIsNamespace = true;
383 } else if (foreignName == S_SEQUENCE) {
384 if (const FoundType element = collectRelated(
385 foreignValue, types, foreign, defaultRevision, namespaces)) {
386 sequenceValueType
387 = element.select(resolved, "Sequence value").qualifiedClassName();
388 }
389 }
390 }
391 if (mode == TopLevel) {
392 resolvedFile = resolved.inputFile();
394 }
395 } else {
396 className = foreignTypeName;
397 resolved = MetaType();
398 }
399 }
400
401 if (!resolved.isEmpty()) {
402 if (mode == RelatedType || !elementNames.isEmpty()) {
403 collectExtraVersions(resolved.properties(), revisions);
404 collectExtraVersions(resolved.methods(), revisions);
405 collectExtraVersions(resolved.sigs(), revisions);
406 }
407
408 collectSuperClasses(resolved, types, foreign, mode, defaultRevision);
409 }
410
411 if (mode != TopLevel)
412 return;
413
414 if (!resolved.isEmpty())
415 collectInterfaces(resolved);
416
417 if (!addedInRevision.isValid()) {
418 addedInRevision = defaultRevision;
419 }
420 if (addedInRevision <= defaultRevision
421 && (!removedInRevision.isValid() || defaultRevision < removedInRevision)) {
422 revisions.append(defaultRevision);
423 }
424
425 std::sort(revisions.begin(), revisions.end());
426 const auto end = std::unique(revisions.begin(), revisions.end());
427 revisions.erase(QList<QTypeRevision>::const_iterator(end), revisions.constEnd());
428
429 resolvedClass = resolved;
430 if (className.isEmpty() && !resolved.isEmpty())
431 className = resolved.qualifiedClassName();
432
433 if (!sequenceValueType.isEmpty()) {
434 isCreatable = false;
435 accessSemantics = DotQmltypes::S_SEQUENCE;
436 } else if (isNamespace) {
437 isCreatable = false;
438 accessSemantics = DotQmltypes::S_NONE;
439 } else if (resolved.kind() == MetaType::Kind::Object) {
440 accessSemantics = DotQmltypes::S_REFERENCE;
441 } else {
442 isCreatable = isConstructible;
443
444 if (resolved.isEmpty()) {
445 if (elementNames.isEmpty()) {
446 // If no resolved, we generally assume it's a value type defined by the
447 // foreign/extended trick.
448 accessSemantics = DotQmltypes::S_VALUE;
449 }
450
451 for (auto elementName = elementNames.begin(); elementName != elementNames.end();) {
452 if (elementName->isEmpty() || elementName->front().isLower()) {
453 // If no resolved, we generally assume it's a value type defined by the
454 // foreign/extended trick.
455 accessSemantics = DotQmltypes::S_VALUE;
456 ++elementName;
457 } else {
458 // Objects and namespaces always have metaobjects and therefore classDefs.
459 // However, we may not be able to resolve the metaobject at compile time. See
460 // the "Invisible" test case. In that case, we must not assume anything about
461 // access semantics.
462
463 warning(classDef)
464 << "Refusing to generate non-lowercase name"
465 << *elementName << "for unknown foreign type";
466 elementName = elementNames.erase(elementName);
467
468 if (elementNames.isEmpty()) {
469 // Make it completely inaccessible.
470 // We cannot get enums from anonymous types after all.
471 accessSemantics = DotQmltypes::S_NONE;
472 }
473 }
474 }
475 } else if (resolved.kind() == MetaType::Kind::Gadget) {
476 accessSemantics = DotQmltypes::S_VALUE;
477 } else {
478 accessSemantics = DotQmltypes::S_NONE;
479 }
480 }
481}
482
484 QAnyStringView related, const QList<MetaType> &types, const QList<MetaType> &foreign,
485 QTypeRevision defaultRevision, const QList<QAnyStringView> &namespaces)
486{
487 if (FoundType other = findType(types, foreign, related, namespaces)) {
488 if (!other.native.isEmpty())
489 collect(other.native, types, foreign, RelatedType, defaultRevision);
490 if (!other.javaScript.isEmpty())
491 collect(other.javaScript, types, foreign, RelatedType, defaultRevision);
492 return other;
493 }
494 return FoundType();
495}
496
498 bool operator()(const UsingDeclaration &a, QAnyStringView b) const
499 {
500 return a.alias < b;
501 }
502
503 bool operator()(QAnyStringView a, const UsingDeclaration &b) const
504 {
505 return a < b.alias;
506 }
507};
508
510 QAnyStringView alias, const QList<UsingDeclaration> &usingDeclarations)
511 : type(alias)
512{
513 handleVoid();
514 if (type.isEmpty())
515 return;
516
517 handleList();
518
519 if (!isList) {
520 handlePointer();
521 handleConst();
522 }
523
524 while (true) {
525 const auto usingDeclaration = std::equal_range(
526 usingDeclarations.begin(), usingDeclarations.end(), type, UsingCompare());
527 if (usingDeclaration.first == usingDeclaration.second)
528 break;
529
530 type = usingDeclaration.first->original;
531 handleVoid();
532 if (type.isEmpty())
533 return;
534
535 if (isPointer) {
536 handleConst();
537 continue;
538 }
539
540 if (!isList) {
541 handleList();
542 if (!isList) {
543 handlePointer();
544 handleConst();
545 }
546 }
547 }
548}
549
550void ResolvedTypeAlias::handleVoid()
551{
552 if (!isPointer && type == "void")
553 type = "";
554}
555
556void ResolvedTypeAlias::handleList()
557{
558 for (QLatin1StringView list : {"QQmlListProperty<"_L1, "QList<"_L1}) {
559 if (!startsWith(type, list) || type.back() != '>'_L1)
560 continue;
561
562 const int listSize = list.size();
563 const QAnyStringView elementType = trimmed(type.mid(listSize, type.size() - listSize - 1));
564
565 // QQmlListProperty internally constructs the pointer. Passing an explicit '*' will
566 // produce double pointers. QList is only for value types. We can't handle QLists
567 // of pointers (unless specially registered, but then they're not isList).
568 if (elementType.back() == '*'_L1)
569 continue;
570
571 isList = true;
572 type = elementType;
573 return;
574 }
575}
576
577void ResolvedTypeAlias::handlePointer()
578{
579 if (type.back() == '*'_L1) {
580 isPointer = true;
581 type = type.chopped(1);
582 while (type.back().isSpace())
583 type.chop(1);
584 }
585}
586
587void ResolvedTypeAlias::handleConst()
588{
589 if (startsWith(type, "const "_L1)) {
590 isConstant = true;
591 type = type.sliced(strlen("const "));
592 }
593}
594
595QT_END_NAMESPACE
MetaTypePrivate::Kind Kind
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