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

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