1// Copyright (C) 2021 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 "qqmljstyperesolver_p.h"
5
6#include "qqmljsimporter_p.h"
7#include "qqmljsimportvisitor_p.h"
8#include "qqmljslogger_p.h"
9#include "qqmljsutils_p.h"
10#include <private/qv4value_p.h>
11
12#include <private/qduplicatetracker_p.h>
13
14#include <QtCore/qloggingcategory.h>
15
16QT_BEGIN_NAMESPACE
17
18using namespace Qt::StringLiterals;
19
20Q_LOGGING_CATEGORY(lcTypeResolver, "qt.qml.compiler.typeresolver", QtInfoMsg);
21
22static inline void assertExtension(const QQmlJSScope::ConstPtr &type, QLatin1String extension)
23{
24 Q_ASSERT(type);
25 Q_ASSERT(type->extensionType().scope->internalName() == extension);
26 Q_ASSERT(type->extensionIsJavaScript());
27}
28
29QQmlJSTypeResolver::QQmlJSTypeResolver(QQmlJSImporter *importer)
30 : m_imports(importer->builtinInternalNames()),
31 m_trackedTypes(std::make_unique<QHash<QQmlJSScope::ConstPtr, TrackedType>>())
32{
33 const QQmlJSImporter::ImportedTypes &builtinTypes = m_imports;
34
35 m_voidType = builtinTypes.type(name: u"void"_s).scope;
36 assertExtension(type: m_voidType, extension: "undefined"_L1);
37
38 m_nullType = builtinTypes.type(name: u"std::nullptr_t"_s).scope;
39 Q_ASSERT(m_nullType);
40
41 m_realType = builtinTypes.type(name: u"double"_s).scope;
42 assertExtension(type: m_realType, extension: "Number"_L1);
43
44 m_floatType = builtinTypes.type(name: u"float"_s).scope;
45 assertExtension(type: m_floatType, extension: "Number"_L1);
46
47 m_int8Type = builtinTypes.type(name: u"qint8"_s).scope;
48 assertExtension(type: m_int8Type, extension: "Number"_L1);
49
50 m_uint8Type = builtinTypes.type(name: u"quint8"_s).scope;
51 assertExtension(type: m_uint8Type, extension: "Number"_L1);
52
53 m_int16Type = builtinTypes.type(name: u"short"_s).scope;
54 assertExtension(type: m_int16Type, extension: "Number"_L1);
55
56 m_uint16Type = builtinTypes.type(name: u"ushort"_s).scope;
57 assertExtension(type: m_uint16Type, extension: "Number"_L1);
58
59 m_int32Type = builtinTypes.type(name: u"int"_s).scope;
60 assertExtension(type: m_int32Type, extension: "Number"_L1);
61
62 m_uint32Type = builtinTypes.type(name: u"uint"_s).scope;
63 assertExtension(type: m_uint32Type, extension: "Number"_L1);
64
65 m_int64Type = builtinTypes.type(name: u"qlonglong"_s).scope;
66 Q_ASSERT(m_int64Type);
67
68 m_uint64Type = builtinTypes.type(name: u"qulonglong"_s).scope;
69 Q_ASSERT(m_uint64Type);
70
71 m_sizeType = builtinTypes.type(name: u"qsizetype"_s).scope;
72 assertExtension(type: m_sizeType, extension: "Number"_L1);
73
74 // qsizetype is either a 32bit or a 64bit signed integer. We don't want to special-case it.
75 Q_ASSERT(m_sizeType == m_int32Type || m_sizeType == m_int64Type);
76
77 m_boolType = builtinTypes.type(name: u"bool"_s).scope;
78 assertExtension(type: m_boolType, extension: "Boolean"_L1);
79
80 m_stringType = builtinTypes.type(name: u"QString"_s).scope;
81 assertExtension(type: m_stringType, extension: "String"_L1);
82
83 m_stringListType = builtinTypes.type(name: u"QStringList"_s).scope;
84 assertExtension(type: m_stringListType, extension: "Array"_L1);
85
86 m_byteArrayType = builtinTypes.type(name: u"QByteArray"_s).scope;
87 assertExtension(type: m_byteArrayType, extension: "ArrayBuffer"_L1);
88
89 m_urlType = builtinTypes.type(name: u"QUrl"_s).scope;
90 assertExtension(type: m_urlType, extension: "URL"_L1);
91
92 m_dateTimeType = builtinTypes.type(name: u"QDateTime"_s).scope;
93 assertExtension(type: m_dateTimeType, extension: "Date"_L1);
94
95 m_dateType = builtinTypes.type(name: u"QDate"_s).scope;
96 Q_ASSERT(m_dateType);
97
98 m_timeType = builtinTypes.type(name: u"QTime"_s).scope;
99 Q_ASSERT(m_timeType);
100
101 m_variantListType = builtinTypes.type(name: u"QVariantList"_s).scope;
102 assertExtension(type: m_variantListType, extension: "Array"_L1);
103
104 m_variantMapType = builtinTypes.type(name: u"QVariantMap"_s).scope;
105 Q_ASSERT(m_variantMapType);
106 m_varType = builtinTypes.type(name: u"QVariant"_s).scope;
107 Q_ASSERT(m_varType);
108
109 m_jsValueType = builtinTypes.type(name: u"QJSValue"_s).scope;
110 Q_ASSERT(m_jsValueType);
111
112 m_qObjectType = builtinTypes.type(name: u"QObject"_s).scope;
113 assertExtension(type: m_qObjectType, extension: "Object"_L1);
114
115 m_qObjectListType = builtinTypes.type(name: u"QObjectList"_s).scope;
116 assertExtension(type: m_qObjectListType, extension: "Array"_L1);
117
118 m_qQmlScriptStringType = builtinTypes.type(name: u"QQmlScriptString"_s).scope;
119 Q_ASSERT(m_qQmlScriptStringType);
120
121 m_functionType = builtinTypes.type(name: u"function"_s).scope;
122 Q_ASSERT(m_functionType);
123
124 m_numberPrototype = builtinTypes.type(name: u"NumberPrototype"_s).scope;
125 Q_ASSERT(m_numberPrototype);
126
127 m_arrayPrototype = builtinTypes.type(name: u"ArrayPrototype"_s).scope;
128 Q_ASSERT(m_arrayPrototype);
129
130 m_listPropertyType = m_qObjectType->listType();
131 Q_ASSERT(m_listPropertyType->internalName() == u"QQmlListProperty<QObject>"_s);
132 Q_ASSERT(m_listPropertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence);
133 Q_ASSERT(m_listPropertyType->valueTypeName() == u"QObject"_s);
134 assertExtension(type: m_listPropertyType, extension: "Array"_L1);
135
136 QQmlJSScope::Ptr emptyType = QQmlJSScope::create();
137 emptyType->setAccessSemantics(QQmlJSScope::AccessSemantics::None);
138 m_emptyType = emptyType;
139
140 QQmlJSScope::Ptr jsPrimitiveType = QQmlJSScope::create();
141 jsPrimitiveType->setInternalName(u"QJSPrimitiveValue"_s);
142 jsPrimitiveType->setFilePath(u"qjsprimitivevalue.h"_s);
143 jsPrimitiveType->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
144 m_jsPrimitiveType = jsPrimitiveType;
145
146 QQmlJSScope::Ptr metaObjectType = QQmlJSScope::create();
147 metaObjectType->setInternalName(u"const QMetaObject"_s);
148 metaObjectType->setFilePath(u"qmetaobject.h"_s);
149 metaObjectType->setAccessSemantics(QQmlJSScope::AccessSemantics::Reference);
150 m_metaObjectType = metaObjectType;
151
152 m_jsGlobalObject = importer->jsGlobalObject();
153
154 QQmlJSScope::Ptr forInIteratorPtr = QQmlJSScope::create();
155 forInIteratorPtr->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
156 forInIteratorPtr->setFilePath(u"qjslist.h"_s);
157 forInIteratorPtr->setInternalName(u"QJSListForInIterator::Ptr"_s);
158 m_forInIteratorPtr = forInIteratorPtr;
159
160 QQmlJSScope::Ptr forOfIteratorPtr = QQmlJSScope::create();
161 forOfIteratorPtr->setAccessSemantics(QQmlJSScope::AccessSemantics::Value);
162 forOfIteratorPtr->setFilePath(u"qjslist.h"_s);
163 forOfIteratorPtr->setInternalName(u"QJSListForOfIterator::Ptr"_s);
164 m_forOfIteratorPtr = forOfIteratorPtr;
165}
166
167/*!
168 \internal
169
170 Initializes the type resolver. As part of that initialization, makes \a
171 visitor traverse the program when given.
172*/
173void QQmlJSTypeResolver::init(QQmlJSImportVisitor *visitor, QQmlJS::AST::Node *program)
174{
175 m_logger = visitor->logger();
176
177 m_objectsById.clear();
178 m_objectsByLocation.clear();
179 m_imports.clear();
180 m_signalHandlers.clear();
181
182 if (program)
183 program->accept(visitor);
184
185 m_objectsById = visitor->addressableScopes();
186 m_objectsByLocation = visitor->scopesBylocation();
187 m_signalHandlers = visitor->signalHandlers();
188 m_imports = visitor->imports();
189 m_seenModuleQualifiers = visitor->seenModuleQualifiers();
190}
191
192QQmlJSScope::ConstPtr QQmlJSTypeResolver::mathObject() const
193{
194 return jsGlobalObject()->property(name: u"Math"_s).type();
195}
196
197QQmlJSScope::ConstPtr QQmlJSTypeResolver::consoleObject() const
198{
199 return jsGlobalObject()->property(name: u"console"_s).type();
200}
201
202QQmlJSScope::ConstPtr
203QQmlJSTypeResolver::scopeForLocation(const QV4::CompiledData::Location &location) const
204{
205 // #if required for standalone DOM compilation against Qt 6.2
206 qCDebug(lcTypeResolver()).nospace()
207 << "looking for object at " << location.line() << ':' << location.column();
208 return m_objectsByLocation[location];
209}
210
211QQmlJSScope::ConstPtr QQmlJSTypeResolver::typeFromAST(QQmlJS::AST::Type *type) const
212{
213 const QString typeId = QmlIR::IRBuilder::asString(node: type->typeId);
214 if (!type->typeArgument)
215 return m_imports.type(name: typeId).scope;
216 if (typeId == u"list"_s) {
217 if (const QQmlJSScope::ConstPtr typeArgument = typeForName(name: type->typeArgument->toString()))
218 return typeArgument->listType();
219 }
220 return QQmlJSScope::ConstPtr();
221}
222
223QQmlJSScope::ConstPtr QQmlJSTypeResolver::typeForConst(QV4::ReturnedValue rv) const
224{
225 QV4::Value value = QV4::Value::fromReturnedValue(val: rv);
226 if (value.isUndefined())
227 return voidType();
228
229 if (value.isInt32())
230 return int32Type();
231
232 if (value.isBoolean())
233 return boolType();
234
235 if (value.isDouble())
236 return realType();
237
238 if (value.isNull())
239 return nullType();
240
241 if (value.isEmpty())
242 return emptyType();
243
244 return {};
245}
246
247QQmlJSRegisterContent
248QQmlJSTypeResolver::typeForBinaryOperation(QSOperator::Op oper, const QQmlJSRegisterContent &left,
249 const QQmlJSRegisterContent &right) const
250{
251 Q_ASSERT(left.isValid());
252 Q_ASSERT(right.isValid());
253
254 switch (oper) {
255 case QSOperator::Op::Equal:
256 case QSOperator::Op::NotEqual:
257 case QSOperator::Op::StrictEqual:
258 case QSOperator::Op::StrictNotEqual:
259 case QSOperator::Op::Lt:
260 case QSOperator::Op::Gt:
261 case QSOperator::Op::Ge:
262 case QSOperator::Op::In:
263 case QSOperator::Op::Le:
264 return globalType(type: boolType());
265 case QSOperator::Op::BitAnd:
266 case QSOperator::Op::BitOr:
267 case QSOperator::Op::BitXor:
268 case QSOperator::Op::LShift:
269 case QSOperator::Op::RShift:
270 return builtinType(type: int32Type());
271 case QSOperator::Op::URShift:
272 return builtinType(type: uint32Type());
273 case QSOperator::Op::Add: {
274 const auto leftContents = containedType(container: left);
275 const auto rightContents = containedType(container: right);
276 if (equals(a: leftContents, b: stringType()) || equals(a: rightContents, b: stringType()))
277 return builtinType(type: stringType());
278
279 const QQmlJSScope::ConstPtr result = merge(a: leftContents, b: rightContents);
280 if (equals(a: result, b: boolType()))
281 return builtinType(type: int32Type());
282 if (isNumeric(type: result))
283 return builtinType(type: realType());
284
285 return builtinType(type: jsPrimitiveType());
286 }
287 case QSOperator::Op::Sub:
288 case QSOperator::Op::Mul:
289 case QSOperator::Op::Exp: {
290 const QQmlJSScope::ConstPtr result = merge(a: containedType(container: left), b: containedType(container: right));
291 return builtinType(type: equals(a: result, b: boolType()) ? int32Type() : realType());
292 }
293 case QSOperator::Op::Div:
294 case QSOperator::Op::Mod:
295 return builtinType(type: realType());
296 case QSOperator::Op::As:
297 return right;
298 default:
299 break;
300 }
301
302 return merge(a: left, b: right);
303}
304
305QQmlJSRegisterContent QQmlJSTypeResolver::typeForArithmeticUnaryOperation(
306 UnaryOperator op, const QQmlJSRegisterContent &operand) const
307{
308 switch (op) {
309 case UnaryOperator::Not:
310 return builtinType(type: boolType());
311 case UnaryOperator::Complement:
312 return builtinType(type: int32Type());
313 case UnaryOperator::Plus:
314 if (isIntegral(type: operand))
315 return operand;
316 Q_FALLTHROUGH();
317 default:
318 if (equals(a: containedType(container: operand), b: boolType()))
319 return builtinType(type: int32Type());
320 break;
321 }
322
323 return builtinType(type: realType());
324}
325
326bool QQmlJSTypeResolver::isPrimitive(const QQmlJSRegisterContent &type) const
327{
328 return isPrimitive(type: containedType(container: type));
329}
330
331bool QQmlJSTypeResolver::isNumeric(const QQmlJSRegisterContent &type) const
332{
333 return isNumeric(type: containedType(container: type));
334}
335
336bool QQmlJSTypeResolver::isIntegral(const QQmlJSRegisterContent &type) const
337{
338 return isIntegral(type: containedType(container: type));
339}
340
341bool QQmlJSTypeResolver::isIntegral(const QQmlJSScope::ConstPtr &type) const
342{
343 return isSignedInteger(type) || isUnsignedInteger(type);
344}
345
346bool QQmlJSTypeResolver::isPrimitive(const QQmlJSScope::ConstPtr &type) const
347{
348 return (isNumeric(type) && !equals(a: type, b: m_int64Type) && !equals(a: type, b: m_uint64Type))
349 || equals(a: type, b: m_boolType) || equals(a: type, b: m_voidType) || equals(a: type, b: m_nullType)
350 || equals(a: type, b: m_stringType) || equals(a: type, b: m_jsPrimitiveType);
351}
352
353bool QQmlJSTypeResolver::isNumeric(const QQmlJSScope::ConstPtr &type) const
354{
355 return QQmlJSUtils::searchBaseAndExtensionTypes(
356 type, check: [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) {
357 if (mode == QQmlJSScope::ExtensionNamespace)
358 return false;
359 return equals(a: scope, b: m_numberPrototype);
360 });
361}
362
363bool QQmlJSTypeResolver::isSignedInteger(const QQmlJSScope::ConstPtr &type) const
364{
365 return equals(a: type, b: m_int8Type)
366 || equals(a: type, b: m_int16Type)
367 || equals(a: type, b: m_int32Type)
368 || equals(a: type, b: m_int64Type);
369}
370
371bool QQmlJSTypeResolver::isUnsignedInteger(const QQmlJSScope::ConstPtr &type) const
372{
373 return equals(a: type, b: m_uint8Type)
374 || equals(a: type, b: m_uint16Type)
375 || equals(a: type, b: m_uint32Type)
376 || equals(a: type, b: m_uint64Type);
377}
378
379bool QQmlJSTypeResolver::isNativeArrayIndex(const QQmlJSScope::ConstPtr &type) const
380{
381 return (equals(a: type, b: m_uint8Type)
382 || equals(a: type, b: m_int8Type)
383 || equals(a: type, b: m_uint16Type)
384 || equals(a: type, b: m_int16Type)
385 || equals(a: type, b: m_uint32Type)
386 || equals(a: type, b: m_int32Type));
387}
388
389QQmlJSScope::ConstPtr
390QQmlJSTypeResolver::containedType(const QQmlJSRegisterContent &container) const
391{
392 if (container.isType())
393 return container.type();
394 if (container.isProperty())
395 return container.property().type();
396 if (container.isEnumeration())
397 return container.enumeration().type();
398 if (container.isMethod())
399 return container.storedType(); // Methods can only be stored in QJSValue.
400 if (container.isImportNamespace()) {
401 switch (container.variant()) {
402 case QQmlJSRegisterContent::ScopeModulePrefix:
403 return container.storedType(); // We don't store scope module prefixes
404 case QQmlJSRegisterContent::ObjectModulePrefix:
405 return container.scopeType(); // We need to pass the original object through.
406 default:
407 Q_UNREACHABLE();
408 }
409 }
410 if (container.isConversion())
411 return container.conversionResult();
412
413 Q_UNREACHABLE_RETURN({});
414}
415
416QQmlJSScope::ConstPtr QQmlJSTypeResolver::trackedType(const QQmlJSScope::ConstPtr &type) const
417{
418 if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
419 return type;
420
421 // If origin is in fact an already tracked type, track the original of that one instead.
422 const auto it = m_trackedTypes->find(key: type);
423 QQmlJSScope::ConstPtr orig = (it == m_trackedTypes->end()) ? type : it->original;
424
425 QQmlJSScope::Ptr clone = QQmlJSScope::clone(origin: orig);
426 m_trackedTypes->insert(key: clone, value: { .original: std::move(orig), .replacement: QQmlJSScope::ConstPtr(), .clone: clone });
427 return clone;
428}
429
430QQmlJSRegisterContent QQmlJSTypeResolver::transformed(
431 const QQmlJSRegisterContent &origin,
432 QQmlJSScope::ConstPtr (QQmlJSTypeResolver::*op)(const QQmlJSScope::ConstPtr &) const) const
433{
434 if (origin.isType()) {
435 return QQmlJSRegisterContent::create(
436 storedType: (this->*op)(origin.storedType()), type: (this->*op)(origin.type()),
437 resultLookupIndex: origin.resultLookupIndex(), variant: origin.variant(), scope: (this->*op)(origin.scopeType()));
438 }
439
440 if (origin.isProperty()) {
441 QQmlJSMetaProperty prop = origin.property();
442 prop.setType((this->*op)(prop.type()));
443 return QQmlJSRegisterContent::create(
444 storedType: (this->*op)(origin.storedType()), property: prop, baseLookupIndex: origin.baseLookupIndex(),
445 resultLookupIndex: origin.resultLookupIndex(), variant: origin.variant(), scope: (this->*op)(origin.scopeType()));
446 }
447
448 if (origin.isEnumeration()) {
449 QQmlJSMetaEnum enumeration = origin.enumeration();
450 enumeration.setType((this->*op)(enumeration.type()));
451 return QQmlJSRegisterContent::create(
452 storedType: (this->*op)(origin.storedType()), enumeration, enumMember: origin.enumMember(),
453 variant: origin.variant(), scope: (this->*op)(origin.scopeType()));
454 }
455
456 if (origin.isMethod()) {
457 return QQmlJSRegisterContent::create(
458 storedType: (this->*op)(origin.storedType()), methods: origin.method(), variant: origin.variant(),
459 scope: (this->*op)(origin.scopeType()));
460 }
461
462 if (origin.isImportNamespace()) {
463 return QQmlJSRegisterContent::create(
464 storedType: (this->*op)(origin.storedType()), importNamespaceStringId: origin.importNamespace(),
465 variant: origin.variant(), scope: (this->*op)(origin.scopeType()));
466 }
467
468 if (origin.isConversion()) {
469 // When retrieving the originals we want a deep retrieval.
470 // When tracking a new type, we don't want to re-track its originals, though.
471
472 const QList<QQmlJSScope::ConstPtr> origins = origin.conversionOrigins();
473 QList<QQmlJSScope::ConstPtr> transformedOrigins;
474 if (op == &QQmlJSTypeResolver::trackedType) {
475 transformedOrigins = origins;
476 } else {
477 transformedOrigins.reserve(asize: origins.length());
478 for (const QQmlJSScope::ConstPtr &origin: origins)
479 transformedOrigins.append(t: (this->*op)(origin));
480 }
481
482 return QQmlJSRegisterContent::create(
483 storedType: (this->*op)(origin.storedType()),
484 origins: transformedOrigins,
485 conversion: (this->*op)(origin.conversionResult()),
486 conversionScope: (this->*op)(origin.conversionResultScope()),
487 variant: origin.variant(), scope: (this->*op)(origin.scopeType()));
488 }
489
490 Q_UNREACHABLE_RETURN({});
491}
492
493QQmlJSRegisterContent QQmlJSTypeResolver::registerContentForName(
494 const QString &name, const QQmlJSScope::ConstPtr &scopeType,
495 bool hasObjectModulePrefix) const
496{
497 QQmlJSScope::ConstPtr type = typeForName(name);
498 if (!type)
499 return QQmlJSRegisterContent();
500
501 if (type->isSingleton()) {
502 return QQmlJSRegisterContent::create(
503 storedType: storedType(type), type, resultLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex,
504 variant: QQmlJSRegisterContent::Singleton, scope: scopeType);
505 }
506
507 if (type->isScript()) {
508 return QQmlJSRegisterContent::create(
509 storedType: storedType(type), type, resultLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex,
510 variant: QQmlJSRegisterContent::Script, scope: scopeType);
511 }
512
513 if (const auto attached = type->attachedType()) {
514 if (!genericType(type: attached)) {
515 m_logger->log(message: u"Cannot resolve generic base of attached %1"_s.arg(
516 a: attached->internalName()),
517 id: qmlCompiler, srcLocation: attached->sourceLocation());
518 return {};
519 } else if (type->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
520 m_logger->log(message: u"Cannot retrieve attached object for non-reference type %1"_s.arg(
521 a: type->internalName()),
522 id: qmlCompiler, srcLocation: type->sourceLocation());
523 return {};
524 } else {
525 // We don't know yet whether we need the attached or the plain object. In direct
526 // mode, we will figure this out using the scope type and access any enums of the
527 // plain type directly. In indirect mode, we can use enum lookups.
528 return QQmlJSRegisterContent::create(
529 storedType: storedType(type: attached), type: attached, resultLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex,
530 variant: hasObjectModulePrefix
531 ? QQmlJSRegisterContent::ObjectAttached
532 : QQmlJSRegisterContent::ScopeAttached, scope: type);
533 }
534 }
535
536 switch (type->accessSemantics()) {
537 case QQmlJSScope::AccessSemantics::None:
538 case QQmlJSScope::AccessSemantics::Reference:
539 // A plain reference to a non-singleton, non-attached type.
540 // We may still need the plain type reference for enum lookups,
541 // Store it as QMetaObject.
542 // This only works with namespaces and object types.
543 return QQmlJSRegisterContent::create(
544 storedType: metaObjectType(), type: metaObjectType(), resultLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex,
545 variant: QQmlJSRegisterContent::MetaType, scope: type);
546 case QQmlJSScope::AccessSemantics::Sequence:
547 case QQmlJSScope::AccessSemantics::Value:
548 if (canAddressValueTypes()) {
549 return QQmlJSRegisterContent::create(
550 storedType: metaObjectType(), type: metaObjectType(), resultLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex,
551 variant: QQmlJSRegisterContent::MetaType, scope: type);
552 }
553 // Else this is not actually a type reference. You cannot get the metaobject
554 // of a value type in QML and sequences don't even have metaobjects.
555 break;
556 }
557
558 return QQmlJSRegisterContent();
559}
560
561QQmlJSRegisterContent QQmlJSTypeResolver::original(const QQmlJSRegisterContent &type) const
562{
563 return transformed(origin: type, op: &QQmlJSTypeResolver::originalType);
564}
565
566QQmlJSRegisterContent QQmlJSTypeResolver::tracked(const QQmlJSRegisterContent &type) const
567{
568 return transformed(origin: type, op: &QQmlJSTypeResolver::trackedType);
569}
570
571QQmlJSScope::ConstPtr QQmlJSTypeResolver::trackedContainedType(
572 const QQmlJSRegisterContent &container) const
573{
574 const QQmlJSScope::ConstPtr type = containedType(container);
575 return m_trackedTypes->contains(key: type) ? type : QQmlJSScope::ConstPtr();
576}
577
578QQmlJSScope::ConstPtr QQmlJSTypeResolver::originalContainedType(
579 const QQmlJSRegisterContent &container) const
580{
581 return originalType(type: containedType(container));
582}
583
584bool QQmlJSTypeResolver::adjustTrackedType(
585 const QQmlJSScope::ConstPtr &tracked, const QQmlJSScope::ConstPtr &conversion) const
586{
587 if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
588 return true;
589
590 const auto it = m_trackedTypes->find(key: tracked);
591 Q_ASSERT(it != m_trackedTypes->end());
592
593 // If we cannot convert to the new type without the help of e.g. lookupResultMetaType(),
594 // we better not change the type.
595 if (!canPrimitivelyConvertFromTo(from: tracked, to: conversion)
596 && !canPopulate(type: conversion, argument: tracked, isExtension: nullptr)
597 && !selectConstructor(type: conversion, argument: tracked, isExtension: nullptr).isValid()) {
598 return false;
599 }
600
601 it->replacement = comparableType(type: conversion);
602 *it->clone = std::move(*QQmlJSScope::clone(origin: conversion));
603 return true;
604}
605
606bool QQmlJSTypeResolver::adjustTrackedType(
607 const QQmlJSScope::ConstPtr &tracked, const QList<QQmlJSScope::ConstPtr> &conversions) const
608{
609 if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
610 return true;
611
612 const auto it = m_trackedTypes->find(key: tracked);
613 Q_ASSERT(it != m_trackedTypes->end());
614 QQmlJSScope::Ptr mutableTracked = it->clone;
615 QQmlJSScope::ConstPtr result;
616 for (const QQmlJSScope::ConstPtr &type : conversions)
617 result = merge(a: type, b: result);
618
619 // If we cannot convert to the new type without the help of e.g. lookupResultMetaType(),
620 // we better not change the type.
621 if (!canPrimitivelyConvertFromTo(from: tracked, to: result)
622 && !canPopulate(type: result, argument: tracked, isExtension: nullptr)
623 && !selectConstructor(type: result, argument: tracked, isExtension: nullptr).isValid()) {
624 return false;
625 }
626
627 it->replacement = comparableType(type: result);
628 *mutableTracked = std::move(*QQmlJSScope::clone(origin: result));
629 return true;
630}
631
632void QQmlJSTypeResolver::adjustOriginalType(
633 const QQmlJSScope::ConstPtr &tracked, const QQmlJSScope::ConstPtr &conversion) const
634{
635 if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
636 return;
637
638 const auto it = m_trackedTypes->find(key: tracked);
639 Q_ASSERT(it != m_trackedTypes->end());
640
641 it->original = conversion;
642 *it->clone = std::move(*QQmlJSScope::clone(origin: conversion));
643}
644
645void QQmlJSTypeResolver::generalizeType(const QQmlJSScope::ConstPtr &type) const
646{
647 if (m_cloneMode == QQmlJSTypeResolver::DoNotCloneTypes)
648 return;
649
650 const auto it = m_trackedTypes->find(key: type);
651 Q_ASSERT(it != m_trackedTypes->end());
652 *it->clone = std::move(*QQmlJSScope::clone(origin: genericType(type)));
653 if (it->replacement)
654 it->replacement = genericType(type: it->replacement);
655 it->original = genericType(type: it->original);
656}
657
658QString QQmlJSTypeResolver::containedTypeName(const QQmlJSRegisterContent &container,
659 bool useFancyName) const
660{
661 QQmlJSScope::ConstPtr type;
662
663 // Use the type proper instead of the attached type
664 switch (container.variant()) {
665 case QQmlJSRegisterContent::ScopeAttached:
666 case QQmlJSRegisterContent::MetaType:
667 type = container.scopeType();
668 break;
669 default:
670 type = containedType(container);
671 break;
672 }
673
674 QString typeName = type->internalName().isEmpty() ? type->baseTypeName() : type->internalName();
675
676 if (useFancyName)
677 return QQmlJSScope::prettyName(name: typeName);
678
679 return typeName;
680}
681
682bool QQmlJSTypeResolver::canConvertFromTo(const QQmlJSScope::ConstPtr &from,
683 const QQmlJSScope::ConstPtr &to) const
684{
685 if (canPrimitivelyConvertFromTo(from, to)
686 || canPopulate(type: to, argument: from, isExtension: nullptr)
687 || selectConstructor(type: to, argument: from, isExtension: nullptr).isValid()) {
688 return true;
689 }
690
691 // ### need a generic solution for custom cpp types:
692 // if (from->m_hasBoolOverload && equals(to, boolType))
693 // return true;
694
695 // All of these types have QString conversions that require a certain format
696 // TODO: Actually verify these strings or deprecate them.
697 // Some of those type are builtins or should be builtins. We should add code for them
698 // in QQmlJSCodeGenerator::conversion().
699 if (equals(a: from, b: m_stringType) && !to.isNull()) {
700 const QString toTypeName = to->internalName();
701 if (toTypeName == u"QPoint"_s || toTypeName == u"QPointF"_s
702 || toTypeName == u"QSize"_s || toTypeName == u"QSizeF"_s
703 || toTypeName == u"QRect"_s || toTypeName == u"QRectF"_s) {
704 return true;
705 }
706 }
707
708 return false;
709}
710
711bool QQmlJSTypeResolver::canConvertFromTo(const QQmlJSRegisterContent &from,
712 const QQmlJSRegisterContent &to) const
713{
714 return canConvertFromTo(from: containedType(container: from), to: containedType(container: to));
715}
716
717static QQmlJSRegisterContent::ContentVariant mergeVariants(QQmlJSRegisterContent::ContentVariant a,
718 QQmlJSRegisterContent::ContentVariant b)
719{
720 return (a == b) ? a : QQmlJSRegisterContent::Unknown;
721}
722
723QQmlJSRegisterContent QQmlJSTypeResolver::merge(const QQmlJSRegisterContent &a,
724 const QQmlJSRegisterContent &b) const
725{
726 if (a == b)
727 return a;
728
729 QList<QQmlJSScope::ConstPtr> origins;
730
731 QQmlJSScope::ConstPtr aResultScope;
732 if (a.isConversion()) {
733 origins.append(other: a.conversionOrigins());
734 aResultScope = a.conversionResultScope();
735 } else {
736 origins.append(t: containedType(container: a));
737 aResultScope = a.scopeType();
738 }
739
740 QQmlJSScope::ConstPtr bResultScope;
741 if (b.isConversion()) {
742 origins.append(other: b.conversionOrigins());
743 bResultScope = b.conversionResultScope();
744 } else {
745 origins.append(t: containedType(container: b));
746 bResultScope = b.scopeType();
747 }
748
749 std::sort(first: origins.begin(), last: origins.end());
750 const auto erase = std::unique(first: origins.begin(), last: origins.end());
751 origins.erase(abegin: erase, aend: origins.end());
752
753 return QQmlJSRegisterContent::create(
754 storedType: merge(a: a.storedType(), b: b.storedType()),
755 origins,
756 conversion: merge(a: containedType(container: a), b: containedType(container: b)),
757 conversionScope: merge(a: aResultScope, b: bResultScope),
758 variant: mergeVariants(a: a.variant(), b: b.variant()),
759 scope: merge(a: a.scopeType(), b: b.scopeType()));
760}
761
762QQmlJSScope::ConstPtr QQmlJSTypeResolver::merge(const QQmlJSScope::ConstPtr &a,
763 const QQmlJSScope::ConstPtr &b) const
764{
765 if (a.isNull())
766 return b;
767
768 if (b.isNull())
769 return a;
770
771 const auto commonBaseType = [this](
772 const QQmlJSScope::ConstPtr &a, const QQmlJSScope::ConstPtr &b) {
773 for (QQmlJSScope::ConstPtr aBase = a; aBase; aBase = aBase->baseType()) {
774 for (QQmlJSScope::ConstPtr bBase = b; bBase; bBase = bBase->baseType()) {
775 if (equals(a: aBase, b: bBase))
776 return aBase;
777 }
778 }
779
780 return QQmlJSScope::ConstPtr();
781 };
782
783
784 if (equals(a, b))
785 return a;
786
787 if (equals(a, b: jsValueType()) || equals(a, b: varType()))
788 return a;
789 if (equals(a: b, b: jsValueType()) || equals(a: b, b: varType()))
790 return b;
791
792 const auto isInt32Compatible = [&](const QQmlJSScope::ConstPtr &type) {
793 return (isIntegral(type)
794 && !equals(a: type, b: uint32Type())
795 && !equals(a: type, b: int64Type())
796 && !equals(a: type, b: uint64Type()))
797 || equals(a: type, b: boolType());
798 };
799
800 if (isInt32Compatible(a) && isInt32Compatible(b))
801 return int32Type();
802
803 const auto isUInt32Compatible = [&](const QQmlJSScope::ConstPtr &type) {
804 return (isUnsignedInteger(type) && !equals(a: type, b: uint64Type())) || equals(a: type, b: boolType());
805 };
806
807 if (isUInt32Compatible(a) && isUInt32Compatible(b))
808 return uint32Type();
809
810 if (isNumeric(type: a) && isNumeric(type: b))
811 return realType();
812
813 if (isPrimitive(type: a) && isPrimitive(type: b))
814 return jsPrimitiveType();
815
816 if (auto commonBase = commonBaseType(a, b))
817 return commonBase;
818
819 if ((equals(a, b: nullType()) || equals(a, b: boolType())) && b->isReferenceType())
820 return b;
821
822 if ((equals(a: b, b: nullType()) || equals(a: b, b: boolType())) && a->isReferenceType())
823 return a;
824
825 return varType();
826}
827
828bool QQmlJSTypeResolver::canHold(
829 const QQmlJSScope::ConstPtr &container, const QQmlJSScope::ConstPtr &contained) const
830{
831 if (equals(a: container, b: contained)
832 || equals(a: container, b: m_varType)
833 || equals(a: container, b: m_jsValueType)) {
834 return true;
835 }
836
837 if (equals(a: container, b: m_jsPrimitiveType))
838 return isPrimitive(type: contained);
839
840 if (equals(a: container, b: m_variantListType))
841 return contained->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence;
842
843 if (equals(a: container, b: m_qObjectListType) || equals(a: container, b: m_listPropertyType)) {
844 if (contained->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence)
845 return false;
846 if (QQmlJSScope::ConstPtr value = contained->valueType())
847 return value->isReferenceType();
848 return false;
849 }
850
851 if (QQmlJSUtils::searchBaseAndExtensionTypes(
852 type: container, check: [&](const QQmlJSScope::ConstPtr &base) {
853 return equals(a: base, b: contained);
854 })) {
855 return true;
856 }
857
858 if (container->isReferenceType()) {
859 if (QQmlJSUtils::searchBaseAndExtensionTypes(
860 type: contained, check: [&](const QQmlJSScope::ConstPtr &base) {
861 return equals(a: base, b: container);
862 })) {
863 return true;
864 }
865 }
866
867 return false;
868}
869
870
871bool QQmlJSTypeResolver::canHoldUndefined(const QQmlJSRegisterContent &content) const
872{
873 const auto canBeUndefined = [this](const QQmlJSScope::ConstPtr &type) {
874 return equals(a: type, b: m_voidType) || equals(a: type, b: m_varType)
875 || equals(a: type, b: m_jsValueType) || equals(a: type, b: m_jsPrimitiveType);
876 };
877
878 if (!canBeUndefined(content.storedType()))
879 return false;
880
881 if (!content.isConversion())
882 return canBeUndefined(containedType(container: content));
883
884 const auto origins = content.conversionOrigins();
885 for (const auto &origin : origins) {
886 if (canBeUndefined(origin))
887 return true;
888 }
889
890 return false;
891}
892
893bool QQmlJSTypeResolver::isOptionalType(const QQmlJSRegisterContent &content) const
894{
895 if (!content.isConversion())
896 return false;
897
898 const auto origins = content.conversionOrigins();
899 if (origins.length() != 2)
900 return false;
901
902 return equals(a: origins[0], b: m_voidType) || equals(a: origins[1], b: m_voidType);
903}
904
905QQmlJSScope::ConstPtr QQmlJSTypeResolver::extractNonVoidFromOptionalType(
906 const QQmlJSRegisterContent &content) const
907{
908 if (!isOptionalType(content))
909 return QQmlJSScope::ConstPtr();
910
911 const auto origins = content.conversionOrigins();
912 const QQmlJSScope::ConstPtr result = equals(a: origins[0], b: m_voidType) ? origins[1] : origins[0];
913 Q_ASSERT(!equals(result, m_voidType));
914 return result;
915}
916
917QQmlJSScope::ConstPtr QQmlJSTypeResolver::genericType(
918 const QQmlJSScope::ConstPtr &type,
919 ComponentIsGeneric allowComponent) const
920{
921 if (type->isScript())
922 return m_jsValueType;
923
924 if (equals(a: type, b: m_metaObjectType))
925 return m_metaObjectType;
926
927 if (type->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
928 QString unresolvedBaseTypeName;
929 for (auto base = type; base;) {
930 // QObject and QQmlComponent are the two required base types.
931 // Any QML type system has to define those, or use the ones from builtins.
932 // As QQmlComponent is derived from QObject, we can restrict ourselves to the latter.
933 // This results in less if'ery when retrieving a QObject* from somewhere and deciding
934 // what it is.
935 if (base->internalName() == u"QObject"_s) {
936 return base;
937 } else if (allowComponent == ComponentIsGeneric::Yes
938 && base->internalName() == u"QQmlComponent"_s) {
939 return base;
940 }
941
942 if (auto baseBase = base->baseType()) {
943 base = baseBase;
944 } else {
945 unresolvedBaseTypeName = base->baseTypeName();
946 break;
947 }
948 }
949
950 // Reference types that are not QObject or QQmlComponent are likely JavaScript objects.
951 // We don't want to deal with those, but m_jsValueType is the best generic option.
952 if (type->filePath().isEmpty())
953 return m_jsValueType;
954
955 m_logger->log(message: u"Object type %1 is not derived from QObject or QQmlComponent. "
956 "You may need to fully qualify all names in C++ so that moc can see them. "
957 "You may also need to add qt_extract_metatypes(<target containing %2>)."_s
958 .arg(args: type->internalName(), args&: unresolvedBaseTypeName),
959 id: qmlCompiler, srcLocation: type->sourceLocation());
960
961 // If it does have a filePath, it's some C++ type which we haven't fully resolved.
962 return m_jsValueType;
963 }
964
965 if (type->isListProperty())
966 return m_listPropertyType;
967
968 if (type->scopeType() == QQmlSA::ScopeType::EnumScope)
969 return type->baseType();
970
971 if (isPrimitive(type))
972 return type;
973
974 for (const QQmlJSScope::ConstPtr &builtin : {
975 m_realType, m_floatType, m_int8Type, m_uint8Type, m_int16Type, m_uint16Type,
976 m_int32Type, m_uint32Type, m_int64Type, m_uint64Type, m_boolType, m_stringType,
977 m_stringListType, m_byteArrayType, m_urlType, m_dateTimeType, m_dateType,
978 m_timeType, m_variantListType, m_variantMapType, m_varType, m_jsValueType,
979 m_jsPrimitiveType, m_listPropertyType, m_qObjectType, m_qObjectListType,
980 m_metaObjectType, m_forInIteratorPtr, m_forOfIteratorPtr }) {
981 if (equals(a: type, b: builtin) || equals(a: type, b: builtin->listType()))
982 return type;
983 }
984
985 return m_varType;
986}
987
988QQmlJSRegisterContent QQmlJSTypeResolver::builtinType(const QQmlJSScope::ConstPtr &type) const
989{
990 Q_ASSERT(storedType(type) == type);
991 return QQmlJSRegisterContent::create(
992 storedType: type, type, resultLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex, variant: QQmlJSRegisterContent::Builtin);
993}
994
995QQmlJSRegisterContent QQmlJSTypeResolver::globalType(const QQmlJSScope::ConstPtr &type) const
996{
997 return QQmlJSRegisterContent::create(
998 storedType: storedType(type), type, resultLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex,
999 variant: QQmlJSRegisterContent::Unknown);
1000}
1001
1002static QQmlJSRegisterContent::ContentVariant scopeContentVariant(QQmlJSScope::ExtensionKind mode,
1003 bool isMethod)
1004{
1005 switch (mode) {
1006 case QQmlJSScope::NotExtension:
1007 return isMethod ? QQmlJSRegisterContent::ScopeMethod : QQmlJSRegisterContent::ScopeProperty;
1008 case QQmlJSScope::ExtensionType:
1009 case QQmlJSScope::ExtensionJavaScript:
1010 return isMethod ? QQmlJSRegisterContent::ExtensionScopeMethod
1011 : QQmlJSRegisterContent::ExtensionScopeProperty;
1012 case QQmlJSScope::ExtensionNamespace:
1013 break;
1014 }
1015 Q_UNREACHABLE_RETURN(QQmlJSRegisterContent::Unknown);
1016}
1017
1018static bool isRevisionAllowed(int memberRevision, const QQmlJSScope::ConstPtr &scope)
1019{
1020 Q_ASSERT(scope->isComposite());
1021 const QTypeRevision revision = QTypeRevision::fromEncodedVersion(value: memberRevision);
1022
1023 // If the memberRevision is either invalid or 0.0, then everything is allowed.
1024 if (!revision.isValid() || revision == QTypeRevision::zero())
1025 return true;
1026
1027 const QTypeRevision typeRevision = QQmlJSScope::nonCompositeBaseRevision(
1028 scope: {.scope: scope->baseType(), .revision: scope->baseTypeRevision()});
1029
1030 // If the revision is not valid, we haven't found a non-composite base type.
1031 // There is nothing we can say about the property then.
1032 return typeRevision.isValid() && typeRevision >= revision;
1033}
1034
1035/*!
1036 * \internal
1037 * We can generally determine the relevant component boundaries for each scope. However,
1038 * if the scope or any of its parents is assigned to a property of which we cannot see the
1039 * type, we don't know whether the type of that property happens to be Component. In that
1040 * case, we can't say.
1041 */
1042bool QQmlJSTypeResolver::canFindComponentBoundaries(const QQmlJSScope::ConstPtr &scope) const
1043{
1044 for (QQmlJSScope::ConstPtr parent = scope; parent; parent = parent->parentScope()) {
1045 if (parent->isAssignedToUnknownProperty())
1046 return false;
1047 }
1048 return true;
1049}
1050
1051QQmlJSRegisterContent QQmlJSTypeResolver::scopedType(const QQmlJSScope::ConstPtr &scope,
1052 const QString &name, int lookupIndex,
1053 QQmlJSScopesByIdOptions options) const
1054{
1055 if (!canFindComponentBoundaries(scope))
1056 return {};
1057
1058 const auto isAssignedToDefaultProperty = [this](const QQmlJSScope::ConstPtr &parent,
1059 const QQmlJSScope::ConstPtr &child) {
1060 const QString defaultPropertyName = parent->defaultPropertyName();
1061 if (defaultPropertyName.isEmpty()) // no reason to search for bindings
1062 return false;
1063
1064 const QList<QQmlJSMetaPropertyBinding> defaultPropBindings =
1065 parent->propertyBindings(name: defaultPropertyName);
1066 for (const QQmlJSMetaPropertyBinding &binding : defaultPropBindings) {
1067 if (binding.bindingType() == QQmlSA::BindingType::Object
1068 && equals(a: binding.objectType(), b: child)) {
1069 return true;
1070 }
1071 }
1072 return false;
1073 };
1074
1075 if (QQmlJSScope::ConstPtr identified = m_objectsById.scope(id: name, referrer: scope, options)) {
1076 return QQmlJSRegisterContent::create(storedType: storedType(type: identified), type: identified, resultLookupIndex: lookupIndex,
1077 variant: QQmlJSRegisterContent::ObjectById, scope);
1078 }
1079
1080 if (QQmlJSScope::ConstPtr base = QQmlJSScope::findCurrentQMLScope(scope)) {
1081 QQmlJSRegisterContent result;
1082 if (QQmlJSUtils::searchBaseAndExtensionTypes(
1083 type: base, check: [&](const QQmlJSScope::ConstPtr &found, QQmlJSScope::ExtensionKind mode) {
1084 if (mode == QQmlJSScope::ExtensionNamespace) // no use for it here
1085 return false;
1086 if (found->hasOwnProperty(name)) {
1087 QQmlJSMetaProperty prop = found->ownProperty(name);
1088 if (!isRevisionAllowed(memberRevision: prop.revision(), scope))
1089 return false;
1090 if (m_parentMode == UseDocumentParent
1091 && name == base->parentPropertyName()) {
1092 QQmlJSScope::ConstPtr baseParent = base->parentScope();
1093 if (baseParent && baseParent->inherits(base: prop.type())
1094 && isAssignedToDefaultProperty(baseParent, base)) {
1095 prop.setType(baseParent);
1096 }
1097 }
1098 result = QQmlJSRegisterContent::create(
1099 storedType: storedType(type: prop.type()), property: prop,
1100 baseLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex, resultLookupIndex: lookupIndex,
1101 variant: scopeContentVariant(mode, isMethod: false), scope);
1102 return true;
1103 }
1104
1105 if (found->hasOwnMethod(name)) {
1106 auto methods = found->ownMethods(name);
1107 for (auto it = methods.begin(); it != methods.end();) {
1108 if (!isRevisionAllowed(memberRevision: it->revision(), scope))
1109 it = methods.erase(pos: it);
1110 else
1111 ++it;
1112 }
1113 if (methods.isEmpty())
1114 return false;
1115 result = QQmlJSRegisterContent::create(
1116 storedType: jsValueType(), methods, variant: scopeContentVariant(mode, isMethod: true), scope);
1117 return true;
1118 }
1119
1120 // Unqualified enums are not allowed
1121
1122 return false;
1123 })) {
1124 return result;
1125 }
1126 }
1127
1128 QQmlJSRegisterContent result = registerContentForName(name);
1129
1130 if (result.isValid())
1131 return result;
1132
1133 if (m_jsGlobalObject->hasProperty(name)) {
1134 return QQmlJSRegisterContent::create(
1135 storedType: jsValueType(), property: m_jsGlobalObject->property(name),
1136 baseLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex, resultLookupIndex: lookupIndex,
1137 variant: QQmlJSRegisterContent::JavaScriptGlobal, scope: m_jsGlobalObject);
1138 } else if (m_jsGlobalObject->hasMethod(name)) {
1139 return QQmlJSRegisterContent::create(storedType: jsValueType(), methods: m_jsGlobalObject->methods(name),
1140 variant: QQmlJSRegisterContent::JavaScriptGlobal,
1141 scope: m_jsGlobalObject);
1142 }
1143
1144 return {};
1145}
1146
1147bool QQmlJSTypeResolver::checkEnums(const QQmlJSScope::ConstPtr &scope, const QString &name,
1148 QQmlJSRegisterContent *result,
1149 QQmlJSScope::ExtensionKind mode) const
1150{
1151 // You can't have lower case enum names in QML, even if we know the enums here.
1152 if (name.isEmpty() || !name.at(i: 0).isUpper())
1153 return false;
1154
1155 const bool inExtension = (mode != QQmlJSScope::NotExtension);
1156
1157 const auto enums = scope->ownEnumerations();
1158 for (const auto &enumeration : enums) {
1159 if ((enumeration.isScoped() || enumeration.isQml()) && enumeration.name() == name) {
1160 *result = QQmlJSRegisterContent::create(
1161 storedType: storedType(type: enumeration.type()), enumeration, enumMember: QString(),
1162 variant: inExtension ? QQmlJSRegisterContent::ExtensionObjectEnum
1163 : QQmlJSRegisterContent::ObjectEnum,
1164 scope);
1165 return true;
1166 }
1167
1168 if ((!enumeration.isScoped() || enumeration.isQml() || !scope->enforcesScopedEnums())
1169 && enumeration.hasKey(key: name)) {
1170 *result = QQmlJSRegisterContent::create(
1171 storedType: storedType(type: enumeration.type()), enumeration, enumMember: name,
1172 variant: inExtension ? QQmlJSRegisterContent::ExtensionObjectEnum
1173 : QQmlJSRegisterContent::ObjectEnum,
1174 scope);
1175 return true;
1176 }
1177 }
1178
1179 return false;
1180}
1181
1182bool QQmlJSTypeResolver::canPopulate(
1183 const QQmlJSScope::ConstPtr &type, const QQmlJSScope::ConstPtr &passedArgumentType,
1184 bool *isExtension) const
1185{
1186 // TODO: We could allow QVariantMap and QVariantHash to be populated, but that needs extra
1187 // code in the code generator.
1188
1189 if (type.isNull()
1190 || canHold(container: passedArgumentType, contained: type)
1191 || isPrimitive(type: passedArgumentType)
1192 || type->accessSemantics() != QQmlJSScope::AccessSemantics::Value
1193 || !type->isStructured()) {
1194 return false;
1195 }
1196
1197 if (isExtension)
1198 *isExtension = !type->extensionType().scope.isNull();
1199
1200 return true;
1201}
1202
1203QQmlJSMetaMethod QQmlJSTypeResolver::selectConstructor(
1204 const QQmlJSScope::ConstPtr &type, const QQmlJSScope::ConstPtr &passedArgumentType,
1205 bool *isExtension) const
1206{
1207 // If the "from" type can hold the target type, we should not try to coerce
1208 // it to any constructor argument.
1209 if (type.isNull()
1210 || canHold(container: passedArgumentType, contained: type)
1211 || type->accessSemantics() != QQmlJSScope::AccessSemantics::Value
1212 || !type->isCreatable()) {
1213 return QQmlJSMetaMethod();
1214 }
1215
1216 auto doSelectConstructor = [&](const QQmlJSScope::ConstPtr &type) {
1217 QQmlJSMetaMethod candidate;
1218
1219 const auto ownMethods = type->ownMethods();
1220 for (const QQmlJSMetaMethod &method : ownMethods) {
1221 if (!method.isConstructor())
1222 continue;
1223
1224 const auto index = method.constructorIndex();
1225 Q_ASSERT(index != QQmlJSMetaMethod::RelativeFunctionIndex::Invalid);
1226
1227 const auto methodArguments = method.parameters();
1228 if (methodArguments.size() != 1)
1229 continue;
1230
1231 const QQmlJSScope::ConstPtr methodArgumentType = methodArguments[0].type();
1232
1233 if (equals(a: passedArgumentType, b: methodArgumentType))
1234 return method;
1235
1236 // Do not select further ctors here. We don't want to do multi-step construction as that
1237 // is confusing and easily leads to infinite recursion.
1238 if (!candidate.isValid()
1239 && canPrimitivelyConvertFromTo(from: passedArgumentType, to: methodArgumentType)) {
1240 candidate = method;
1241 }
1242 }
1243
1244 return candidate;
1245 };
1246
1247 if (QQmlJSScope::ConstPtr extension = type->extensionType().scope) {
1248 const QQmlJSMetaMethod ctor = doSelectConstructor(extension);
1249 if (ctor.isValid()) {
1250 if (isExtension)
1251 *isExtension = true;
1252 return ctor;
1253 }
1254 }
1255
1256 if (isExtension)
1257 *isExtension = false;
1258
1259 return doSelectConstructor(type);
1260}
1261
1262bool QQmlJSTypeResolver::areEquivalentLists(
1263 const QQmlJSScope::ConstPtr &a, const QQmlJSScope::ConstPtr &b) const
1264{
1265 const QQmlJSScope::ConstPtr equivalentLists[2][2] = {
1266 { m_stringListType, m_stringType->listType() },
1267 { m_variantListType, m_varType->listType() }
1268 };
1269
1270 for (const auto eq : equivalentLists) {
1271 if ((equals(a, b: eq[0]) && equals(a: b, b: eq[1])) || (equals(a, b: eq[1]) && equals(a: b, b: eq[0])))
1272 return true;
1273 }
1274
1275 return false;
1276}
1277
1278bool QQmlJSTypeResolver::isTriviallyCopyable(const QQmlJSScope::ConstPtr &type) const
1279{
1280 // pointers are trivially copyable
1281 if (type->isReferenceType())
1282 return true;
1283
1284 // Enum values are trivially copyable
1285 if (type->scopeType() == QQmlSA::ScopeType::EnumScope)
1286 return true;
1287
1288 for (const QQmlJSScope::ConstPtr &trivial : {
1289 m_nullType, m_voidType,
1290 m_boolType, m_metaObjectType,
1291 m_realType, m_floatType,
1292 m_int8Type, m_uint8Type,
1293 m_int16Type, m_uint16Type,
1294 m_int32Type, m_uint32Type,
1295 m_int64Type, m_uint64Type }) {
1296 if (equals(a: type, b: trivial))
1297 return true;
1298 }
1299
1300 return false;
1301}
1302
1303bool QQmlJSTypeResolver::inherits(const QQmlJSScope::ConstPtr &derived, const QQmlJSScope::ConstPtr &base) const
1304{
1305 const bool matchByName = !base->isComposite();
1306 for (QQmlJSScope::ConstPtr derivedBase = derived; derivedBase;
1307 derivedBase = derivedBase->baseType()) {
1308 if (equals(a: derivedBase, b: base))
1309 return true;
1310 if (matchByName
1311 && !derivedBase->isComposite()
1312 && derivedBase->internalName() == base->internalName()) {
1313 return true;
1314 }
1315 }
1316 return false;
1317}
1318
1319bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo(
1320 const QQmlJSScope::ConstPtr &from, const QQmlJSScope::ConstPtr &to) const
1321{
1322 if (equals(a: from, b: to))
1323 return true;
1324 if (equals(a: from, b: m_varType) || equals(a: to, b: m_varType))
1325 return true;
1326 if (equals(a: from, b: m_jsValueType) || equals(a: to, b: m_jsValueType))
1327 return true;
1328 if (equals(a: to, b: m_qQmlScriptStringType))
1329 return true;
1330 if (isNumeric(type: from) && isNumeric(type: to))
1331 return true;
1332 if (isNumeric(type: from) && equals(a: to, b: m_boolType))
1333 return true;
1334 if (from->accessSemantics() == QQmlJSScope::AccessSemantics::Reference
1335 && (equals(a: to, b: m_boolType) || equals(a: to, b: m_stringType))) {
1336 return true;
1337 }
1338
1339 // Yes, our String has number constructors.
1340 if (isNumeric(type: from) && equals(a: to, b: m_stringType))
1341 return true;
1342
1343 // We can convert strings to numbers, but not to enums
1344 if (equals(a: from, b: m_stringType) && isNumeric(type: to))
1345 return to->scopeType() != QQmlJSScope::ScopeType::EnumScope;
1346
1347 // We can always convert between strings and urls.
1348 if ((equals(a: from, b: m_stringType) && equals(a: to, b: m_urlType))
1349 || (equals(a: from, b: m_urlType) && equals(a: to, b: m_stringType))) {
1350 return true;
1351 }
1352
1353 // We can always convert between strings and byte arrays.
1354 if ((equals(a: from, b: m_stringType) && equals(a: to, b: m_byteArrayType))
1355 || (equals(a: from, b: m_byteArrayType) && equals(a: to, b: m_stringType))) {
1356 return true;
1357 }
1358
1359 if (equals(a: to, b: m_voidType))
1360 return true;
1361
1362 if (to.isNull())
1363 return equals(a: from, b: m_voidType);
1364
1365 const auto types = { m_dateTimeType, m_dateType, m_timeType, m_stringType };
1366 for (const auto &originType : types) {
1367 if (!equals(a: from, b: originType))
1368 continue;
1369
1370 for (const auto &targetType : types) {
1371 if (equals(a: to, b: targetType))
1372 return true;
1373 }
1374
1375 if (equals(a: to, b: m_realType))
1376 return true;
1377
1378 break;
1379 }
1380
1381 if (equals(a: from, b: m_nullType)
1382 && to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
1383 return true;
1384 }
1385
1386 if (equals(a: from, b: m_jsPrimitiveType)) {
1387 // You can cast any primitive to a nullptr
1388 return isPrimitive(type: to) || to->accessSemantics() == QQmlJSScope::AccessSemantics::Reference;
1389 }
1390
1391 if (equals(a: to, b: m_jsPrimitiveType))
1392 return isPrimitive(type: from);
1393
1394 if (equals(a: from, b: m_variantListType))
1395 return to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence;
1396
1397 const bool matchByName = !to->isComposite();
1398 Q_ASSERT(!matchByName || !to->internalName().isEmpty());
1399 for (auto baseType = from; baseType; baseType = baseType->baseType()) {
1400 if (equals(a: baseType, b: to))
1401 return true;
1402 if (matchByName && baseType->internalName() == to->internalName())
1403 return true;
1404 }
1405
1406 // We can convert anything that fits into QJSPrimitiveValue
1407 if (canConvertFromTo(from, to: m_jsPrimitiveType) && canConvertFromTo(from: m_jsPrimitiveType, to))
1408 return true;
1409
1410 // We can convert everything to bool.
1411 if (equals(a: to, b: m_boolType))
1412 return true;
1413
1414 if (areEquivalentLists(a: from, b: to))
1415 return true;
1416
1417 if (from->isListProperty()
1418 && to->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence
1419 && canConvertFromTo(from: from->valueType(), to: to->valueType())) {
1420 return true;
1421 }
1422
1423 if (equals(a: to, b: m_stringType)
1424 && from->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) {
1425 return canConvertFromTo(from: from->valueType(), to: m_stringType);
1426 }
1427
1428 return false;
1429}
1430
1431QQmlJSRegisterContent QQmlJSTypeResolver::lengthProperty(
1432 bool isWritable, const QQmlJSScope::ConstPtr &scope) const
1433{
1434 QQmlJSMetaProperty prop;
1435 prop.setPropertyName(u"length"_s);
1436 prop.setTypeName(u"qsizetype"_s);
1437 prop.setType(sizeType());
1438 prop.setIsWritable(isWritable);
1439 return QQmlJSRegisterContent::create(
1440 storedType: sizeType(), property: prop, baseLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex,
1441 resultLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex, variant: QQmlJSRegisterContent::Builtin, scope);
1442}
1443
1444QQmlJSRegisterContent QQmlJSTypeResolver::memberType(
1445 const QQmlJSScope::ConstPtr &type, const QString &name, int baseLookupIndex,
1446 int resultLookupIndex) const
1447{
1448 QQmlJSRegisterContent result;
1449
1450 // If we got a plain type reference we have to check the enums of the _scope_.
1451 if (equals(a: type, b: metaObjectType()))
1452 return {};
1453
1454 if (equals(a: type, b: variantMapType())) {
1455 QQmlJSMetaProperty prop;
1456 prop.setPropertyName(name);
1457 prop.setTypeName(u"QVariant"_s);
1458 prop.setType(varType());
1459 prop.setIsWritable(true);
1460 return QQmlJSRegisterContent::create(
1461 storedType: varType(), property: prop, baseLookupIndex, resultLookupIndex,
1462 variant: QQmlJSRegisterContent::GenericObjectProperty, scope: type);
1463 }
1464
1465 if (equals(a: type, b: jsValueType())) {
1466 QQmlJSMetaProperty prop;
1467 prop.setPropertyName(name);
1468 prop.setTypeName(u"QJSValue"_s);
1469 prop.setType(jsValueType());
1470 prop.setIsWritable(true);
1471 return QQmlJSRegisterContent::create(
1472 storedType: jsValueType(), property: prop, baseLookupIndex, resultLookupIndex,
1473 variant: QQmlJSRegisterContent::GenericObjectProperty, scope: type);
1474 }
1475
1476 if ((equals(a: type, b: stringType())
1477 || type->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence)
1478 && name == u"length"_s) {
1479 return lengthProperty(isWritable: !equals(a: type, b: stringType()), scope: type);
1480 }
1481
1482 const auto check = [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) {
1483 if (mode != QQmlJSScope::ExtensionNamespace) {
1484 if (scope->hasOwnProperty(name)) {
1485 const auto prop = scope->ownProperty(name);
1486 result = QQmlJSRegisterContent::create(
1487 storedType: storedType(type: prop.type()),
1488 property: prop, baseLookupIndex, resultLookupIndex,
1489 variant: mode == QQmlJSScope::NotExtension
1490 ? QQmlJSRegisterContent::ObjectProperty
1491 : QQmlJSRegisterContent::ExtensionObjectProperty,
1492 scope);
1493 return true;
1494 }
1495
1496 if (scope->hasOwnMethod(name)) {
1497 const auto methods = scope->ownMethods(name);
1498 result = QQmlJSRegisterContent::create(
1499 storedType: jsValueType(), methods,
1500 variant: mode == QQmlJSScope::NotExtension
1501 ? QQmlJSRegisterContent::ObjectMethod
1502 : QQmlJSRegisterContent::ExtensionObjectMethod,
1503 scope);
1504 return true;
1505 }
1506 }
1507
1508 return checkEnums(scope, name, result: &result, mode);
1509 };
1510
1511 if (QQmlJSUtils::searchBaseAndExtensionTypes(type, check))
1512 return result;
1513
1514 for (auto scope = type;
1515 scope && (scope->scopeType() == QQmlSA::ScopeType::JSFunctionScope
1516 || scope->scopeType() == QQmlSA::ScopeType::JSLexicalScope);
1517 scope = scope->parentScope()) {
1518 if (auto ownIdentifier = scope->ownJSIdentifier(id: name)) {
1519 QQmlJSMetaProperty prop;
1520 prop.setPropertyName(name);
1521 prop.setTypeName(u"QJSValue"_s);
1522 prop.setType(jsValueType());
1523 prop.setIsWritable(!(ownIdentifier.value().isConst));
1524
1525 return QQmlJSRegisterContent::create(storedType: jsValueType(), property: prop, baseLookupIndex,
1526 resultLookupIndex,
1527 variant: QQmlJSRegisterContent::JavaScriptObject, scope);
1528 }
1529 }
1530
1531 if (QQmlJSScope::ConstPtr attachedBase = typeForName(name)) {
1532 if (QQmlJSScope::ConstPtr attached = attachedBase->attachedType()) {
1533 if (!genericType(type: attached)) {
1534 m_logger->log(message: u"Cannot resolve generic base of attached %1"_s.arg(
1535 a: attached->internalName()),
1536 id: qmlCompiler, srcLocation: attached->sourceLocation());
1537 return {};
1538 } else if (type->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
1539 m_logger->log(message: u"Cannot retrieve attached object for non-reference type %1"_s.arg(
1540 a: type->internalName()),
1541 id: qmlCompiler, srcLocation: type->sourceLocation());
1542 return {};
1543 } else {
1544 return QQmlJSRegisterContent::create(
1545 storedType: storedType(type: attached), type: attached, resultLookupIndex,
1546 variant: QQmlJSRegisterContent::ObjectAttached, scope: attachedBase);
1547 }
1548 }
1549 }
1550
1551 return {};
1552}
1553
1554QQmlJSRegisterContent QQmlJSTypeResolver::memberEnumType(const QQmlJSScope::ConstPtr &type,
1555 const QString &name) const
1556{
1557 QQmlJSRegisterContent result;
1558
1559 if (QQmlJSUtils::searchBaseAndExtensionTypes(
1560 type, check: [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) {
1561 return checkEnums(scope, name, result: &result, mode);
1562 })) {
1563 return result;
1564 }
1565
1566 return {};
1567}
1568
1569QQmlJSRegisterContent QQmlJSTypeResolver::memberType(
1570 const QQmlJSRegisterContent &type, const QString &name, int lookupIndex) const
1571{
1572 if (type.isType()) {
1573 const auto content = type.type();
1574 const auto result = memberType(type: content, name, baseLookupIndex: type.resultLookupIndex(), resultLookupIndex: lookupIndex);
1575 if (result.isValid())
1576 return result;
1577
1578 // If we didn't find anything and it's an attached type,
1579 // we might have an enum of the attaching type.
1580 return memberEnumType(type: type.scopeType(), name);
1581 }
1582 if (type.isProperty())
1583 return memberType(type: type.property().type(), name, baseLookupIndex: type.resultLookupIndex(), resultLookupIndex: lookupIndex);
1584 if (type.isEnumeration()) {
1585 const auto enumeration = type.enumeration();
1586 if (!type.enumMember().isEmpty() || !enumeration.hasKey(key: name))
1587 return {};
1588 return QQmlJSRegisterContent::create(storedType: storedType(type: enumeration.type()), enumeration, enumMember: name,
1589 variant: QQmlJSRegisterContent::ObjectEnum, scope: type.scopeType());
1590 }
1591 if (type.isMethod()) {
1592 QQmlJSMetaProperty prop;
1593 prop.setTypeName(u"QJSValue"_s);
1594 prop.setPropertyName(name);
1595 prop.setType(jsValueType());
1596 prop.setIsWritable(true);
1597 return QQmlJSRegisterContent::create(
1598 storedType: jsValueType(), property: prop, baseLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex, resultLookupIndex: lookupIndex,
1599 variant: QQmlJSRegisterContent::GenericObjectProperty, scope: jsValueType());
1600 }
1601 if (type.isImportNamespace()) {
1602 Q_ASSERT(type.scopeType()->isReferenceType());
1603 return registerContentForName(
1604 name, scopeType: type.scopeType(),
1605 hasObjectModulePrefix: type.variant() == QQmlJSRegisterContent::ObjectModulePrefix);
1606 }
1607 if (type.isConversion()) {
1608 if (const auto result = memberType(
1609 type: type.conversionResult(), name, baseLookupIndex: type.resultLookupIndex(), resultLookupIndex: lookupIndex);
1610 result.isValid()) {
1611 return result;
1612 }
1613
1614 if (const auto result = memberEnumType(type: type.scopeType(), name); result.isValid())
1615 return result;
1616
1617 // If the conversion consists of only undefined and one actual type,
1618 // we can produce the members of that one type.
1619 // If the value is then actually undefined, the result is an exception.
1620
1621 auto origins = type.conversionOrigins();
1622 const auto begin = origins.begin();
1623 const auto end = std::remove_if(first: begin, last: origins.end(),
1624 pred: [this](const QQmlJSScope::ConstPtr &origin) {
1625 return equals(a: origin, b: m_voidType);
1626 });
1627
1628 // If the conversion cannot hold the original type, it loses information.
1629 return (end - begin == 1 && canHold(container: type.conversionResult(), contained: *begin))
1630 ? memberType(type: *begin, name, baseLookupIndex: type.resultLookupIndex(), resultLookupIndex: lookupIndex)
1631 : QQmlJSRegisterContent();
1632 }
1633
1634 Q_UNREACHABLE_RETURN({});
1635}
1636
1637QQmlJSRegisterContent QQmlJSTypeResolver::valueType(const QQmlJSRegisterContent &list) const
1638{
1639 QQmlJSScope::ConstPtr scope;
1640 QQmlJSScope::ConstPtr value;
1641
1642 auto valueType = [&](const QQmlJSScope::ConstPtr &scope) {
1643 if (scope->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence)
1644 return scope->valueType();
1645
1646 if (equals(a: scope, b: m_forInIteratorPtr))
1647 return m_sizeType;
1648
1649 if (equals(a: scope, b: m_forOfIteratorPtr))
1650 return list.scopeType()->valueType();
1651
1652 if (equals(a: scope, b: m_jsValueType) || equals(a: scope, b: m_varType))
1653 return m_jsValueType;
1654
1655 if (equals(a: scope, b: m_stringType))
1656 return m_stringType;
1657
1658 return QQmlJSScope::ConstPtr();
1659 };
1660
1661 if (list.isType()) {
1662 scope = list.type();
1663 value = valueType(scope);
1664 } else if (list.isConversion()) {
1665 value = valueType(list.conversionResult());
1666 } else if (list.isProperty()) {
1667 const auto prop = list.property();
1668 scope = prop.type();
1669 value = valueType(scope);
1670 }
1671
1672 if (value.isNull())
1673 return {};
1674
1675 QQmlJSMetaProperty property;
1676 property.setPropertyName(u"[]"_s);
1677 property.setTypeName(value->internalName());
1678 property.setType(value);
1679
1680 return QQmlJSRegisterContent::create(
1681 storedType: storedType(type: value), property, baseLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex,
1682 resultLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex, variant: QQmlJSRegisterContent::ListValue, scope);
1683}
1684
1685QQmlJSRegisterContent QQmlJSTypeResolver::returnType(
1686 const QQmlJSScope::ConstPtr &type, QQmlJSRegisterContent::ContentVariant variant,
1687 const QQmlJSScope::ConstPtr &scope) const
1688{
1689 Q_ASSERT(variant == QQmlJSRegisterContent::MethodReturnValue
1690 || variant == QQmlJSRegisterContent::JavaScriptReturnValue);
1691 return QQmlJSRegisterContent::create(
1692 storedType: storedType(type), type, resultLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex, variant, scope);
1693}
1694
1695QQmlJSRegisterContent QQmlJSTypeResolver::iteratorPointer(
1696 const QQmlJSRegisterContent &listType, QQmlJS::AST::ForEachType type,
1697 int lookupIndex) const
1698{
1699 const QQmlJSScope::ConstPtr value = (type == QQmlJS::AST::ForEachType::In)
1700 ? m_int32Type
1701 : containedType(container: valueType(list: listType));
1702
1703 QQmlJSScope::ConstPtr iteratorPointer = type == QQmlJS::AST::ForEachType::In
1704 ? m_forInIteratorPtr
1705 : m_forOfIteratorPtr;
1706
1707 const QQmlJSScope::ConstPtr listContained = containedType(container: listType);
1708
1709 QQmlJSMetaProperty prop;
1710 prop.setPropertyName(u"<>"_s);
1711 prop.setTypeName(iteratorPointer->internalName());
1712 prop.setType(iteratorPointer);
1713 return QQmlJSRegisterContent::create(
1714 storedType: storedType(type: iteratorPointer), property: prop, baseLookupIndex: lookupIndex,
1715 resultLookupIndex: QQmlJSRegisterContent::InvalidLookupIndex, variant: QQmlJSRegisterContent::ListIterator,
1716 scope: listContained);
1717}
1718
1719bool QQmlJSTypeResolver::registerIsStoredIn(
1720 const QQmlJSRegisterContent &reg, const QQmlJSScope::ConstPtr &type) const
1721{
1722 return equals(a: reg.storedType(), b: type);
1723}
1724
1725bool QQmlJSTypeResolver::registerContains(const QQmlJSRegisterContent &reg,
1726 const QQmlJSScope::ConstPtr &type) const
1727{
1728 if (reg.isType())
1729 return equals(a: reg.type(), b: type);
1730 if (reg.isConversion())
1731 return equals(a: reg.conversionResult(), b: type);
1732 if (reg.isProperty())
1733 return equals(a: type, b: reg.property().type());
1734 if (reg.isEnumeration())
1735 return equals(a: type, b: reg.enumeration().type());
1736 if (reg.isMethod())
1737 return equals(a: type, b: jsValueType());
1738 return false;
1739}
1740
1741QQmlJSScope::ConstPtr QQmlJSTypeResolver::storedType(const QQmlJSScope::ConstPtr &type) const
1742{
1743 if (type.isNull())
1744 return {};
1745 if (equals(a: type, b: voidType()))
1746 return type;
1747 if (type->isScript())
1748 return jsValueType();
1749 if (type->isComposite()) {
1750 if (const QQmlJSScope::ConstPtr nonComposite = QQmlJSScope::nonCompositeBaseType(type))
1751 return nonComposite;
1752
1753 // If we can't find the non-composite base, we really don't know what it is.
1754 return genericType(type);
1755 }
1756 if (type->filePath().isEmpty())
1757 return genericType(type);
1758 return type;
1759}
1760
1761QQmlJSScope::ConstPtr QQmlJSTypeResolver::originalType(const QQmlJSScope::ConstPtr &type) const
1762{
1763 const auto it = m_trackedTypes->find(key: type);
1764 return it == m_trackedTypes->end() ? type : it->original;
1765}
1766
1767/*!
1768 * \internal
1769 *
1770 * Compares the origin types of \a a and \a b. A straight a == b would compare the identity
1771 * of the pointers. However, since we clone types to keep track of them, we need a separate
1772 * way to compare the clones. Usually you'd do *a == *b for that, but as QQmlJSScope is rather
1773 * large, we offer an optimization here that uses the type tracking we already have in place.
1774 */
1775bool QQmlJSTypeResolver::equals(const QQmlJSScope::ConstPtr &a, const QQmlJSScope::ConstPtr &b) const
1776{
1777 return comparableType(type: a) == comparableType(type: b);
1778}
1779
1780QQmlJSRegisterContent QQmlJSTypeResolver::convert(
1781 const QQmlJSRegisterContent &from, const QQmlJSRegisterContent &to) const
1782{
1783 if (from.isConversion()) {
1784 return QQmlJSRegisterContent::create(
1785 storedType: to.storedType(), origins: from.conversionOrigins(), conversion: containedType(container: to),
1786 conversionScope: to.scopeType() ? to.scopeType() : from.conversionResultScope(),
1787 variant: from.variant(), scope: from.scopeType());
1788 }
1789
1790 return QQmlJSRegisterContent::create(
1791 storedType: to.storedType(), origins: QList<QQmlJSScope::ConstPtr>{containedType(container: from)},
1792 conversion: containedType(container: to), conversionScope: to.scopeType(), variant: from.variant(), scope: from.scopeType());
1793}
1794
1795QQmlJSRegisterContent QQmlJSTypeResolver::cast(
1796 const QQmlJSRegisterContent &from, const QQmlJSScope::ConstPtr &to) const
1797{
1798 return from.castTo(newContainedType: to).storedIn(newStoredType: storedType(type: to));
1799}
1800
1801QQmlJSScope::ConstPtr QQmlJSTypeResolver::comparableType(const QQmlJSScope::ConstPtr &type) const
1802{
1803 const auto it = m_trackedTypes->constFind(key: type);
1804 if (it == m_trackedTypes->constEnd())
1805 return type;
1806 return it->replacement ? it->replacement : it->original;
1807}
1808
1809QT_END_NAMESPACE
1810

source code of qtdeclarative/src/qmlcompiler/qqmljstyperesolver.cpp