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