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