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