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 | |
35 | QT_BEGIN_NAMESPACE |
36 | |
37 | namespace detail { |
38 | /*! \internal |
39 | |
40 | Utility method that returns proper value according to the type To. This |
41 | version returns From. |
42 | */ |
43 | template<typename To, typename From, typename std::enable_if_t<!std::is_pointer_v<To>, int> = 0> |
44 | static 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 | */ |
57 | template<typename To, typename From, typename std::enable_if_t<std::is_pointer_v<To>, int> = 0> |
58 | static 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 | |
65 | class QQmlJSTypeResolver; |
66 | class QQmlJSScopesById; |
67 | struct 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 | |
370 | bool Q_QMLCOMPILER_PRIVATE_EXPORT canStrictlyCompareWithVar( |
371 | const QQmlJSTypeResolver *typeResolver, const QQmlJSRegisterContent &lhsContent, |
372 | const QQmlJSRegisterContent &rhsContent); |
373 | |
374 | bool Q_QMLCOMPILER_PRIVATE_EXPORT canCompareWithQObject(const QQmlJSTypeResolver *typeResolver, |
375 | const QQmlJSRegisterContent &lhsContent, |
376 | const QQmlJSRegisterContent &rhsContent); |
377 | |
378 | bool Q_QMLCOMPILER_PRIVATE_EXPORT canCompareWithQUrl(const QQmlJSTypeResolver *typeResolver, |
379 | const QQmlJSRegisterContent &lhsContent, |
380 | const QQmlJSRegisterContent &rhsContent); |
381 | |
382 | QT_END_NAMESPACE |
383 | |
384 | #endif // QQMLJSUTILS_P_H |
385 | |