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

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