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

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