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

source code of qtdeclarative/src/qml/qml/qqmlcomponentandaliasresolver_p.h