1 | // Copyright (C) 2019 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 "qqmljscontextualtypes_p.h" |
5 | #include "qqmljsscope_p.h" |
6 | #include "qqmljstypereader_p.h" |
7 | #include "qqmljsimporter_p.h" |
8 | #include "qqmljsutils_p.h" |
9 | #include "qqmlsa.h" |
10 | #include "qqmlsa_p.h" |
11 | |
12 | #include <QtCore/qqueue.h> |
13 | #include <QtCore/qsharedpointer.h> |
14 | |
15 | #include <private/qduplicatetracker_p.h> |
16 | |
17 | #include <algorithm> |
18 | #include <type_traits> |
19 | |
20 | QT_BEGIN_NAMESPACE |
21 | |
22 | /*! |
23 | \class QQmlJSScope |
24 | \internal |
25 | \brief Tracks the types for the QmlCompiler |
26 | |
27 | QQmlJSScope tracks the types used in qml for the QmlCompiler. |
28 | |
29 | Multiple QQmlJSScope objects might be created for the same conceptual type, except when reused |
30 | due to extensive caching. Two QQmlJSScope objects are considered equal when they are backed |
31 | by the same implementation, that is, they have the same internalName. |
32 | */ |
33 | |
34 | using namespace Qt::StringLiterals; |
35 | |
36 | QQmlJSScope::QQmlJSScope(const QString &internalName) : QQmlJSScope{} |
37 | { |
38 | m_internalName = internalName; |
39 | } |
40 | |
41 | QQmlJSScope::Ptr QQmlJSScope::create(const QString &internalName) |
42 | { |
43 | return QSharedPointer<QQmlJSScope>(new QQmlJSScope(internalName)); |
44 | } |
45 | |
46 | void QQmlJSScope::reparent(const QQmlJSScope::Ptr &parentScope, const QQmlJSScope::Ptr &childScope) |
47 | { |
48 | if (const QQmlJSScope::Ptr parent = childScope->m_parentScope.toStrongRef()) |
49 | parent->m_childScopes.removeOne(t: childScope); |
50 | if (parentScope) |
51 | parentScope->m_childScopes.append(t: childScope); |
52 | childScope->m_parentScope = parentScope; |
53 | } |
54 | |
55 | QQmlJSScope::Ptr QQmlJSScope::clone(const ConstPtr &origin) |
56 | { |
57 | if (origin.isNull()) |
58 | return QQmlJSScope::Ptr(); |
59 | QQmlJSScope::Ptr cloned = create(); |
60 | *cloned = *origin; |
61 | if (QQmlJSScope::Ptr parent = cloned->parentScope()) |
62 | parent->m_childScopes.append(t: cloned); |
63 | return cloned; |
64 | } |
65 | |
66 | /*! |
67 | \internal |
68 | Return all the JavaScript identifiers defined in the current scope. |
69 | */ |
70 | QHash<QString, QQmlJSScope::JavaScriptIdentifier> QQmlJSScope::ownJSIdentifiers() const |
71 | { |
72 | return m_jsIdentifiers; |
73 | } |
74 | |
75 | void QQmlJSScope::insertJSIdentifier(const QString &name, const JavaScriptIdentifier &identifier) |
76 | { |
77 | Q_ASSERT(m_scopeType != QQmlSA::ScopeType::QMLScope); |
78 | if (identifier.kind == JavaScriptIdentifier::LexicalScoped |
79 | || identifier.kind == JavaScriptIdentifier::Injected |
80 | || m_scopeType == QQmlSA::ScopeType::JSFunctionScope) { |
81 | m_jsIdentifiers.insert(key: name, value: identifier); |
82 | } else { |
83 | auto targetScope = parentScope(); |
84 | while (targetScope->m_scopeType != QQmlSA::ScopeType::JSFunctionScope) |
85 | targetScope = targetScope->parentScope(); |
86 | targetScope->m_jsIdentifiers.insert(key: name, value: identifier); |
87 | } |
88 | } |
89 | |
90 | void QQmlJSScope::insertPropertyIdentifier(const QQmlJSMetaProperty &property) |
91 | { |
92 | addOwnProperty(prop: property); |
93 | QQmlJSMetaMethod method( |
94 | QQmlSignalNames::propertyNameToChangedSignalName(property: property.propertyName()), u"void"_s); |
95 | method.setMethodType(QQmlJSMetaMethodType::Signal); |
96 | method.setIsImplicitQmlPropertyChangeSignal(true); |
97 | addOwnMethod(method); |
98 | } |
99 | |
100 | bool QQmlJSScope::hasMethod(const QString &name) const |
101 | { |
102 | return QQmlJSUtils::searchBaseAndExtensionTypes( |
103 | type: this, check: [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) { |
104 | if (mode == QQmlJSScope::ExtensionNamespace) |
105 | return false; |
106 | return scope->m_methods.contains(key: name); |
107 | }); |
108 | } |
109 | |
110 | /*! |
111 | Returns all methods visible from this scope including those of |
112 | base types and extensions. |
113 | |
114 | \note Methods that get shadowed are not included and only the |
115 | version visible from this scope is contained. Additionally method |
116 | overrides are not included either, only the first visible version |
117 | of any method is included. |
118 | */ |
119 | QHash<QString, QQmlJSMetaMethod> QQmlJSScope::methods() const |
120 | { |
121 | QHash<QString, QQmlJSMetaMethod> results; |
122 | QQmlJSUtils::searchBaseAndExtensionTypes( |
123 | type: this, check: [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) { |
124 | if (mode == QQmlJSScope::ExtensionNamespace) |
125 | return false; |
126 | for (auto it = scope->m_methods.constBegin(); it != scope->m_methods.constEnd(); |
127 | it++) { |
128 | if (!results.contains(key: it.key())) |
129 | results.insert(key: it.key(), value: it.value()); |
130 | } |
131 | return false; |
132 | }); |
133 | |
134 | return results; |
135 | } |
136 | |
137 | QList<QQmlJSMetaMethod> QQmlJSScope::methods(const QString &name) const |
138 | { |
139 | QList<QQmlJSMetaMethod> results; |
140 | |
141 | QQmlJSUtils::searchBaseAndExtensionTypes( |
142 | type: this, check: [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) { |
143 | if (mode == QQmlJSScope::ExtensionNamespace) |
144 | return false; |
145 | results.append(other: scope->ownMethods(name)); |
146 | return false; |
147 | }); |
148 | return results; |
149 | } |
150 | |
151 | QList<QQmlJSMetaMethod> QQmlJSScope::methods(const QString &name, QQmlJSMetaMethodType type) const |
152 | { |
153 | QList<QQmlJSMetaMethod> results; |
154 | |
155 | QQmlJSUtils::searchBaseAndExtensionTypes( |
156 | type: this, check: [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) { |
157 | if (mode == QQmlJSScope::ExtensionNamespace) |
158 | return false; |
159 | const auto ownMethods = scope->ownMethods(name); |
160 | for (const auto &method : ownMethods) { |
161 | if (method.methodType() == type) |
162 | results.append(t: method); |
163 | } |
164 | return false; |
165 | }); |
166 | return results; |
167 | } |
168 | |
169 | bool QQmlJSScope::hasEnumeration(const QString &name) const |
170 | { |
171 | return QQmlJSUtils::searchBaseAndExtensionTypes( |
172 | type: this, check: [&](const QQmlJSScope *scope) { return scope->m_enumerations.contains(key: name); }); |
173 | } |
174 | |
175 | bool QQmlJSScope::hasOwnEnumerationKey(const QString &name) const |
176 | { |
177 | for (const auto &e : m_enumerations) { |
178 | if (e.keys().contains(str: name)) |
179 | return true; |
180 | } |
181 | return false; |
182 | } |
183 | |
184 | bool QQmlJSScope::hasEnumerationKey(const QString &name) const |
185 | { |
186 | return QQmlJSUtils::searchBaseAndExtensionTypes( |
187 | type: this, check: [&](const QQmlJSScope *scope) { return scope->hasOwnEnumerationKey(name); }); |
188 | } |
189 | |
190 | QQmlJSMetaEnum QQmlJSScope::enumeration(const QString &name) const |
191 | { |
192 | QQmlJSMetaEnum result; |
193 | |
194 | QQmlJSUtils::searchBaseAndExtensionTypes(type: this, check: [&](const QQmlJSScope *scope) { |
195 | const auto it = scope->m_enumerations.find(key: name); |
196 | if (it == scope->m_enumerations.end()) |
197 | return false; |
198 | result = *it; |
199 | return true; |
200 | }); |
201 | |
202 | return result; |
203 | } |
204 | |
205 | QHash<QString, QQmlJSMetaEnum> QQmlJSScope::enumerations() const |
206 | { |
207 | QHash<QString, QQmlJSMetaEnum> results; |
208 | |
209 | QQmlJSUtils::searchBaseAndExtensionTypes(type: this, check: [&](const QQmlJSScope *scope) { |
210 | for (auto it = scope->m_enumerations.constBegin(); it != scope->m_enumerations.constEnd(); |
211 | it++) { |
212 | if (!results.contains(key: it.key())) |
213 | results.insert(key: it.key(), value: it.value()); |
214 | } |
215 | return false; |
216 | }); |
217 | |
218 | return results; |
219 | } |
220 | |
221 | QString QQmlJSScope::augmentedInternalName() const |
222 | { |
223 | using namespace Qt::StringLiterals; |
224 | Q_ASSERT(!m_internalName.isEmpty()); |
225 | |
226 | switch (m_semantics) { |
227 | case AccessSemantics::Reference: |
228 | return m_internalName + " *"_L1; |
229 | case AccessSemantics::Value: |
230 | case AccessSemantics::Sequence: |
231 | break; |
232 | case AccessSemantics::None: |
233 | // If we got a namespace, it might still be a regular type, exposed as namespace. |
234 | // We may need to travel the inheritance chain all the way up to QObject to |
235 | // figure this out, since all other types may be exposed the same way. |
236 | for (QQmlJSScope::ConstPtr base = baseType(); base; base = base->baseType()) { |
237 | switch (base->accessSemantics()) { |
238 | case AccessSemantics::Reference: |
239 | return m_internalName + " *"_L1; |
240 | case AccessSemantics::Value: |
241 | case AccessSemantics::Sequence: |
242 | return m_internalName; |
243 | case AccessSemantics::None: |
244 | break; |
245 | } |
246 | } |
247 | break; |
248 | } |
249 | return m_internalName; |
250 | } |
251 | |
252 | QString QQmlJSScope::prettyName(QAnyStringView name) |
253 | { |
254 | const auto internal = "$internal$."_L1; |
255 | const QString anonymous = "$anonymous$."_L1; |
256 | |
257 | QString pretty = name.toString(); |
258 | |
259 | if (pretty.startsWith(s: internal)) |
260 | pretty = pretty.mid(position: internal.size()); |
261 | else if (pretty.startsWith(s: anonymous)) |
262 | pretty = pretty.mid(position: anonymous.size()); |
263 | |
264 | if (pretty == u"std::nullptr_t") |
265 | return u"null"_s; |
266 | |
267 | if (pretty == u"void") |
268 | return u"undefined"_s; |
269 | |
270 | return pretty; |
271 | } |
272 | |
273 | /*! |
274 | \internal |
275 | Returns true if the scope is the outermost element of a separate Component |
276 | Either because it has been implicitly wrapped, e.g. due to an assignment to |
277 | a Component property, or because it is the first (and only) child of a |
278 | Component. |
279 | For visitors: This method should only be called after implicit components |
280 | are detected, that is, after QQmlJSImportVisitor::endVisit(UiProgram *) |
281 | was called. |
282 | */ |
283 | bool QQmlJSScope::isComponentRootElement() const { |
284 | if (m_flags.testFlag(flag: WrappedInImplicitComponent)) |
285 | return true; |
286 | |
287 | auto base = nonCompositeBaseType(type: parentScope()); // handles null parentScope() |
288 | if (!base) |
289 | return false; |
290 | return base->internalName() == u"QQmlComponent"; |
291 | } |
292 | |
293 | std::optional<QQmlJSScope::JavaScriptIdentifier> |
294 | QQmlJSScope::jsIdentifier(const QString &id) const |
295 | { |
296 | for (const auto *scope = this; scope; scope = scope->parentScope().data()) { |
297 | if (scope->m_scopeType == QQmlSA::ScopeType::JSFunctionScope |
298 | || scope->m_scopeType == QQmlSA::ScopeType::JSLexicalScope) { |
299 | auto it = scope->m_jsIdentifiers.find(key: id); |
300 | if (it != scope->m_jsIdentifiers.end()) |
301 | return *it; |
302 | } |
303 | } |
304 | |
305 | return std::optional<JavaScriptIdentifier>{}; |
306 | } |
307 | |
308 | std::optional<QQmlJSScope::JavaScriptIdentifier> QQmlJSScope::ownJSIdentifier(const QString &id) const |
309 | { |
310 | auto it = m_jsIdentifiers.find(key: id); |
311 | if (it != m_jsIdentifiers.end()) |
312 | return *it; |
313 | |
314 | return std::optional<JavaScriptIdentifier>{}; |
315 | } |
316 | |
317 | static QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr> |
318 | qFindInlineComponents(QStringView typeName, const QQmlJS::ContextualTypes &contextualTypes) |
319 | { |
320 | const int separatorIndex = typeName.lastIndexOf(c: u'.'); |
321 | // do not crash in typeName.sliced() when it starts or ends with an '.'. |
322 | if (separatorIndex < 1 || separatorIndex >= typeName.size() - 1) |
323 | return {}; |
324 | |
325 | const auto parentIt = contextualTypes.types().constFind(key: typeName.first(n: separatorIndex).toString()); |
326 | if (parentIt == contextualTypes.types().constEnd()) |
327 | return {}; |
328 | |
329 | auto inlineComponentParent = *parentIt; |
330 | |
331 | // find the inline components using BFS, as inline components defined in childrens are also |
332 | // accessible from other qml documents. Same for inline components defined in a base class of |
333 | // the parent. Use BFS over DFS as the inline components are probably not deeply-nested. |
334 | |
335 | QStringView inlineComponentName = typeName.sliced(pos: separatorIndex + 1); |
336 | QQueue<QQmlJSScope::ConstPtr> candidatesForInlineComponents; |
337 | candidatesForInlineComponents.enqueue(t: inlineComponentParent.scope); |
338 | while (candidatesForInlineComponents.size()) { |
339 | QQmlJSScope::ConstPtr current = candidatesForInlineComponents.dequeue(); |
340 | if (!current) // if some type was not resolved, ignore it instead of crashing |
341 | continue; |
342 | if (current->isInlineComponent() && current->inlineComponentName() == inlineComponentName) { |
343 | return { .scope: current, .revision: inlineComponentParent.revision }; |
344 | } |
345 | // check alternatively the inline components at layer 1 in current and basetype, then at |
346 | // layer 2, etc... |
347 | candidatesForInlineComponents.append(other: current->childScopes()); |
348 | if (const auto base = current->baseType()) |
349 | candidatesForInlineComponents.enqueue(t: base); |
350 | } |
351 | return {}; |
352 | } |
353 | |
354 | /*! \internal |
355 | * Finds a type in contextualTypes with given name. |
356 | * If a type is found, then its name is inserted into usedTypes (when provided). |
357 | * If contextualTypes has mode INTERNAl, then namespace resolution for enums is |
358 | * done (eg for Qt::Alignment). |
359 | * If contextualTypes has mode QML, then inline component resolution is done |
360 | * ("qmlFileName.IC" is correctly resolved from qmlFileName). |
361 | */ |
362 | QQmlJSScope::ImportedScope<QQmlJSScope::ConstPtr> QQmlJSScope::findType( |
363 | const QString &name, const QQmlJS::ContextualTypes &contextualTypes, |
364 | QSet<QString> *usedTypes) |
365 | { |
366 | const auto useType = [&]() { |
367 | if (usedTypes != nullptr) |
368 | usedTypes->insert(value: name); |
369 | }; |
370 | |
371 | auto type = contextualTypes.types().constFind(key: name); |
372 | |
373 | if (type != contextualTypes.types().constEnd()) { |
374 | useType(); |
375 | return *type; |
376 | } |
377 | |
378 | const auto findListType = [&](const QString &prefix, const QString &postfix) |
379 | -> ImportedScope<ConstPtr> { |
380 | if (name.startsWith(s: prefix) && name.endsWith(s: postfix)) { |
381 | const qsizetype prefixLength = prefix.length(); |
382 | const QString &elementName |
383 | = name.mid(position: prefixLength, n: name.length() - prefixLength - postfix.length()); |
384 | const ImportedScope<ConstPtr> element |
385 | = findType(name: elementName, contextualTypes, usedTypes); |
386 | if (element.scope) { |
387 | useType(); |
388 | return { .scope: element.scope->listType(), .revision: element.revision }; |
389 | } |
390 | } |
391 | |
392 | return {}; |
393 | }; |
394 | |
395 | switch (contextualTypes.context()) { |
396 | case QQmlJS::ContextualTypes::INTERNAL: { |
397 | if (const auto listType = findListType(u"QList<"_s, u ">"_s); |
398 | listType.scope && !listType.scope->isReferenceType()) { |
399 | return listType; |
400 | } |
401 | |
402 | if (const auto listType = findListType(u"QQmlListProperty<"_s, u ">"_s); |
403 | listType.scope && listType.scope->isReferenceType()) { |
404 | return listType; |
405 | } |
406 | |
407 | // look for c++ namescoped enums! |
408 | const auto colonColon = name.lastIndexOf(QStringLiteral("::")); |
409 | if (colonColon == -1) |
410 | break; |
411 | |
412 | const QString outerTypeName = name.left(n: colonColon); |
413 | const auto outerType = contextualTypes.types().constFind(key: outerTypeName); |
414 | if (outerType == contextualTypes.types().constEnd()) |
415 | break; |
416 | |
417 | for (const auto &innerType : std::as_const(t: outerType->scope->m_childScopes)) { |
418 | if (innerType->m_internalName == name) { |
419 | useType(); |
420 | return { .scope: innerType, .revision: outerType->revision }; |
421 | } |
422 | } |
423 | |
424 | break; |
425 | } |
426 | case QQmlJS::ContextualTypes::QML: { |
427 | // look after inline components |
428 | const auto inlineComponent = qFindInlineComponents(typeName: name, contextualTypes); |
429 | if (inlineComponent.scope) { |
430 | useType(); |
431 | return inlineComponent; |
432 | } |
433 | |
434 | if (const auto listType = findListType(u"list<"_s, u ">"_s); listType.scope) |
435 | return listType; |
436 | |
437 | break; |
438 | } |
439 | } |
440 | return {}; |
441 | } |
442 | |
443 | QTypeRevision QQmlJSScope::resolveType( |
444 | const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &context, |
445 | QSet<QString> *usedTypes) |
446 | { |
447 | if (self->accessSemantics() == AccessSemantics::Sequence |
448 | && self->internalName().startsWith(s: u"QQmlListProperty<"_s)) { |
449 | self->setIsListProperty(true); |
450 | } |
451 | |
452 | const QString baseTypeName = self->baseTypeName(); |
453 | const auto baseType = findType(name: baseTypeName, contextualTypes: context, usedTypes); |
454 | if (!self->m_baseType.scope && !baseTypeName.isEmpty()) |
455 | self->m_baseType = { .scope: baseType.scope, .revision: baseType.revision }; |
456 | |
457 | if (!self->m_attachedType && !self->m_attachedTypeName.isEmpty()) |
458 | self->m_attachedType = findType(name: self->m_attachedTypeName, contextualTypes: context, usedTypes).scope; |
459 | |
460 | if (!self->m_valueType && !self->m_valueTypeName.isEmpty()) |
461 | self->m_valueType = findType(name: self->m_valueTypeName, contextualTypes: context, usedTypes).scope; |
462 | |
463 | if (!self->m_extensionType) { |
464 | if (self->m_extensionTypeName.isEmpty()) { |
465 | if (self->accessSemantics() == AccessSemantics::Sequence) { |
466 | // All sequence types are implicitly extended by JS Array. |
467 | self->setExtensionTypeName(u"Array"_s); |
468 | self->setExtensionIsJavaScript(true); |
469 | self->m_extensionType = context.arrayType(); |
470 | } |
471 | } else { |
472 | self->m_extensionType = findType(name: self->m_extensionTypeName, contextualTypes: context, usedTypes).scope; |
473 | } |
474 | } |
475 | |
476 | |
477 | for (auto it = self->m_properties.begin(), end = self->m_properties.end(); it != end; ++it) { |
478 | const QString typeName = it->typeName(); |
479 | if (it->type() || typeName.isEmpty()) |
480 | continue; |
481 | |
482 | if (const auto type = findType(name: typeName, contextualTypes: context, usedTypes); type.scope) { |
483 | it->setType(it->isList() ? type.scope->listType() : type.scope); |
484 | continue; |
485 | } |
486 | |
487 | const auto enumeration = self->m_enumerations.find(key: typeName); |
488 | if (enumeration != self->m_enumerations.end()) { |
489 | it->setType(it->isList() |
490 | ? enumeration->type()->listType() |
491 | : QQmlJSScope::ConstPtr(enumeration->type())); |
492 | } |
493 | } |
494 | |
495 | const auto resolveParameter = [&](QQmlJSMetaParameter ¶meter) { |
496 | if (const QString typeName = parameter.typeName(); |
497 | !parameter.type() && !typeName.isEmpty()) { |
498 | auto type = findType(name: typeName, contextualTypes: context, usedTypes); |
499 | if (type.scope && parameter.isList()) { |
500 | type.scope = type.scope->listType(); |
501 | parameter.setIsList(false); |
502 | parameter.setIsPointer(false); |
503 | parameter.setTypeName(type.scope ? type.scope->internalName() : QString()); |
504 | } else if (type.scope && type.scope->isReferenceType()) { |
505 | parameter.setIsPointer(true); |
506 | } |
507 | parameter.setType({ type.scope }); |
508 | } |
509 | }; |
510 | |
511 | for (auto it = self->m_methods.begin(), end = self->m_methods.end(); it != end; ++it) { |
512 | auto returnValue = it->returnValue(); |
513 | resolveParameter(returnValue); |
514 | it->setReturnValue(returnValue); |
515 | |
516 | auto parameters = it->parameters(); |
517 | for (int i = 0, length = parameters.size(); i < length; ++i) |
518 | resolveParameter(parameters[i]); |
519 | it->setParameters(parameters); |
520 | } |
521 | |
522 | for (auto it = self->m_jsIdentifiers.begin(); it != self->m_jsIdentifiers.end(); ++it) { |
523 | if (it->typeName) |
524 | it->scope = findType(name: it->typeName.value(), contextualTypes: context, usedTypes).scope; |
525 | } |
526 | |
527 | return baseType.revision; |
528 | } |
529 | |
530 | void QQmlJSScope::updateChildScope( |
531 | const QQmlJSScope::Ptr &childScope, const QQmlJSScope::Ptr &self, |
532 | const QQmlJS::ContextualTypes &contextualTypes, QSet<QString> *usedTypes) |
533 | { |
534 | switch (childScope->scopeType()) { |
535 | case QQmlSA::ScopeType::GroupedPropertyScope: |
536 | QQmlJSUtils::searchBaseAndExtensionTypes( |
537 | type: self.data(), check: [&](const QQmlJSScope *type, QQmlJSScope::ExtensionKind mode) { |
538 | if (mode == QQmlJSScope::ExtensionNamespace) |
539 | return false; |
540 | const auto propertyIt = type->m_properties.find(key: childScope->internalName()); |
541 | if (propertyIt != type->m_properties.end()) { |
542 | childScope->m_baseType.scope = QQmlJSScope::ConstPtr(propertyIt->type()); |
543 | if (propertyIt->type()) |
544 | childScope->m_semantics = propertyIt->type()->accessSemantics(); |
545 | childScope->setBaseTypeName(propertyIt->typeName()); |
546 | return true; |
547 | } |
548 | return false; |
549 | }); |
550 | break; |
551 | case QQmlSA::ScopeType::AttachedPropertyScope: |
552 | if (const auto attachedBase = findType( |
553 | name: childScope->internalName(), contextualTypes, usedTypes).scope) { |
554 | childScope->m_baseType.scope = attachedBase->attachedType(); |
555 | childScope->setBaseTypeName(attachedBase->attachedTypeName()); |
556 | } |
557 | break; |
558 | default: |
559 | break; |
560 | } |
561 | } |
562 | |
563 | template<typename Resolver, typename ChildScopeUpdater> |
564 | static QTypeRevision resolveTypesInternal( |
565 | Resolver resolve, ChildScopeUpdater update, const QQmlJSScope::Ptr &self, |
566 | const QQmlJS::ContextualTypes &contextualTypes, QSet<QString> *usedTypes) |
567 | { |
568 | const QTypeRevision revision = resolve(self, contextualTypes, usedTypes); |
569 | // NB: constness ensures no detach |
570 | const auto childScopes = self->childScopes(); |
571 | for (auto it = childScopes.begin(), end = childScopes.end(); it != end; ++it) { |
572 | const auto childScope = *it; |
573 | update(childScope, self, contextualTypes, usedTypes); |
574 | resolveTypesInternal(resolve, update, childScope, contextualTypes, usedTypes); // recursion |
575 | } |
576 | return revision; |
577 | } |
578 | |
579 | QTypeRevision QQmlJSScope::resolveTypes( |
580 | const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &contextualTypes, |
581 | QSet<QString> *usedTypes) |
582 | { |
583 | const auto resolveAll = [](const QQmlJSScope::Ptr &self, |
584 | const QQmlJS::ContextualTypes &contextualTypes, |
585 | QSet<QString> *usedTypes) { |
586 | resolveEnums(self, contextualTypes, usedTypes); |
587 | resolveList(self, arrayType: contextualTypes.arrayType()); |
588 | return resolveType(self, context: contextualTypes, usedTypes); |
589 | }; |
590 | return resolveTypesInternal(resolve: resolveAll, update: updateChildScope, self, contextualTypes, usedTypes); |
591 | } |
592 | |
593 | void QQmlJSScope::resolveNonEnumTypes( |
594 | const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &contextualTypes, |
595 | QSet<QString> *usedTypes) |
596 | { |
597 | resolveTypesInternal(resolve: resolveType, update: updateChildScope, self, contextualTypes, usedTypes); |
598 | } |
599 | |
600 | static QString flagStorage(const QString &underlyingType) |
601 | { |
602 | // All numeric types are builtins. Therefore we can exhaustively check the internal names. |
603 | |
604 | if (underlyingType == u"uint" |
605 | || underlyingType == u"quint8" |
606 | || underlyingType == u"ushort" |
607 | || underlyingType == u"ulonglong") { |
608 | return u"uint"_s; |
609 | } |
610 | |
611 | if (underlyingType == u"int" |
612 | || underlyingType == u"qint8" |
613 | || underlyingType == u"short" |
614 | || underlyingType == u"longlong") { |
615 | return u"int"_s; |
616 | } |
617 | |
618 | // Will fail to resolve and produce an error on usage. |
619 | // It's harmless if you never use the enum. |
620 | return QString(); |
621 | } |
622 | |
623 | /*! |
624 | \internal |
625 | Resolves all enums of self. |
626 | |
627 | Some enums happen to have an alias, e.g. when an enum is used as a flag, the enum will exist in |
628 | two versions, once as enum (e.g. Qt::MouseButton) and once as a flag (e.g. Qt::MouseButtons). In |
629 | this case, normally only the flag is exposed to the qt metatype system and tools like qmltc will |
630 | have troubles when encountering the enum in signal parameters etc. To solve this problem, |
631 | resolveEnums() will create a QQmlJSMetaEnum copy for the alias in case the 'self'-scope already |
632 | does not have an enum called like the alias. |
633 | */ |
634 | void QQmlJSScope::resolveEnums( |
635 | const QQmlJSScope::Ptr &self, const QQmlJS::ContextualTypes &contextualTypes, |
636 | QSet<QString> *usedTypes) |
637 | { |
638 | // temporary hash to avoid messing up m_enumerations while iterators are active on it |
639 | QHash<QString, QQmlJSMetaEnum> toBeAppended; |
640 | for (auto it = self->m_enumerations.begin(), end = self->m_enumerations.end(); it != end; ++it) { |
641 | if (it->type()) |
642 | continue; |
643 | QQmlJSScope::Ptr enumScope = QQmlJSScope::create(); |
644 | reparent(parentScope: self, childScope: enumScope); |
645 | enumScope->m_scopeType = QQmlSA::ScopeType::EnumScope; |
646 | |
647 | QString typeName = it->typeName(); |
648 | if (typeName.isEmpty()) |
649 | typeName = QStringLiteral("int"); |
650 | else if (it->isFlag()) |
651 | typeName = flagStorage(underlyingType: typeName); |
652 | enumScope->setBaseTypeName(typeName); |
653 | const auto type = findType(name: typeName, contextualTypes, usedTypes); |
654 | enumScope->m_baseType = { .scope: type.scope, .revision: type.revision }; |
655 | |
656 | enumScope->m_semantics = AccessSemantics::Value; |
657 | enumScope->m_internalName = self->internalName() + QStringLiteral("::") + it->name(); |
658 | if (QString alias = it->alias(); !alias.isEmpty() |
659 | && self->m_enumerations.constFind(key: alias) == self->m_enumerations.constEnd()) { |
660 | auto aliasScope = QQmlJSScope::clone(origin: enumScope); |
661 | aliasScope->m_internalName = self->internalName() + QStringLiteral("::") + alias; |
662 | QQmlJSMetaEnum cpy(*it); |
663 | cpy.setType(QQmlJSScope::ConstPtr(aliasScope)); |
664 | toBeAppended.insert(key: alias, value: cpy); |
665 | } |
666 | it->setType(QQmlJSScope::ConstPtr(enumScope)); |
667 | } |
668 | // no more iterators active on m_enumerations, so it can be changed safely now |
669 | self->m_enumerations.insert(hash: toBeAppended); |
670 | } |
671 | |
672 | void QQmlJSScope::resolveList(const QQmlJSScope::Ptr &self, const QQmlJSScope::ConstPtr &arrayType) |
673 | { |
674 | if (self->listType() || self->accessSemantics() == AccessSemantics::Sequence) |
675 | return; |
676 | |
677 | Q_ASSERT(!arrayType.isNull()); |
678 | QQmlJSScope::Ptr listType = QQmlJSScope::create(); |
679 | listType->setAccessSemantics(AccessSemantics::Sequence); |
680 | listType->setValueTypeName(self->internalName()); |
681 | |
682 | if (self->isComposite()) { |
683 | // There is no internalName for this thing. Just set the value type right away |
684 | listType->setInternalName(u"QQmlListProperty<>"_s); |
685 | listType->m_valueType = QQmlJSScope::ConstPtr(self); |
686 | } else if (self->isReferenceType()) { |
687 | listType->setInternalName(u"QQmlListProperty<%2>"_s.arg(a: self->internalName())); |
688 | // Do not set a filePath on the list type, so that we have to generalize it |
689 | // even in direct mode. |
690 | } else { |
691 | listType->setInternalName(u"QList<%2>"_s.arg(a: self->internalName())); |
692 | listType->setFilePath(self->filePath()); |
693 | } |
694 | |
695 | const QQmlJSImportedScope element = {.scope: self, .revision: QTypeRevision()}; |
696 | const QQmlJSImportedScope array = {.scope: arrayType, .revision: QTypeRevision()}; |
697 | QQmlJS::ContextualTypes contextualTypes( |
698 | QQmlJS::ContextualTypes::INTERNAL, { { self->internalName(), element }, }, |
699 | arrayType); |
700 | QQmlJSScope::resolveTypes(self: listType, contextualTypes); |
701 | |
702 | Q_ASSERT(listType->valueType() == self); |
703 | self->m_listType = listType; |
704 | } |
705 | |
706 | void QQmlJSScope::resolveGroup( |
707 | const Ptr &self, const ConstPtr &baseType, |
708 | const QQmlJS::ContextualTypes &contextualTypes, QSet<QString> *usedTypes) |
709 | { |
710 | Q_ASSERT(baseType); |
711 | // Generalized group properties are always composite, |
712 | // which means we expect contextualTypes to be QML names. |
713 | Q_ASSERT(self->isComposite()); |
714 | |
715 | self->m_baseType.scope = baseType; |
716 | self->m_semantics = baseType->accessSemantics(); |
717 | resolveNonEnumTypes(self, contextualTypes, usedTypes); |
718 | } |
719 | |
720 | QQmlJSScope::ConstPtr QQmlJSScope::findCurrentQMLScope(const QQmlJSScope::ConstPtr &scope) |
721 | { |
722 | auto qmlScope = scope; |
723 | while (qmlScope && qmlScope->m_scopeType != QQmlSA::ScopeType::QMLScope) |
724 | qmlScope = qmlScope->parentScope(); |
725 | return qmlScope; |
726 | } |
727 | |
728 | bool QQmlJSScope::hasProperty(const QString &name) const |
729 | { |
730 | return QQmlJSUtils::searchBaseAndExtensionTypes( |
731 | type: this, check: [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) { |
732 | if (mode == QQmlJSScope::ExtensionNamespace) |
733 | return false; |
734 | return scope->m_properties.contains(key: name); |
735 | }); |
736 | } |
737 | |
738 | QQmlJSMetaProperty QQmlJSScope::property(const QString &name) const |
739 | { |
740 | QQmlJSMetaProperty prop; |
741 | QQmlJSUtils::searchBaseAndExtensionTypes( |
742 | type: this, check: [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) { |
743 | if (mode == QQmlJSScope::ExtensionNamespace) |
744 | return false; |
745 | const auto it = scope->m_properties.find(key: name); |
746 | if (it == scope->m_properties.end()) |
747 | return false; |
748 | prop = *it; |
749 | return true; |
750 | }); |
751 | return prop; |
752 | } |
753 | |
754 | /*! |
755 | Returns all properties visible from this scope including those of |
756 | base types and extensions. |
757 | |
758 | \note Properties that get shadowed are not included and only the |
759 | version visible from this scope is contained. |
760 | */ |
761 | QHash<QString, QQmlJSMetaProperty> QQmlJSScope::properties() const |
762 | { |
763 | QHash<QString, QQmlJSMetaProperty> results; |
764 | QQmlJSUtils::searchBaseAndExtensionTypes( |
765 | type: this, check: [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) { |
766 | if (mode == QQmlJSScope::ExtensionNamespace) |
767 | return false; |
768 | for (auto it = scope->m_properties.constBegin(); |
769 | it != scope->m_properties.constEnd(); it++) { |
770 | if (!results.contains(key: it.key())) |
771 | results.insert(key: it.key(), value: it.value()); |
772 | } |
773 | return false; |
774 | }); |
775 | return results; |
776 | } |
777 | |
778 | QQmlJSScope::AnnotatedScope QQmlJSScope::ownerOfProperty(const QQmlJSScope::ConstPtr &self, |
779 | const QString &name) |
780 | { |
781 | QQmlJSScope::AnnotatedScope owner; |
782 | QQmlJSUtils::searchBaseAndExtensionTypes( |
783 | type: self, check: [&](const QQmlJSScope::ConstPtr &scope, QQmlJSScope::ExtensionKind mode) { |
784 | if (mode == QQmlJSScope::ExtensionNamespace) |
785 | return false; |
786 | if (scope->hasOwnProperty(name)) { |
787 | owner = { .scope: scope, .extensionSpecifier: mode }; |
788 | return true; |
789 | } |
790 | return false; |
791 | }); |
792 | return owner; |
793 | } |
794 | |
795 | void QQmlJSScope::setPropertyLocallyRequired(const QString &name, bool isRequired) |
796 | { |
797 | if (!isRequired) |
798 | m_requiredPropertyNames.removeOne(t: name); |
799 | else if (!m_requiredPropertyNames.contains(str: name)) |
800 | m_requiredPropertyNames.append(t: name); |
801 | } |
802 | |
803 | bool QQmlJSScope::isPropertyRequired(const QString &name) const |
804 | { |
805 | bool isRequired = false; |
806 | QQmlJSUtils::searchBaseAndExtensionTypes( |
807 | type: this, check: [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) { |
808 | if (scope->isPropertyLocallyRequired(name)) { |
809 | isRequired = true; |
810 | return true; |
811 | } |
812 | |
813 | // the hasOwnProperty() below only makes sense if our scope is |
814 | // not an extension namespace |
815 | if (mode == QQmlJSScope::ExtensionNamespace) |
816 | return false; |
817 | |
818 | // If it has a property of that name, and that is not required, then none of the |
819 | // base types matter. You cannot make a derived type's property required with |
820 | // a "required" specification in a base type. |
821 | return scope->hasOwnProperty(name); |
822 | }); |
823 | return isRequired; |
824 | } |
825 | |
826 | bool QQmlJSScope::isPropertyLocallyRequired(const QString &name) const |
827 | { |
828 | return m_requiredPropertyNames.contains(str: name); |
829 | } |
830 | |
831 | void QQmlJSScope::addOwnPropertyBinding(const QQmlJSMetaPropertyBinding &binding, BindingTargetSpecifier specifier) |
832 | { |
833 | Q_ASSERT(binding.sourceLocation().isValid()); |
834 | m_propertyBindings.insert(key: binding.propertyName(), value: binding); |
835 | |
836 | // NB: insert() prepends \a binding to the list of bindings, but we need |
837 | // append, so rotate |
838 | using iter = typename QMultiHash<QString, QQmlJSMetaPropertyBinding>::iterator; |
839 | QPair<iter, iter> r = m_propertyBindings.equal_range(key: binding.propertyName()); |
840 | std::rotate(first: r.first, middle: std::next(x: r.first), last: r.second); |
841 | |
842 | // additionally store bindings in the QmlIR compatible order |
843 | addOwnPropertyBindingInQmlIROrder(binding, specifier); |
844 | Q_ASSERT(m_propertyBindings.size() == m_propertyBindingsArray.size()); |
845 | } |
846 | |
847 | void QQmlJSScope::addOwnPropertyBindingInQmlIROrder(const QQmlJSMetaPropertyBinding &binding, |
848 | BindingTargetSpecifier specifier) |
849 | { |
850 | // the order: |
851 | // * ordinary bindings are prepended to the binding array |
852 | // * list bindings are properly ordered within each other, so basically |
853 | // prepended "in bulk" |
854 | // * bindings to default properties (which are not explicitly mentioned in |
855 | // binding expression) are inserted by source location's offset |
856 | |
857 | static_assert(QTypeInfo<QQmlJSScope::QmlIRCompatibilityBindingData>::isRelocatable, |
858 | "We really want T to be relocatable as it improves QList<T> performance"); |
859 | |
860 | switch (specifier) { |
861 | case BindingTargetSpecifier::SimplePropertyTarget: { |
862 | m_propertyBindingsArray.emplaceFront(args: binding.propertyName(), |
863 | args: binding.sourceLocation().offset); |
864 | break; |
865 | } |
866 | case BindingTargetSpecifier::ListPropertyTarget: { |
867 | const auto bindingOnTheSameProperty = |
868 | [&](const QQmlJSScope::QmlIRCompatibilityBindingData &x) { |
869 | return x.propertyName == binding.propertyName(); |
870 | }; |
871 | // fake "prepend in bulk" by appending a list binding to the sequence of |
872 | // bindings to the same property. there's an implicit QML language |
873 | // guarantee that such sequence does not contain arbitrary in-between |
874 | // bindings that do not belong to the same list property |
875 | auto pos = std::find_if_not(first: m_propertyBindingsArray.begin(), last: m_propertyBindingsArray.end(), |
876 | pred: bindingOnTheSameProperty); |
877 | Q_ASSERT(pos == m_propertyBindingsArray.begin() |
878 | || std::prev(pos)->propertyName == binding.propertyName()); |
879 | m_propertyBindingsArray.emplace(before: pos, args: binding.propertyName(), |
880 | args: binding.sourceLocation().offset); |
881 | break; |
882 | } |
883 | case BindingTargetSpecifier::UnnamedPropertyTarget: { |
884 | // see QmlIR::PoolList<>::findSortedInsertionPoint() |
885 | const auto findInsertionPoint = [this](const QQmlJSMetaPropertyBinding &x) { |
886 | qsizetype pos = -1; |
887 | for (auto it = m_propertyBindingsArray.cbegin(); it != m_propertyBindingsArray.cend(); |
888 | ++it) { |
889 | if (!(it->sourceLocationOffset <= x.sourceLocation().offset)) |
890 | break; |
891 | ++pos; |
892 | } |
893 | return pos; |
894 | }; |
895 | |
896 | // see QmlIR::PoolList<>::insertAfter() |
897 | const auto insertAfter = [this](qsizetype pos, const QQmlJSMetaPropertyBinding &x) { |
898 | if (pos == -1) { |
899 | m_propertyBindingsArray.emplaceFront(args: x.propertyName(), args: x.sourceLocation().offset); |
900 | } else if (pos == m_propertyBindingsArray.size()) { |
901 | m_propertyBindingsArray.emplaceBack(args: x.propertyName(), args: x.sourceLocation().offset); |
902 | } else { |
903 | // since we insert *after*, use (pos + 1) as insertion point |
904 | m_propertyBindingsArray.emplace(i: pos + 1, args: x.propertyName(), |
905 | args: x.sourceLocation().offset); |
906 | } |
907 | }; |
908 | |
909 | const qsizetype insertionPos = findInsertionPoint(binding); |
910 | insertAfter(insertionPos, binding); |
911 | break; |
912 | } |
913 | default: { |
914 | Q_UNREACHABLE(); |
915 | break; |
916 | } |
917 | } |
918 | } |
919 | |
920 | QList<QQmlJSMetaPropertyBinding> QQmlJSScope::ownPropertyBindingsInQmlIROrder() const |
921 | { |
922 | QList<QQmlJSMetaPropertyBinding> qmlIrOrdered; |
923 | qmlIrOrdered.reserve(asize: m_propertyBindingsArray.size()); |
924 | |
925 | for (const auto &data : m_propertyBindingsArray) { |
926 | const auto [first, last] = m_propertyBindings.equal_range(key: data.propertyName); |
927 | Q_ASSERT(first != last); |
928 | auto binding = std::find_if(first: first, last: last, pred: [&](const QQmlJSMetaPropertyBinding &x) { |
929 | return x.sourceLocation().offset == data.sourceLocationOffset; |
930 | }); |
931 | Q_ASSERT(binding != last); |
932 | qmlIrOrdered.append(t: *binding); |
933 | } |
934 | |
935 | return qmlIrOrdered; |
936 | } |
937 | |
938 | bool QQmlJSScope::hasPropertyBindings(const QString &name) const |
939 | { |
940 | return QQmlJSUtils::searchBaseAndExtensionTypes( |
941 | type: this, check: [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) { |
942 | if (mode != QQmlJSScope::NotExtension) { |
943 | Q_ASSERT(!scope->hasOwnPropertyBindings(name)); |
944 | return false; |
945 | } |
946 | return scope->hasOwnPropertyBindings(name); |
947 | }); |
948 | } |
949 | |
950 | QList<QQmlJSMetaPropertyBinding> QQmlJSScope::propertyBindings(const QString &name) const |
951 | { |
952 | QList<QQmlJSMetaPropertyBinding> bindings; |
953 | QQmlJSUtils::searchBaseAndExtensionTypes( |
954 | type: this, check: [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) { |
955 | if (mode != QQmlJSScope::NotExtension) { |
956 | Q_ASSERT(!scope->hasOwnPropertyBindings(name)); |
957 | return false; |
958 | } |
959 | const auto range = scope->ownPropertyBindings(name); |
960 | for (auto it = range.first; it != range.second; ++it) |
961 | bindings.append(t: *it); |
962 | return false; |
963 | }); |
964 | return bindings; |
965 | } |
966 | |
967 | bool QQmlJSScope::hasInterface(const QString &name) const |
968 | { |
969 | return QQmlJSUtils::searchBaseAndExtensionTypes( |
970 | type: this, check: [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) { |
971 | if (mode != QQmlJSScope::NotExtension) |
972 | return false; |
973 | return scope->m_interfaceNames.contains(str: name); |
974 | }); |
975 | } |
976 | |
977 | bool QQmlJSScope::isNameDeferred(const QString &name) const |
978 | { |
979 | bool isDeferred = false; |
980 | |
981 | QQmlJSUtils::searchBaseAndExtensionTypes(type: this, check: [&](const QQmlJSScope *scope) { |
982 | const QStringList immediate = scope->ownImmediateNames(); |
983 | if (!immediate.isEmpty()) { |
984 | isDeferred = !immediate.contains(str: name); |
985 | return true; |
986 | } |
987 | const QStringList deferred = scope->ownDeferredNames(); |
988 | if (!deferred.isEmpty()) { |
989 | isDeferred = deferred.contains(str: name); |
990 | return true; |
991 | } |
992 | return false; |
993 | }); |
994 | |
995 | return isDeferred; |
996 | } |
997 | |
998 | void QQmlJSScope::setBaseTypeName(const QString &baseTypeName) |
999 | { |
1000 | m_flags.setFlag(flag: HasBaseTypeError, on: false); |
1001 | m_baseTypeNameOrError = baseTypeName; |
1002 | } |
1003 | |
1004 | QString QQmlJSScope::baseTypeName() const |
1005 | { |
1006 | return m_flags.testFlag(flag: HasBaseTypeError) ? QString() : m_baseTypeNameOrError; |
1007 | } |
1008 | |
1009 | void QQmlJSScope::setBaseTypeError(const QString &baseTypeError) |
1010 | { |
1011 | m_flags.setFlag(flag: HasBaseTypeError); |
1012 | m_baseTypeNameOrError = baseTypeError; |
1013 | } |
1014 | |
1015 | /*! |
1016 | \internal |
1017 | The name of the module is only saved in the QmlComponent. Iterate through the parent scopes until |
1018 | the QmlComponent or the root is reached to find out the module name of the component in which `this` |
1019 | resides. |
1020 | */ |
1021 | QString QQmlJSScope::moduleName() const |
1022 | { |
1023 | for (const QQmlJSScope *it = this; it; it = it->parentScope().get()) { |
1024 | const QString name = it->ownModuleName(); |
1025 | if (!name.isEmpty()) |
1026 | return name; |
1027 | } |
1028 | return {}; |
1029 | } |
1030 | |
1031 | QString QQmlJSScope::baseTypeError() const |
1032 | { |
1033 | return m_flags.testFlag(flag: HasBaseTypeError) ? m_baseTypeNameOrError : QString(); |
1034 | } |
1035 | |
1036 | QString QQmlJSScope::attachedTypeName() const |
1037 | { |
1038 | QString name; |
1039 | QQmlJSUtils::searchBaseAndExtensionTypes( |
1040 | type: this, check: [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) { |
1041 | if (mode != QQmlJSScope::NotExtension) |
1042 | return false; |
1043 | if (scope->ownAttachedType().isNull()) |
1044 | return false; |
1045 | name = scope->ownAttachedTypeName(); |
1046 | return true; |
1047 | }); |
1048 | |
1049 | return name; |
1050 | } |
1051 | |
1052 | QQmlJSScope::ConstPtr QQmlJSScope::attachedType() const |
1053 | { |
1054 | QQmlJSScope::ConstPtr ptr; |
1055 | QQmlJSUtils::searchBaseAndExtensionTypes( |
1056 | type: this, check: [&](const QQmlJSScope *scope, QQmlJSScope::ExtensionKind mode) { |
1057 | if (mode != QQmlJSScope::NotExtension) |
1058 | return false; |
1059 | if (scope->ownAttachedType().isNull()) |
1060 | return false; |
1061 | ptr = scope->ownAttachedType(); |
1062 | return true; |
1063 | }); |
1064 | |
1065 | return ptr; |
1066 | } |
1067 | |
1068 | QQmlJSScope::AnnotatedScope QQmlJSScope::extensionType() const |
1069 | { |
1070 | if (!m_extensionType) |
1071 | return { .scope: m_extensionType, .extensionSpecifier: NotExtension }; |
1072 | if (m_flags & ExtensionIsJavaScript) |
1073 | return { .scope: m_extensionType, .extensionSpecifier: ExtensionJavaScript }; |
1074 | if (m_flags & ExtensionIsNamespace) |
1075 | return { .scope: m_extensionType, .extensionSpecifier: ExtensionNamespace }; |
1076 | return { .scope: m_extensionType, .extensionSpecifier: ExtensionType }; |
1077 | } |
1078 | |
1079 | void QQmlJSScope::addOwnRuntimeFunctionIndex(QQmlJSMetaMethod::AbsoluteFunctionIndex index) |
1080 | { |
1081 | m_runtimeFunctionIndices.emplaceBack(args&: index); |
1082 | } |
1083 | |
1084 | bool QQmlJSScope::isResolved() const |
1085 | { |
1086 | const bool nameIsEmpty = (m_scopeType == ScopeType::AttachedPropertyScope |
1087 | || m_scopeType == ScopeType::GroupedPropertyScope) |
1088 | ? m_internalName.isEmpty() |
1089 | : m_baseTypeNameOrError.isEmpty(); |
1090 | if (nameIsEmpty) |
1091 | return true; |
1092 | if (m_baseType.scope.isNull()) |
1093 | return false; |
1094 | if (isComposite() && !nonCompositeBaseType(type: baseType())) |
1095 | return false; |
1096 | return true; |
1097 | } |
1098 | |
1099 | QString QQmlJSScope::defaultPropertyName() const |
1100 | { |
1101 | QString name; |
1102 | QQmlJSUtils::searchBaseAndExtensionTypes(type: this, check: [&](const QQmlJSScope *scope) { |
1103 | name = scope->ownDefaultPropertyName(); |
1104 | return !name.isEmpty(); |
1105 | }); |
1106 | return name; |
1107 | } |
1108 | |
1109 | QString QQmlJSScope::parentPropertyName() const |
1110 | { |
1111 | QString name; |
1112 | QQmlJSUtils::searchBaseAndExtensionTypes(type: this, check: [&](const QQmlJSScope *scope) { |
1113 | name = scope->ownParentPropertyName(); |
1114 | return !name.isEmpty(); |
1115 | }); |
1116 | return name; |
1117 | } |
1118 | |
1119 | bool QQmlJSScope::isFullyResolved() const |
1120 | { |
1121 | bool baseResolved = true; |
1122 | QQmlJSUtils::searchBaseAndExtensionTypes(type: this, check: [&](const QQmlJSScope *scope) { |
1123 | if (!scope->isResolved()) { |
1124 | baseResolved = false; |
1125 | return true; |
1126 | } |
1127 | return false; |
1128 | }); |
1129 | |
1130 | return baseResolved; |
1131 | } |
1132 | |
1133 | QQmlJSScope::Export::Export( |
1134 | QString package, QString type, QTypeRevision version, QTypeRevision revision) |
1135 | : m_package(std::move(package)) |
1136 | , m_type(std::move(type)) |
1137 | , m_version(std::move(version)) |
1138 | , m_revision(std::move(revision)) |
1139 | { |
1140 | } |
1141 | |
1142 | bool QQmlJSScope::Export::isValid() const |
1143 | { |
1144 | return m_version.isValid() || !m_package.isEmpty() || !m_type.isEmpty(); |
1145 | } |
1146 | |
1147 | QDeferredFactory<QQmlJSScope>::QDeferredFactory(QQmlJSImporter *importer, const QString &filePath, |
1148 | const TypeReader &typeReader) |
1149 | : m_filePath(filePath), |
1150 | m_importer(importer), |
1151 | m_typeReader(typeReader ? typeReader |
1152 | : [](QQmlJSImporter *importer, const QString &filePath, |
1153 | const QSharedPointer<QQmlJSScope> &scopeToPopulate) { |
1154 | QQmlJSTypeReader defaultTypeReader(importer, filePath); |
1155 | defaultTypeReader(scopeToPopulate); |
1156 | return defaultTypeReader.errors(); |
1157 | }) |
1158 | { |
1159 | } |
1160 | |
1161 | void QDeferredFactory<QQmlJSScope>::populate(const QSharedPointer<QQmlJSScope> &scope) const |
1162 | { |
1163 | scope->setOwnModuleName(m_moduleName); |
1164 | scope->setFilePath(m_filePath); |
1165 | |
1166 | QList<QQmlJS::DiagnosticMessage> errors = m_typeReader(m_importer, m_filePath, scope); |
1167 | m_importer->m_globalWarnings.append(l: errors); |
1168 | |
1169 | scope->setInternalName(internalName()); |
1170 | QQmlJSScope::resolveEnums( |
1171 | self: scope, contextualTypes: m_importer->builtinInternalNames().contextualTypes()); |
1172 | QQmlJSScope::resolveList( |
1173 | self: scope, arrayType: m_importer->builtinInternalNames().contextualTypes().arrayType()); |
1174 | |
1175 | if (m_isSingleton && !scope->isSingleton()) { |
1176 | m_importer->m_globalWarnings.append( |
1177 | t: { QStringLiteral( |
1178 | "Type %1 declared as singleton in qmldir but missing pragma Singleton") |
1179 | .arg(a: scope->internalName()), |
1180 | .type: QtCriticalMsg, .loc: QQmlJS::SourceLocation() }); |
1181 | scope->setIsSingleton(true); |
1182 | } else if (!m_isSingleton && scope->isSingleton()) { |
1183 | m_importer->m_globalWarnings.append( |
1184 | t: { QStringLiteral("Type %1 not declared as singleton in qmldir " |
1185 | "but using pragma Singleton") |
1186 | .arg(a: scope->internalName()), |
1187 | .type: QtCriticalMsg, .loc: QQmlJS::SourceLocation() }); |
1188 | scope->setIsSingleton(false); |
1189 | } |
1190 | } |
1191 | |
1192 | /*! |
1193 | \internal |
1194 | Checks whether \a derived type can be assigned to this type. Returns \c |
1195 | true if the type hierarchy of \a derived contains a type equal to this. |
1196 | |
1197 | \note Assigning \a derived to "QVariant" or "QJSValue" is always possible and |
1198 | the function returns \c true in this case. In addition any "QObject" based \a derived type |
1199 | can be assigned to a this type if that type is derived from "QQmlComponent". |
1200 | */ |
1201 | bool QQmlJSScope::canAssign(const QQmlJSScope::ConstPtr &derived) const |
1202 | { |
1203 | if (!derived) |
1204 | return false; |
1205 | |
1206 | // expect this and derived types to have non-composite bases |
1207 | Q_ASSERT(!isComposite() || nonCompositeBaseType(baseType())); |
1208 | Q_ASSERT(nonCompositeBaseType(derived)); |
1209 | |
1210 | // the logic with isBaseComponent (as well as the way we set this flag) |
1211 | // feels wrong - QTBUG-101940 |
1212 | const bool isBaseComponent = [this]() { |
1213 | if (internalName() == u"QQmlComponent") |
1214 | return true; |
1215 | else if (isComposite()) |
1216 | return false; |
1217 | for (auto cppBase = nonCompositeBaseType(type: baseType()); cppBase; |
1218 | cppBase = cppBase->baseType()) { |
1219 | if (cppBase->internalName() == u"QQmlAbstractDelegateComponent") |
1220 | return true; |
1221 | } |
1222 | return false; |
1223 | }(); |
1224 | |
1225 | QDuplicateTracker<QQmlJSScope::ConstPtr> seen; |
1226 | for (auto scope = derived; !scope.isNull() && !seen.hasSeen(s: scope); |
1227 | scope = scope->baseType()) { |
1228 | if (isSameType(otherScope: scope)) |
1229 | return true; |
1230 | if (isBaseComponent && scope->internalName() == u"QObject"_s) |
1231 | return true; |
1232 | } |
1233 | |
1234 | if (internalName() == u"QVariant"_s|| internalName() == u "QJSValue"_s) |
1235 | return true; |
1236 | |
1237 | return isListProperty() && valueType()->canAssign(derived); |
1238 | } |
1239 | |
1240 | /*! |
1241 | \internal |
1242 | Checks whether this type or its parents have a custom parser. |
1243 | */ |
1244 | bool QQmlJSScope::isInCustomParserParent() const |
1245 | { |
1246 | for (const auto *scope = this; scope; scope = scope->parentScope().get()) { |
1247 | if (!scope->baseType().isNull() && scope->baseType()->hasCustomParser()) |
1248 | return true; |
1249 | } |
1250 | |
1251 | return false; |
1252 | } |
1253 | |
1254 | /*! |
1255 | * \internal |
1256 | * if this->isInlineComponent(), then this getter returns the name of the inline |
1257 | * component. |
1258 | */ |
1259 | std::optional<QString> QQmlJSScope::inlineComponentName() const |
1260 | { |
1261 | Q_ASSERT(isInlineComponent() == m_inlineComponentName.has_value()); |
1262 | return m_inlineComponentName; |
1263 | } |
1264 | |
1265 | /*! |
1266 | * \internal |
1267 | * If this type is part of an inline component, return its name. Otherwise, if this type |
1268 | * is part of the document root, return the document root name. |
1269 | */ |
1270 | QQmlJSScope::InlineComponentOrDocumentRootName QQmlJSScope::enclosingInlineComponentName() const |
1271 | { |
1272 | for (auto *type = this; type; type = type->parentScope().get()) { |
1273 | if (type->isInlineComponent()) |
1274 | return *type->inlineComponentName(); |
1275 | } |
1276 | return RootDocumentNameType(); |
1277 | } |
1278 | |
1279 | QVector<QQmlJSScope::ConstPtr> QQmlJSScope::childScopes() const |
1280 | { |
1281 | QVector<QQmlJSScope::ConstPtr> result; |
1282 | result.reserve(asize: m_childScopes.size()); |
1283 | for (const auto &child : m_childScopes) |
1284 | result.append(t: child); |
1285 | return result; |
1286 | } |
1287 | |
1288 | /*! |
1289 | \internal |
1290 | |
1291 | Returns true if this type or any base type of it has the "EnforcesScopedEnums" flag. |
1292 | The rationale is that you can turn on enforcement of scoped enums, but you cannot turn |
1293 | it off explicitly. |
1294 | */ |
1295 | bool QQmlJSScope::enforcesScopedEnums() const |
1296 | { |
1297 | for (const QQmlJSScope *scope = this; scope; scope = scope->baseType().get()) { |
1298 | if (scope->hasEnforcesScopedEnumsFlag()) |
1299 | return true; |
1300 | } |
1301 | return false; |
1302 | } |
1303 | |
1304 | /*! |
1305 | \internal |
1306 | Returns true if the current type is creatable by checking all the required base classes. |
1307 | "Uncreatability" is only inherited from base types for composite types (in qml) and not for non-composite types (c++). |
1308 | |
1309 | For the exact definition: |
1310 | A type is uncreatable if and only if one of its composite base type or its first non-composite base type matches |
1311 | following criteria: |
1312 | \list |
1313 | \li the base type is a singleton, or |
1314 | \li the base type is an attached type, or |
1315 | \li the base type is a C++ type with the QML_UNCREATABLE or QML_ANONYMOUS macro, or |
1316 | \li the base type is a type without default constructor (in that case, it really needs QML_UNCREATABLE or QML_ANONYMOUS) |
1317 | \endlist |
1318 | */ |
1319 | bool QQmlJSScope::isCreatable() const |
1320 | { |
1321 | auto isCreatableNonRecursive = [](const QQmlJSScope *scope) { |
1322 | return scope->hasCreatableFlag() && !scope->isSingleton() |
1323 | && scope->scopeType() == QQmlSA::ScopeType::QMLScope; |
1324 | }; |
1325 | |
1326 | for (const QQmlJSScope* scope = this; scope; scope = scope->baseType().get()) { |
1327 | if (!scope->isComposite()) { |
1328 | // just check the first nonComposite (c++) base for isCreatableNonRecursive() and then stop |
1329 | return isCreatableNonRecursive(scope); |
1330 | } else { |
1331 | // check all composite (qml) bases for isCreatableNonRecursive(). |
1332 | if (isCreatableNonRecursive(scope)) |
1333 | return true; |
1334 | } |
1335 | } |
1336 | // no uncreatable bases found |
1337 | return false; |
1338 | } |
1339 | |
1340 | bool QQmlJSScope::isStructured() const |
1341 | { |
1342 | for (const QQmlJSScope *scope = this; scope; scope = scope->baseType().get()) { |
1343 | if (!scope->isComposite()) |
1344 | return scope->hasStructuredFlag(); |
1345 | } |
1346 | return false; |
1347 | } |
1348 | |
1349 | QQmlSA::Element QQmlJSScope::createQQmlSAElement(const ConstPtr &ptr) |
1350 | { |
1351 | QQmlSA::Element element; |
1352 | *reinterpret_cast<QQmlJSScope::ConstPtr *>(element.m_data) = ptr; |
1353 | return element; |
1354 | } |
1355 | |
1356 | QQmlSA::Element QQmlJSScope::createQQmlSAElement(ConstPtr &&ptr) |
1357 | { |
1358 | QQmlSA::Element element; |
1359 | *reinterpret_cast<QQmlJSScope::ConstPtr *>(element.m_data) = std::move(ptr); |
1360 | return element; |
1361 | } |
1362 | |
1363 | const QQmlJSScope::ConstPtr &QQmlJSScope::scope(const QQmlSA::Element &element) |
1364 | { |
1365 | return *reinterpret_cast<const QQmlJSScope::ConstPtr *>(element.m_data); |
1366 | } |
1367 | |
1368 | QTypeRevision |
1369 | QQmlJSScope::nonCompositeBaseRevision(const ImportedScope<QQmlJSScope::ConstPtr> &scope) |
1370 | { |
1371 | for (auto base = scope; base.scope; |
1372 | base = { .scope: base.scope->m_baseType.scope, .revision: base.scope->m_baseType.revision }) { |
1373 | if (!base.scope->isComposite()) |
1374 | return base.revision; |
1375 | } |
1376 | return {}; |
1377 | } |
1378 | |
1379 | /*! |
1380 | \internal |
1381 | Checks whether \a otherScope is the same type as this. |
1382 | |
1383 | In addition to checking whether the scopes are identical, we also cover duplicate scopes with |
1384 | the same internal name. |
1385 | */ |
1386 | bool QQmlJSScope::isSameType(const ConstPtr &otherScope) const |
1387 | { |
1388 | return this == otherScope.get() |
1389 | || (!this->internalName().isEmpty() |
1390 | && this->internalName() == otherScope->internalName()); |
1391 | } |
1392 | |
1393 | bool QQmlJSScope::inherits(const ConstPtr &base) const |
1394 | { |
1395 | for (const QQmlJSScope *scope = this; scope; scope = scope->baseType().get()) { |
1396 | if (scope->isSameType(otherScope: base)) |
1397 | return true; |
1398 | } |
1399 | return false; |
1400 | } |
1401 | |
1402 | |
1403 | QT_END_NAMESPACE |
1404 |
Definitions
- QQmlJSScope
- create
- reparent
- clone
- ownJSIdentifiers
- insertJSIdentifier
- insertPropertyIdentifier
- hasMethod
- methods
- methods
- methods
- hasEnumeration
- hasOwnEnumerationKey
- hasEnumerationKey
- enumeration
- enumerations
- augmentedInternalName
- prettyName
- isComponentRootElement
- jsIdentifier
- ownJSIdentifier
- qFindInlineComponents
- findType
- resolveType
- updateChildScope
- resolveTypesInternal
- resolveTypes
- resolveNonEnumTypes
- flagStorage
- resolveEnums
- resolveList
- resolveGroup
- findCurrentQMLScope
- hasProperty
- property
- properties
- ownerOfProperty
- setPropertyLocallyRequired
- isPropertyRequired
- isPropertyLocallyRequired
- addOwnPropertyBinding
- addOwnPropertyBindingInQmlIROrder
- ownPropertyBindingsInQmlIROrder
- hasPropertyBindings
- propertyBindings
- hasInterface
- isNameDeferred
- setBaseTypeName
- baseTypeName
- setBaseTypeError
- moduleName
- baseTypeError
- attachedTypeName
- attachedType
- extensionType
- addOwnRuntimeFunctionIndex
- isResolved
- defaultPropertyName
- parentPropertyName
- isFullyResolved
- Export
- isValid
- QDeferredFactory
- populate
- canAssign
- isInCustomParserParent
- inlineComponentName
- enclosingInlineComponentName
- childScopes
- enforcesScopedEnums
- isCreatable
- isStructured
- createQQmlSAElement
- createQQmlSAElement
- scope
- nonCompositeBaseRevision
- isSameType
Learn Advanced QML with KDAB
Find out more