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