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 | |
16 | QT_BEGIN_NAMESPACE |
17 | |
18 | using namespace Qt::StringLiterals; |
19 | |
20 | Q_LOGGING_CATEGORY(lcTypeResolver, "qt.qml.compiler.typeresolver" , QtInfoMsg); |
21 | |
22 | QQmlJSTypeResolver::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 | */ |
122 | void 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 | |
140 | QQmlJSScope::ConstPtr |
141 | QQmlJSTypeResolver::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 | |
149 | QQmlJSScope::ConstPtr QQmlJSTypeResolver::scopeForId( |
150 | const QString &id, const QQmlJSScope::ConstPtr &referrer) const |
151 | { |
152 | return m_objectsById.scope(id, referrer); |
153 | } |
154 | |
155 | QString QQmlJSTypeResolver::idForScope( |
156 | const QQmlJSScope::ConstPtr &scope, const QQmlJSScope::ConstPtr &referrer) const |
157 | { |
158 | return m_objectsById.id(scope, referrer); |
159 | } |
160 | |
161 | QQmlJSScope::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 | |
173 | QQmlJSScope::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 | |
197 | QQmlJSRegisterContent |
198 | QQmlJSTypeResolver::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 | |
255 | QQmlJSRegisterContent 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 | |
276 | bool QQmlJSTypeResolver::isPrimitive(const QQmlJSRegisterContent &type) const |
277 | { |
278 | return isPrimitive(type: containedType(container: type)); |
279 | } |
280 | |
281 | bool QQmlJSTypeResolver::isNumeric(const QQmlJSRegisterContent &type) const |
282 | { |
283 | return isNumeric(type: containedType(container: type)); |
284 | } |
285 | |
286 | bool QQmlJSTypeResolver::isIntegral(const QQmlJSRegisterContent &type) const |
287 | { |
288 | return isIntegral(type: containedType(container: type)); |
289 | } |
290 | |
291 | bool 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 | |
297 | bool 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 | |
304 | bool 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 | |
314 | bool 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 | |
322 | bool 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 | |
330 | QQmlJSScope::ConstPtr |
331 | QQmlJSTypeResolver::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 | |
357 | QQmlJSScope::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 | |
371 | QQmlJSRegisterContent 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 | |
420 | QQmlJSRegisterContent 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 | |
482 | QQmlJSRegisterContent QQmlJSTypeResolver::original(const QQmlJSRegisterContent &type) const |
483 | { |
484 | return transformed(origin: type, op: &QQmlJSTypeResolver::originalType); |
485 | } |
486 | |
487 | QQmlJSRegisterContent QQmlJSTypeResolver::tracked(const QQmlJSRegisterContent &type) const |
488 | { |
489 | return transformed(origin: type, op: &QQmlJSTypeResolver::trackedType); |
490 | } |
491 | |
492 | QQmlJSScope::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 | |
499 | QQmlJSScope::ConstPtr QQmlJSTypeResolver::originalContainedType( |
500 | const QQmlJSRegisterContent &container) const |
501 | { |
502 | return originalType(type: containedType(container)); |
503 | } |
504 | |
505 | bool 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 | |
526 | bool 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 | |
551 | void 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 | |
564 | void 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 | |
577 | QString 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 | |
601 | bool 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 | |
627 | bool QQmlJSTypeResolver::canConvertFromTo(const QQmlJSRegisterContent &from, |
628 | const QQmlJSRegisterContent &to) const |
629 | { |
630 | return canConvertFromTo(from: containedType(container: from), to: containedType(container: to)); |
631 | } |
632 | |
633 | static QQmlJSRegisterContent::ContentVariant mergeVariants(QQmlJSRegisterContent::ContentVariant a, |
634 | QQmlJSRegisterContent::ContentVariant b) |
635 | { |
636 | return (a == b) ? a : QQmlJSRegisterContent::Unknown; |
637 | } |
638 | |
639 | QQmlJSRegisterContent 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 | |
675 | QQmlJSScope::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 | |
745 | bool 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 | |
788 | bool 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 | |
810 | QQmlJSScope::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 | |
877 | QQmlJSRegisterContent 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 | |
883 | QQmlJSRegisterContent QQmlJSTypeResolver::globalType(const QQmlJSScope::ConstPtr &type) const |
884 | { |
885 | return QQmlJSRegisterContent::create(storedType: storedType(type), type, variant: QQmlJSRegisterContent::Unknown); |
886 | } |
887 | |
888 | static 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 | |
903 | static 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 | |
920 | QQmlJSRegisterContent 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 | |
1010 | bool 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 | |
1045 | QQmlJSMetaMethod 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 | |
1097 | bool 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 | |
1113 | bool 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 | |
1215 | QQmlJSRegisterContent 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 | |
1226 | QQmlJSRegisterContent 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 | |
1330 | QQmlJSRegisterContent 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 | |
1345 | QQmlJSRegisterContent 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 | |
1414 | QQmlJSRegisterContent 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 | |
1452 | QQmlJSRegisterContent 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 | |
1461 | bool QQmlJSTypeResolver::registerIsStoredIn( |
1462 | const QQmlJSRegisterContent ®, const QQmlJSScope::ConstPtr &type) const |
1463 | { |
1464 | return equals(a: reg.storedType(), b: type); |
1465 | } |
1466 | |
1467 | bool QQmlJSTypeResolver::registerContains(const QQmlJSRegisterContent ®, |
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 | |
1483 | QQmlJSScope::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 | |
1503 | QQmlJSScope::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 | */ |
1517 | bool QQmlJSTypeResolver::equals(const QQmlJSScope::ConstPtr &a, const QQmlJSScope::ConstPtr &b) const |
1518 | { |
1519 | return comparableType(type: a) == comparableType(type: b); |
1520 | } |
1521 | |
1522 | QQmlJSRegisterContent 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 | |
1537 | QQmlJSScope::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 | |
1545 | QT_END_NAMESPACE |
1546 | |