1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#ifndef QQMLJSUTILS_P_H
5#define QQMLJSUTILS_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists purely as an
12// implementation detail. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16
17#include <qtqmlcompilerexports.h>
18
19#include "qqmljslogger_p.h"
20#include "qqmljsregistercontent_p.h"
21#include "qqmljsresourcefilemapper_p.h"
22#include "qqmljsscope_p.h"
23#include "qqmljsmetatypes_p.h"
24
25#include <QtCore/qdir.h>
26#include <QtCore/qstack.h>
27#include <QtCore/qstring.h>
28#include <QtCore/qstringbuilder.h>
29#include <QtCore/qstringview.h>
30
31#include <QtQml/private/qqmlsignalnames_p.h>
32#include <private/qduplicatetracker_p.h>
33
34#include <optional>
35#include <functional>
36#include <type_traits>
37#include <variant>
38
39QT_BEGIN_NAMESPACE
40
41namespace detail {
42/*! \internal
43
44 Utility method that returns proper value according to the type To. This
45 version returns From.
46*/
47template<typename To, typename From, typename std::enable_if_t<!std::is_pointer_v<To>, int> = 0>
48static auto getQQmlJSScopeFromSmartPtr(const From &p) -> From
49{
50 static_assert(!std::is_pointer_v<From>, "From has to be a smart pointer holding QQmlJSScope");
51 return p;
52}
53
54/*! \internal
55
56 Utility method that returns proper value according to the type To. This
57 version returns From::get(), which is a raw pointer. The returned type
58 is not necessarily equal to To (e.g. To might be `QQmlJSScope *` while
59 returned is `const QQmlJSScope *`).
60*/
61template<typename To, typename From, typename std::enable_if_t<std::is_pointer_v<To>, int> = 0>
62static auto getQQmlJSScopeFromSmartPtr(const From &p) -> decltype(p.get())
63{
64 static_assert(!std::is_pointer_v<From>, "From has to be a smart pointer holding QQmlJSScope");
65 return p.get();
66}
67}
68
69class QQmlJSTypeResolver;
70class QQmlJSScopesById;
71struct Q_QMLCOMPILER_EXPORT QQmlJSUtils
72{
73 /*! \internal
74 Returns escaped version of \a s. This function is mostly useful for code
75 generators.
76 */
77 static QString escapeString(QString s)
78 {
79 using namespace Qt::StringLiterals;
80 return s.replace(c: '\\'_L1, after: "\\\\"_L1)
81 .replace(c: '"'_L1, after: "\\\""_L1)
82 .replace(c: '\n'_L1, after: "\\n"_L1)
83 .replace(c: '?'_L1, after: "\\?"_L1);
84 }
85
86 /*! \internal
87 Returns \a s wrapped into a literal macro specified by \a ctor. By
88 default, returns a QStringLiteral-wrapped literal. This function is
89 mostly useful for code generators.
90
91 \note This function escapes \a s before wrapping it.
92 */
93 static QString toLiteral(const QString &s, QStringView ctor = u"QStringLiteral")
94 {
95 return ctor % u"(\"" % escapeString(s) % u"\")";
96 }
97
98 /*! \internal
99 Returns \a type string conditionally wrapped into \c{const} and \c{&}.
100 This function is mostly useful for code generators.
101 */
102 static QString constRefify(QString type)
103 {
104 if (!type.endsWith(c: u'*'))
105 type = u"const " % type % u"&";
106 return type;
107 }
108
109 static std::optional<QQmlJSMetaProperty>
110 changeHandlerProperty(const QQmlJSScope::ConstPtr &scope, QStringView signalName)
111 {
112 if (!signalName.endsWith(s: QLatin1String("Changed")))
113 return {};
114 constexpr int length = int(sizeof("Changed") / sizeof(char)) - 1;
115 signalName.chop(n: length);
116 auto p = scope->property(name: signalName.toString());
117 const bool isBindable = !p.bindable().isEmpty();
118 const bool canNotify = !p.notify().isEmpty();
119 if (p.isValid() && (isBindable || canNotify))
120 return p;
121 return {};
122 }
123
124 static std::optional<QQmlJSMetaProperty>
125 propertyFromChangedHandler(const QQmlJSScope::ConstPtr &scope, QStringView changedHandler)
126 {
127 auto signalName = QQmlSignalNames::changedHandlerNameToPropertyName(handler: changedHandler);
128 if (!signalName)
129 return {};
130
131 auto p = scope->property(name: *signalName);
132 const bool isBindable = !p.bindable().isEmpty();
133 const bool canNotify = !p.notify().isEmpty();
134 if (p.isValid() && (isBindable || canNotify))
135 return p;
136 return {};
137 }
138
139 static bool hasCompositeBase(const QQmlJSScope::ConstPtr &scope)
140 {
141 if (!scope)
142 return false;
143 const auto base = scope->baseType();
144 if (!base)
145 return false;
146 return base->isComposite() && base->scopeType() == QQmlSA::ScopeType::QMLScope;
147 }
148
149 enum PropertyAccessor {
150 PropertyAccessor_Read,
151 PropertyAccessor_Write,
152 };
153 /*! \internal
154
155 Returns \c true if \a p is bindable and property accessor specified by
156 \a accessor is equal to "default". Returns \c false otherwise.
157
158 \note This function follows BINDABLE-only properties logic (e.g. in moc)
159 */
160 static bool bindablePropertyHasDefaultAccessor(const QQmlJSMetaProperty &p,
161 PropertyAccessor accessor)
162 {
163 if (p.bindable().isEmpty())
164 return false;
165 switch (accessor) {
166 case PropertyAccessor::PropertyAccessor_Read:
167 return p.read() == QLatin1String("default");
168 case PropertyAccessor::PropertyAccessor_Write:
169 return p.write() == QLatin1String("default");
170 default:
171 break;
172 }
173 return false;
174 }
175
176 enum ResolvedAliasTarget {
177 AliasTarget_Invalid,
178 AliasTarget_Property,
179 AliasTarget_Object,
180 };
181 struct ResolvedAlias
182 {
183 QQmlJSMetaProperty property;
184 QQmlJSScope::ConstPtr owner;
185 ResolvedAliasTarget kind = ResolvedAliasTarget::AliasTarget_Invalid;
186 };
187 struct AliasResolutionVisitor
188 {
189 std::function<void()> reset = []() {};
190 std::function<void(const QQmlJSScope::ConstPtr &)> processResolvedId =
191 [](const QQmlJSScope::ConstPtr &) {};
192 std::function<void(const QQmlJSMetaProperty &, const QQmlJSScope::ConstPtr &)>
193 processResolvedProperty =
194 [](const QQmlJSMetaProperty &, const QQmlJSScope::ConstPtr &) {};
195 };
196 static ResolvedAlias resolveAlias(const QQmlJSTypeResolver *typeResolver,
197 const QQmlJSMetaProperty &property,
198 const QQmlJSScope::ConstPtr &owner,
199 const AliasResolutionVisitor &visitor);
200 static ResolvedAlias resolveAlias(const QQmlJSScopesById &idScopes,
201 const QQmlJSMetaProperty &property,
202 const QQmlJSScope::ConstPtr &owner,
203 const AliasResolutionVisitor &visitor);
204
205 template<typename QQmlJSScopePtr, typename Action>
206 static bool searchBaseAndExtensionTypes(QQmlJSScopePtr type, const Action &check)
207 {
208 if (!type)
209 return false;
210
211 using namespace detail;
212
213 // NB: among other things, getQQmlJSScopeFromSmartPtr() also resolves const
214 // vs non-const pointer issue, so use it's return value as the type
215 using T = decltype(getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(
216 std::declval<QQmlJSScope::ConstPtr>()));
217
218 const auto checkWrapper = [&](const auto &scope, QQmlJSScope::ExtensionKind mode) {
219 if constexpr (std::is_invocable<Action, decltype(scope),
220 QQmlJSScope::ExtensionKind>::value) {
221 return check(scope, mode);
222 } else {
223 static_assert(std::is_invocable<Action, decltype(scope)>::value,
224 "Inferred type Action has unexpected arguments");
225 Q_UNUSED(mode);
226 return check(scope);
227 }
228 };
229
230 const bool isValueOrSequenceType = [type]() {
231 switch (type->accessSemantics()) {
232 case QQmlJSScope::AccessSemantics::Value:
233 case QQmlJSScope::AccessSemantics::Sequence:
234 return true;
235 default:
236 break;
237 }
238 return false;
239 }();
240
241 QDuplicateTracker<T> seen;
242 for (T scope = type; scope && !seen.hasSeen(scope);
243 scope = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(scope->baseType())) {
244 QDuplicateTracker<T> seenExtensions;
245 // Extensions override the types they extend. However, usually base
246 // types of extensions are ignored. The unusual cases are when we
247 // have a value or sequence type or when we have the QObject type, in which
248 // case we also study the extension's base type hierarchy.
249 const bool isQObject = scope->internalName() == QLatin1String("QObject");
250 auto [extensionPtr, extensionKind] = scope->extensionType();
251 auto extension = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(extensionPtr);
252 do {
253 if (!extension || seenExtensions.hasSeen(extension))
254 break;
255
256 if (checkWrapper(extension, extensionKind))
257 return true;
258 extension = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(extension->baseType());
259 } while (isValueOrSequenceType || isQObject);
260
261 if (checkWrapper(scope, QQmlJSScope::NotExtension))
262 return true;
263 }
264
265 return false;
266 }
267
268 template<typename Action>
269 static void traverseFollowingQmlIrObjectStructure(const QQmlJSScope::Ptr &root, Action act)
270 {
271 // We *have* to perform DFS here: QmlIR::Object entries within the
272 // QmlIR::Document are stored in the order they appear during AST traversal
273 // (which does DFS)
274 QStack<QQmlJSScope::Ptr> stack;
275 stack.push(t: root);
276
277 while (!stack.isEmpty()) {
278 QQmlJSScope::Ptr current = stack.pop();
279
280 act(current);
281
282 auto children = current->childScopes();
283 // arrays are special: they are reverse-processed in QmlIRBuilder
284 if (!current->isArrayScope())
285 std::reverse(first: children.begin(), last: children.end()); // left-to-right DFS
286 stack.append(l: std::move(children));
287 }
288 }
289
290 /*! \internal
291
292 Traverses the base types and extensions of \a scope in the order aligned
293 with QMetaObjects created at run time for these types and extensions
294 (except that QQmlVMEMetaObject is ignored). \a start is the starting
295 type in the hierarchy where \a act is applied.
296
297 \note To call \a act for every type in the hierarchy, use
298 scope->extensionType().scope as \a start
299 */
300 template<typename Action>
301 static void traverseFollowingMetaObjectHierarchy(const QQmlJSScope::ConstPtr &scope,
302 const QQmlJSScope::ConstPtr &start, Action act)
303 {
304 // Meta objects are arranged in the following way:
305 // * static meta objects are chained first
306 // * dynamic meta objects are added on top - they come from extensions.
307 // QQmlVMEMetaObject ignored here
308 //
309 // Example:
310 // ```
311 // class A : public QObject {
312 // QML_EXTENDED(Ext)
313 // };
314 // class B : public A {
315 // QML_EXTENDED(Ext2)
316 // };
317 // ```
318 // gives: Ext2 -> Ext -> B -> A -> QObject
319 // ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
320 // ^^^^^^^^^^^ static meta objects
321 // dynamic meta objects
322
323 using namespace Qt::StringLiterals;
324 // ignore special extensions
325 const QLatin1String ignoredExtensionNames[] = {
326 // QObject extensions: (not related to C++)
327 "Object"_L1,
328 "ObjectPrototype"_L1,
329 };
330
331 QList<QQmlJSScope::AnnotatedScope> types;
332 QList<QQmlJSScope::AnnotatedScope> extensions;
333 const auto collect = [&](const QQmlJSScope::ConstPtr &type, QQmlJSScope::ExtensionKind m) {
334 if (m == QQmlJSScope::NotExtension) {
335 types.append(t: QQmlJSScope::AnnotatedScope { .scope: type, .extensionSpecifier: m });
336 return false;
337 }
338
339 for (const auto &name : ignoredExtensionNames) {
340 if (type->internalName() == name)
341 return false;
342 }
343 extensions.append(t: QQmlJSScope::AnnotatedScope { .scope: type, .extensionSpecifier: m });
344 return false;
345 };
346 searchBaseAndExtensionTypes(scope, collect);
347
348 QList<QQmlJSScope::AnnotatedScope> all;
349 all.reserve(size: extensions.size() + types.size());
350 // first extensions then types
351 all.append(l: std::move(extensions));
352 all.append(l: std::move(types));
353
354 auto begin = all.cbegin();
355 // skip to start
356 while (begin != all.cend() && !begin->scope->isSameType(otherScope: start))
357 ++begin;
358
359 // iterate over extensions and types starting at a specified point
360 for (; begin != all.cend(); ++begin)
361 act(begin->scope, begin->extensionSpecifier);
362 }
363
364 static std::optional<QQmlJSFixSuggestion> didYouMean(const QString &userInput,
365 QStringList candidates,
366 QQmlJS::SourceLocation location);
367
368 static std::variant<QString, QQmlJS::DiagnosticMessage>
369 sourceDirectoryPath(const QQmlJSImporter *importer, const QString &buildDirectoryPath);
370
371 template <typename Container>
372 static void deduplicate(Container &container)
373 {
374 std::sort(container.begin(), container.end());
375 auto erase = std::unique(container.begin(), container.end());
376 container.erase(erase, container.end());
377 }
378
379 static QStringList cleanPaths(QStringList &&paths)
380 {
381 for (QString &path : paths)
382 path = QDir::cleanPath(path);
383 return std::move(paths);
384 }
385
386 static QStringList resourceFilesFromBuildFolders(const QStringList &buildFolders);
387 static QString qmlSourcePathFromBuildPath(const QQmlJSResourceFileMapper *mapper,
388 const QString &pathInBuildFolder);
389 static QString qmlBuildPathFromSourcePath(const QQmlJSResourceFileMapper *mapper,
390 const QString &pathInBuildFolder);
391};
392
393bool Q_QMLCOMPILER_EXPORT canStrictlyCompareWithVar(
394 const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType,
395 const QQmlJSScope::ConstPtr &rhsType);
396
397bool Q_QMLCOMPILER_EXPORT canCompareWithQObject(
398 const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType,
399 const QQmlJSScope::ConstPtr &rhsType);
400
401bool Q_QMLCOMPILER_EXPORT canCompareWithQUrl(
402 const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType,
403 const QQmlJSScope::ConstPtr &rhsType);
404
405QT_END_NAMESPACE
406
407#endif // QQMLJSUTILS_P_H
408

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtdeclarative/src/qmlcompiler/qqmljsutils_p.h