Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlcomponentandaliasresolver_p.h
Go to the documentation of this file.
1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3#ifndef QQMLCOMPONENTANDALIASRESOLVER_P_H
4#define QQMLCOMPONENTANDALIASRESOLVER_P_H
5
6//
7// W A R N I N G
8// -------------
9//
10// This file is not part of the Qt API. It exists purely as an
11// implementation detail. This header file may change from version to
12// version without notice, or even be removed.
13//
14// We mean it.
15//
16
17#include <QtQml/qqmlcomponent.h>
18#include <QtQml/qqmlerror.h>
19
20#include <QtCore/qglobal.h>
21#include <QtCore/qhash.h>
22
23#include <private/qqmltypeloader_p.h>
24#include <private/qqmlpropertyresolver_p.h>
25#include <private/qqmlpropertycachecreator_p.h>
26
28
30
31// This class primarily resolves component boundaries in a document.
32// With the information about boundaries, it then goes on to resolve aliases and generalized
33// group properties. Both rely on IDs as first part of their expressions and the IDs have
34// to be located in surrounding components. That's why we have to do this with the component
35// boundaries in mind.
36
41
42template<typename ObjectContainer>
44{
45public:
46 using CompiledObject = typename ObjectContainer::CompiledObject;
47 using CompiledBinding = typename ObjectContainer::CompiledBinding;
48
50 ObjectContainer *compiler,
51 QQmlPropertyCacheVector *propertyCaches);
52
53 [[nodiscard]] QQmlError resolve(int root = 0);
54
55private:
56 enum AliasResolutionResult {
57 NoAliasResolved,
58 SomeAliasesResolved,
59 AllAliasesResolved
60 };
61
62 // To be specialized for each container
63 void allocateNamedObjects(CompiledObject *object) const;
64 void setObjectId(int index) const;
65 [[nodiscard]] bool markAsComponent(int index) const;
66 [[nodiscard]] AliasResolutionResult resolveAliasesInObject(
67 const CompiledObject &component, int objectIndex,
68 QQmlPropertyCacheAliasCreator<ObjectContainer> *aliasCacheCreator, QQmlError *error);
69 [[nodiscard]] bool appendAliasToPropertyCache(
70 const CompiledObject *component, const QV4::CompiledData::Alias *alias, int objectIndex,
71 int aliasIndex, int encodedPropertyIndex,
72 QQmlPropertyCacheAliasCreator<ObjectContainer> *aliasCacheCreator, QQmlError *error)
73 {
74 *error = aliasCacheCreator->appendAliasToPropertyCache(
75 *component, *alias, objectIndex, aliasIndex, encodedPropertyIndex);
76 resolvedAliases.insert(alias);
77 return !error->isValid();
78 }
79
80
81 void resolveGeneralizedGroupProperty(const CompiledObject &component, CompiledBinding *binding);
82 [[nodiscard]] bool wrapImplicitComponent(CompiledBinding *binding);
83
84 [[nodiscard]] QQmlError findAndRegisterImplicitComponents(
85 const CompiledObject *obj, const QQmlPropertyCache::ConstPtr &propertyCache);
86 [[nodiscard]] QQmlError collectIdsAndAliases(int objectIndex);
87 [[nodiscard]] QQmlError resolveAliases(int componentIndex);
88 void resolveGeneralizedGroupProperties(int componentIndex);
89 [[nodiscard]] QQmlError resolveComponentsInInlineComponentRoot(int root);
90
91 QString stringAt(int idx) const { return m_compiler->stringAt(idx); }
92 QV4::ResolvedTypeReference *resolvedType(int id) const { return m_compiler->resolvedType(id); }
93
94 [[nodiscard]] QQmlError error(
95 const QV4::CompiledData::Location &location,
96 const QString &description)
97 {
98 QQmlError error;
99 error.setLine(qmlConvertSourceCoordinate<quint32, int>(location.line()));
100 error.setColumn(qmlConvertSourceCoordinate<quint32, int>(location.column()));
101 error.setDescription(description);
102 error.setUrl(m_compiler->url());
103 return error;
104 }
105
106 template<typename Token>
107 [[nodiscard]] QQmlError error(Token token, const QString &description)
108 {
109 return error(token->location, description);
110 }
111
112 static bool isUsableComponent(const QMetaObject *metaObject)
113 {
114 // The metaObject is a component we're interested in if it either is a QQmlComponent itself
115 // or if any of its parents is a QQmlAbstractDelegateComponent. We don't want to include
116 // qqmldelegatecomponent_p.h because it belongs to QtQmlModels.
117
118 if (metaObject == &QQmlComponent::staticMetaObject)
119 return true;
120
121 for (; metaObject; metaObject = metaObject->superClass()) {
122 if (qstrcmp(metaObject->className(), "QQmlAbstractDelegateComponent") == 0)
123 return true;
124 }
125
126 return false;
127 }
128
129 ObjectContainer *m_compiler = nullptr;
130
131 // Implicit component insertion may have added objects and thus we also need
132 // to extend the symmetric propertyCaches. Therefore, non-const propertyCaches.
133 QQmlPropertyCacheVector *m_propertyCaches = nullptr;
134
135 // indices of the objects that are actually Component {}
136 QVector<quint32> m_componentRoots;
137 QVector<int> m_objectsWithAliases;
138 QVector<CompiledBinding *> m_generalizedGroupProperties;
139 QSet<const QV4::CompiledData::Alias *> resolvedAliases;
140 typename ObjectContainer::IdToObjectMap m_idToObjectIndex;
141};
142
143template<typename ObjectContainer>
145 ObjectContainer *compiler,
146 QQmlPropertyCacheVector *propertyCaches)
147 : m_compiler(compiler)
149{
150}
151
152template<typename ObjectContainer>
153QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::findAndRegisterImplicitComponents(
154 const CompiledObject *obj, const QQmlPropertyCache::ConstPtr &propertyCache)
155{
156 QQmlPropertyResolver propertyResolver(propertyCache);
157
158 const QQmlPropertyData *defaultProperty = obj->indexOfDefaultPropertyOrAlias != -1
159 ? propertyCache->parent()->defaultProperty()
160 : propertyCache->defaultProperty();
161
162 for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd(); binding != end; ++binding) {
163 if (binding->type() != QV4::CompiledData::Binding::Type_Object)
164 continue;
165 if (binding->hasFlag(QV4::CompiledData::Binding::IsSignalHandlerObject))
166 continue;
167
168 auto targetObject = m_compiler->objectAt(binding->value.objectIndex);
169 auto typeReference = resolvedType(targetObject->inheritedTypeNameIndex);
170 Q_ASSERT(typeReference);
171
172 const QMetaObject *firstMetaObject = nullptr;
173 if (const auto propCache = typeReference->typePropertyCache())
174 firstMetaObject = propCache->firstCppMetaObject();
175 if (isUsableComponent(firstMetaObject))
176 continue;
177
178 // if here, not a QQmlComponent, so needs wrapping
179 const QQmlPropertyData *pd = nullptr;
180 if (binding->propertyNameIndex != quint32(0)) {
181 bool notInRevision = false;
182 pd = propertyResolver.property(stringAt(binding->propertyNameIndex), &notInRevision);
183 } else {
184 pd = defaultProperty;
185 }
186 if (!pd || !pd->isQObject())
187 continue;
188
189 // If the version is given, use it and look up by QQmlType.
190 // Otherwise, make sure we look up by metaobject.
191 // TODO: Is this correct?
192 QQmlPropertyCache::ConstPtr pc = pd->typeVersion().hasMinorVersion()
193 ? QQmlMetaType::rawPropertyCacheForType(pd->propType(), pd->typeVersion())
194 : QQmlMetaType::rawPropertyCacheForType(pd->propType());
195 const QMetaObject *mo = pc ? pc->firstCppMetaObject() : nullptr;
196 while (mo) {
197 if (mo == &QQmlComponent::staticMetaObject)
198 break;
199 mo = mo->superClass();
200 }
201
202 if (!mo)
203 continue;
204
205 if (!wrapImplicitComponent(binding))
206 return error(binding, QQmlComponentAndAliasResolverBase::tr("Cannot wrap implicit component"));
207 }
208
209 return QQmlError();
210}
211
212template<typename ObjectContainer>
213QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::resolveComponentsInInlineComponentRoot(
214 int root)
215{
216 // Find implicit components in the inline component itself. Also warn about inline
217 // components being explicit components.
218
219 const auto rootObj = m_compiler->objectAt(root);
220 Q_ASSERT(rootObj->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot));
221
222 if (const int typeName = rootObj->inheritedTypeNameIndex) {
223 const auto *tref = resolvedType(typeName);
224 Q_ASSERT(tref);
225 if (tref->type().metaObject() == &QQmlComponent::staticMetaObject) {
226 qCWarning(lcQmlTypeCompiler).nospace().noquote()
227 << m_compiler->url().toString() << ":" << rootObj->location.line() << ":"
228 << rootObj->location.column()
229 << ": Using a Component as the root of an inline component is deprecated: "
230 "inline components are "
231 "automatically wrapped into Components when needed.";
232 return QQmlError();
233 }
234 }
235
236 const QQmlPropertyCache::ConstPtr rootCache = m_propertyCaches->at(root);
237 Q_ASSERT(rootCache);
238
239 return findAndRegisterImplicitComponents(rootObj, rootCache);
240}
241
242// Resolve ignores everything relating to inline components, except for implicit components.
243template<typename ObjectContainer>
245{
246 // Detect real Component {} objects as well as implicitly defined components, such as
247 // someItemDelegate: Item {}
248 // In the implicit case Item is surrounded by a synthetic Component {} because the property
249 // on the left hand side is of QQmlComponent type.
250 const int objCountWithoutSynthesizedComponents = m_compiler->objectCount();
251
252 if (root != 0) {
253 const QQmlError error = resolveComponentsInInlineComponentRoot(root);
254 if (error.isValid())
255 return error;
256 }
257
258 // root+1, as ic root is handled at the end
259 const int startObjectIndex = root == 0 ? root : root+1;
260
261 for (int i = startObjectIndex; i < objCountWithoutSynthesizedComponents; ++i) {
262 auto obj = m_compiler->objectAt(i);
263 const bool isInlineComponentRoot
264 = obj->hasFlag(QV4::CompiledData::Object::IsInlineComponentRoot);
265 const bool isPartOfInlineComponent
266 = obj->hasFlag(QV4::CompiledData::Object::IsPartOfInlineComponent);
267 QQmlPropertyCache::ConstPtr cache = m_propertyCaches->at(i);
268
269 if (root == 0) {
270 // normal component root, skip over anything inline component related
271 if (isInlineComponentRoot || isPartOfInlineComponent)
272 continue;
273 } else if (!isPartOfInlineComponent || isInlineComponentRoot) {
274 // When handling an inline component, stop where the inline component ends
275 // Note: We do not support nested inline components. Therefore, isInlineComponentRoot
276 // tells us that the element after the current inline component is again an
277 // inline component
278 break;
279 }
280
281 if (obj->inheritedTypeNameIndex == 0 && !cache)
282 continue;
283
284 bool isExplicitComponent = false;
285 if (obj->inheritedTypeNameIndex) {
286 auto *tref = resolvedType(obj->inheritedTypeNameIndex);
287 Q_ASSERT(tref);
288 if (tref->type().metaObject() == &QQmlComponent::staticMetaObject)
289 isExplicitComponent = true;
290 }
291
292 if (!isExplicitComponent) {
293 if (cache) {
294 const QQmlError error = findAndRegisterImplicitComponents(obj, cache);
295 if (error.isValid())
296 return error;
297 }
298 continue;
299 }
300
301 if (!markAsComponent(i))
302 return error(obj, QQmlComponentAndAliasResolverBase::tr("Cannot mark object as component"));
303
304 // check if this object is the root
305 if (i == 0) {
306 if (isExplicitComponent)
307 qCWarning(lcQmlTypeCompiler).nospace().noquote()
308 << m_compiler->url().toString() << ":" << obj->location.line() << ":"
309 << obj->location.column()
310 << ": Using a Component as the root of a QML document is deprecated: types "
311 "defined in qml documents are "
312 "automatically wrapped into Components when needed.";
313 }
314
315 if (obj->functionCount() > 0)
316 return error(obj, QQmlComponentAndAliasResolverBase::tr("Component objects cannot declare new functions."));
317 if (obj->propertyCount() > 0 || obj->aliasCount() > 0)
318 return error(obj, QQmlComponentAndAliasResolverBase::tr("Component objects cannot declare new properties."));
319 if (obj->signalCount() > 0)
320 return error(obj, QQmlComponentAndAliasResolverBase::tr("Component objects cannot declare new signals."));
321
322 if (obj->bindingCount() == 0)
323 return error(obj, QQmlComponentAndAliasResolverBase::tr("Cannot create empty component specification"));
324
325 const auto rootBinding = obj->bindingsBegin();
326 const auto bindingsEnd = obj->bindingsEnd();
327
328 // Produce the more specific "no properties" error rather than the "invalid body" error
329 // where possible.
330 for (auto b = rootBinding; b != bindingsEnd; ++b) {
331 if (b->propertyNameIndex == 0)
332 continue;
333
334 return error(b, QQmlComponentAndAliasResolverBase::tr("Component elements may not contain properties other than id"));
335 }
336
337 if (auto b = rootBinding;
338 b->type() != QV4::CompiledData::Binding::Type_Object || ++b != bindingsEnd) {
339 return error(obj, QQmlComponentAndAliasResolverBase::tr("Invalid component body specification"));
340 }
341
342 // For the root object, we are going to collect ids/aliases and resolve them for as a
343 // separate last pass.
344 if (i != 0)
345 m_componentRoots.append(i);
346 }
347
348 for (int i = 0; i < m_componentRoots.size(); ++i) {
349 CompiledObject *component = m_compiler->objectAt(m_componentRoots.at(i));
350 const auto rootBinding = component->bindingsBegin();
351
352 m_idToObjectIndex.clear();
353 m_objectsWithAliases.clear();
354 m_generalizedGroupProperties.clear();
355
356 if (const QQmlError error = collectIdsAndAliases(rootBinding->value.objectIndex);
357 error.isValid()) {
358 return error;
359 }
360
361 allocateNamedObjects(component);
362
363 if (const QQmlError error = resolveAliases(m_componentRoots.at(i)); error.isValid())
364 return error;
365
366 resolveGeneralizedGroupProperties(m_componentRoots.at(i));
367 }
368
369 // Collect ids and aliases for root
370 m_idToObjectIndex.clear();
371 m_objectsWithAliases.clear();
372 m_generalizedGroupProperties.clear();
373
374 if (const QQmlError error = collectIdsAndAliases(root); error.isValid())
375 return error;
376
377 allocateNamedObjects(m_compiler->objectAt(root));
378 if (const QQmlError error = resolveAliases(root); error.isValid())
379 return error;
380
381 resolveGeneralizedGroupProperties(root);
382 return QQmlError();
383}
384
385template<typename ObjectContainer>
386QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::collectIdsAndAliases(int objectIndex)
387{
388 auto obj = m_compiler->objectAt(objectIndex);
389
390 if (obj->idNameIndex != 0) {
391 if (m_idToObjectIndex.contains(obj->idNameIndex))
392 return error(obj->locationOfIdProperty, QQmlComponentAndAliasResolverBase::tr("id is not unique"));
393 setObjectId(objectIndex);
394 m_idToObjectIndex.insert(obj->idNameIndex, objectIndex);
395 }
396
397 if (obj->aliasCount() > 0)
398 m_objectsWithAliases.append(objectIndex);
399
400 // Stop at Component boundary
401 if (obj->hasFlag(QV4::CompiledData::Object::IsComponent) && objectIndex != /*root object*/0)
402 return QQmlError();
403
404 for (auto binding = obj->bindingsBegin(), end = obj->bindingsEnd();
405 binding != end; ++binding) {
406 switch (binding->type()) {
407 case QV4::CompiledData::Binding::Type_GroupProperty: {
408 const auto *inner = m_compiler->objectAt(binding->value.objectIndex);
409 if (m_compiler->stringAt(inner->inheritedTypeNameIndex).isEmpty()) {
410 const auto cache = m_propertyCaches->at(objectIndex);
411 if (!cache || !cache->property(
412 m_compiler->stringAt(binding->propertyNameIndex), nullptr, nullptr)) {
413 m_generalizedGroupProperties.append(binding);
414 }
415 }
416 }
417 Q_FALLTHROUGH();
418 case QV4::CompiledData::Binding::Type_Object:
419 case QV4::CompiledData::Binding::Type_AttachedProperty:
420 if (const QQmlError error = collectIdsAndAliases(binding->value.objectIndex);
421 error.isValid()) {
422 return error;
423 }
424 break;
425 default:
426 break;
427 }
428 }
429
430 return QQmlError();
431}
432
433template<typename ObjectContainer>
434QQmlError QQmlComponentAndAliasResolver<ObjectContainer>::resolveAliases(int componentIndex)
435{
436 if (m_objectsWithAliases.isEmpty())
437 return QQmlError();
438
439 QQmlPropertyCacheAliasCreator<ObjectContainer> aliasCacheCreator(m_propertyCaches, m_compiler);
440
441 bool atLeastOneAliasResolved;
442 do {
443 atLeastOneAliasResolved = false;
444 QVector<int> pendingObjects;
445
446 for (int objectIndex: std::as_const(m_objectsWithAliases)) {
447
448 QQmlError error;
449 const auto &component = *m_compiler->objectAt(componentIndex);
450 const auto result
451 = resolveAliasesInObject(component, objectIndex, &aliasCacheCreator, &error);
452 if (error.isValid())
453 return error;
454
455 if (result == AllAliasesResolved) {
456 atLeastOneAliasResolved = true;
457 } else if (result == SomeAliasesResolved) {
458 atLeastOneAliasResolved = true;
459 pendingObjects.append(objectIndex);
460 } else {
461 pendingObjects.append(objectIndex);
462 }
463 }
464 qSwap(m_objectsWithAliases, pendingObjects);
465 } while (!m_objectsWithAliases.isEmpty() && atLeastOneAliasResolved);
466
467 if (!atLeastOneAliasResolved && !m_objectsWithAliases.isEmpty()) {
468 const CompiledObject *obj = m_compiler->objectAt(m_objectsWithAliases.first());
469 for (auto alias = obj->aliasesBegin(), end = obj->aliasesEnd(); alias != end; ++alias) {
470 if (!resolvedAliases.contains(alias))
471 return error(alias->location, QQmlComponentAndAliasResolverBase::tr("Circular alias reference detected"));
472 }
473 }
474
475 return QQmlError();
476}
477
478template<typename ObjectContainer>
479void QQmlComponentAndAliasResolver<ObjectContainer>::resolveGeneralizedGroupProperties(
480 int componentIndex)
481{
482 const auto &component = *m_compiler->objectAt(componentIndex);
483 for (CompiledBinding *binding : m_generalizedGroupProperties)
484 resolveGeneralizedGroupProperty(component, binding);
485}
486
487QT_END_NAMESPACE
488
489#endif // QQMLCOMPONENTANDALIASRESOLVER_P_H
QQmlComponentAndAliasResolver(ObjectContainer *compiler, QQmlPropertyCacheVector *propertyCaches)
typename ObjectContainer::CompiledObject CompiledObject
typename ObjectContainer::CompiledBinding CompiledBinding
QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(lcEventDispatcher)