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 | |
39 | QT_BEGIN_NAMESPACE |
40 | |
41 | namespace detail { |
42 | /*! \internal |
43 | |
44 | Utility method that returns proper value according to the type To. This |
45 | version returns From. |
46 | */ |
47 | template<typename To, typename From, typename std::enable_if_t<!std::is_pointer_v<To>, int> = 0> |
48 | static 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 | */ |
61 | template<typename To, typename From, typename std::enable_if_t<std::is_pointer_v<To>, int> = 0> |
62 | static 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 | |
69 | class QQmlJSTypeResolver; |
70 | class QQmlJSScopesById; |
71 | struct 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 | |
393 | bool Q_QMLCOMPILER_EXPORT canStrictlyCompareWithVar( |
394 | const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType, |
395 | const QQmlJSScope::ConstPtr &rhsType); |
396 | |
397 | bool Q_QMLCOMPILER_EXPORT canCompareWithQObject( |
398 | const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType, |
399 | const QQmlJSScope::ConstPtr &rhsType); |
400 | |
401 | bool Q_QMLCOMPILER_EXPORT canCompareWithQUrl( |
402 | const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType, |
403 | const QQmlJSScope::ConstPtr &rhsType); |
404 | |
405 | QT_END_NAMESPACE |
406 | |
407 | #endif // QQMLJSUTILS_P_H |
408 |
Definitions
- getQQmlJSScopeFromSmartPtr
- getQQmlJSScopeFromSmartPtr
- QQmlJSUtils
- escapeString
- toLiteral
- constRefify
- changeHandlerProperty
- propertyFromChangedHandler
- hasCompositeBase
- PropertyAccessor
- bindablePropertyHasDefaultAccessor
- ResolvedAliasTarget
- ResolvedAlias
- AliasResolutionVisitor
- searchBaseAndExtensionTypes
- traverseFollowingQmlIrObjectStructure
- traverseFollowingMetaObjectHierarchy
- deduplicate
Learn Advanced QML with KDAB
Find out more