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