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