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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtdeclarative/src/qmltyperegistrar/qqmltypesclassdescription.cpp