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

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