aboutsummaryrefslogtreecommitdiffstats
path: root/src/qmlcompiler/qqmljsutils_p.h
blob: 9eb6840f9d3655d56e41e5fd8fd9efd8917d49fc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#ifndef QQMLJSUTILS_P_H
#define QQMLJSUTILS_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.

#include <qtqmlcompilerexports.h>

#include "qqmljslogger_p.h"
#include "qqmljsregistercontent_p.h"
#include "qqmljsscope_p.h"
#include "qqmljsmetatypes_p.h"

#include <QtCore/qstack.h>
#include <QtCore/qstring.h>
#include <QtCore/qstringview.h>
#include <QtCore/qstringbuilder.h>
#include <QtQml/private/qqmlsignalnames_p.h>
#include <private/qduplicatetracker_p.h>

#include <optional>
#include <functional>
#include <type_traits>
#include <variant>

QT_BEGIN_NAMESPACE

namespace detail {
/*! \internal

    Utility method that returns proper value according to the type To. This
    version returns From.
*/
template<typename To, typename From, typename std::enable_if_t<!std::is_pointer_v<To>, int> = 0>
static auto getQQmlJSScopeFromSmartPtr(const From &p) -> From
{
    static_assert(!std::is_pointer_v<From>, "From has to be a smart pointer holding QQmlJSScope");
    return p;
}

/*! \internal

    Utility method that returns proper value according to the type To. This
    version returns From::get(), which is a raw pointer. The returned type
    is not necessarily equal to To (e.g. To might be `QQmlJSScope *` while
    returned is `const QQmlJSScope *`).
*/
template<typename To, typename From, typename std::enable_if_t<std::is_pointer_v<To>, int> = 0>
static auto getQQmlJSScopeFromSmartPtr(const From &p) -> decltype(p.get())
{
    static_assert(!std::is_pointer_v<From>, "From has to be a smart pointer holding QQmlJSScope");
    return p.get();
}
}

class QQmlJSTypeResolver;
class QQmlJSScopesById;
struct Q_QMLCOMPILER_EXPORT QQmlJSUtils
{
    /*! \internal
        Returns escaped version of \a s. This function is mostly useful for code
        generators.
    */
    static QString escapeString(QString s)
    {
        using namespace Qt::StringLiterals;
        return s.replace('\\'_L1, "\\\\"_L1)
                .replace('"'_L1, "\\\""_L1)
                .replace('\n'_L1, "\\n"_L1)
                .replace('?'_L1, "\\?"_L1);
    }

    /*! \internal
        Returns \a s wrapped into a literal macro specified by \a ctor. By
        default, returns a QStringLiteral-wrapped literal. This function is
        mostly useful for code generators.

        \note This function escapes \a s before wrapping it.
    */
    static QString toLiteral(const QString &s, QStringView ctor = u"QStringLiteral")
    {
        return ctor % u"(\"" % escapeString(s) % u"\")";
    }

    /*! \internal
        Returns \a type string conditionally wrapped into \c{const} and \c{&}.
        This function is mostly useful for code generators.
    */
    static QString constRefify(QString type)
    {
        if (!type.endsWith(u'*'))
            type = u"const " % type % u"&";
        return type;
    }

    static std::optional<QQmlJSMetaProperty>
    changeHandlerProperty(const QQmlJSScope::ConstPtr &scope, QStringView signalName)
    {
        if (!signalName.endsWith(QLatin1String("Changed")))
            return {};
        constexpr int length = int(sizeof("Changed") / sizeof(char)) - 1;
        signalName.chop(length);
        auto p = scope->property(signalName.toString());
        const bool isBindable = !p.bindable().isEmpty();
        const bool canNotify = !p.notify().isEmpty();
        if (p.isValid() && (isBindable || canNotify))
            return p;
        return {};
    }

    static std::optional<QQmlJSMetaProperty>
    propertyFromChangedHandler(const QQmlJSScope::ConstPtr &scope, QStringView changedHandler)
    {
        auto signalName = QQmlSignalNames::changedHandlerNameToPropertyName(changedHandler);
        if (!signalName)
            return {};

        auto p = scope->property(*signalName);
        const bool isBindable = !p.bindable().isEmpty();
        const bool canNotify = !p.notify().isEmpty();
        if (p.isValid() && (isBindable || canNotify))
            return p;
        return {};
    }

    static bool hasCompositeBase(const QQmlJSScope::ConstPtr &scope)
    {
        if (!scope)
            return false;
        const auto base = scope->baseType();
        if (!base)
            return false;
        return base->isComposite() && base->scopeType() == QQmlSA::ScopeType::QMLScope;
    }

    enum PropertyAccessor {
        PropertyAccessor_Read,
        PropertyAccessor_Write,
    };
    /*! \internal

        Returns \c true if \a p is bindable and property accessor specified by
        \a accessor is equal to "default". Returns \c false otherwise.

        \note This function follows BINDABLE-only properties logic (e.g. in moc)
    */
    static bool bindablePropertyHasDefaultAccessor(const QQmlJSMetaProperty &p,
                                                   PropertyAccessor accessor)
    {
        if (p.bindable().isEmpty())
            return false;
        switch (accessor) {
        case PropertyAccessor::PropertyAccessor_Read:
            return p.read() == QLatin1String("default");
        case PropertyAccessor::PropertyAccessor_Write:
            return p.write() == QLatin1String("default");
        default:
            break;
        }
        return false;
    }

    enum ResolvedAliasTarget {
        AliasTarget_Invalid,
        AliasTarget_Property,
        AliasTarget_Object,
    };
    struct ResolvedAlias
    {
        QQmlJSMetaProperty property;
        QQmlJSScope::ConstPtr owner;
        ResolvedAliasTarget kind = ResolvedAliasTarget::AliasTarget_Invalid;
    };
    struct AliasResolutionVisitor
    {
        std::function<void()> reset = []() {};
        std::function<void(const QQmlJSScope::ConstPtr &)> processResolvedId =
                [](const QQmlJSScope::ConstPtr &) {};
        std::function<void(const QQmlJSMetaProperty &, const QQmlJSScope::ConstPtr &)>
                processResolvedProperty =
                        [](const QQmlJSMetaProperty &, const QQmlJSScope::ConstPtr &) {};
    };
    static ResolvedAlias resolveAlias(const QQmlJSTypeResolver *typeResolver,
                                      const QQmlJSMetaProperty &property,
                                      const QQmlJSScope::ConstPtr &owner,
                                      const AliasResolutionVisitor &visitor);
    static ResolvedAlias resolveAlias(const QQmlJSScopesById &idScopes,
                                      const QQmlJSMetaProperty &property,
                                      const QQmlJSScope::ConstPtr &owner,
                                      const AliasResolutionVisitor &visitor);

    template<typename QQmlJSScopePtr, typename Action>
    static bool searchBaseAndExtensionTypes(QQmlJSScopePtr type, const Action &check)
    {
        if (!type)
            return false;

        using namespace detail;

        // NB: among other things, getQQmlJSScopeFromSmartPtr() also resolves const
        // vs non-const pointer issue, so use it's return value as the type
        using T = decltype(getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(
                std::declval<QQmlJSScope::ConstPtr>()));

        const auto checkWrapper = [&](const auto &scope, QQmlJSScope::ExtensionKind mode) {
            if constexpr (std::is_invocable<Action, decltype(scope),
                                            QQmlJSScope::ExtensionKind>::value) {
                return check(scope, mode);
            } else {
                static_assert(std::is_invocable<Action, decltype(scope)>::value,
                              "Inferred type Action has unexpected arguments");
                Q_UNUSED(mode);
                return check(scope);
            }
        };

        const bool isValueOrSequenceType = [type]() {
            switch (type->accessSemantics()) {
            case QQmlJSScope::AccessSemantics::Value:
            case QQmlJSScope::AccessSemantics::Sequence:
                return true;
            default:
                break;
            }
            return false;
        }();

        QDuplicateTracker<T> seen;
        for (T scope = type; scope && !seen.hasSeen(scope);
             scope = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(scope->baseType())) {
            QDuplicateTracker<T> seenExtensions;
            // Extensions override the types they extend. However, usually base
            // types of extensions are ignored. The unusual cases are when we
            // have a value or sequence type or when we have the QObject type, in which
            // case we also study the extension's base type hierarchy.
            const bool isQObject = scope->internalName() == QLatin1String("QObject");
            auto [extensionPtr, extensionKind] = scope->extensionType();
            auto extension = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(extensionPtr);
            do {
                if (!extension || seenExtensions.hasSeen(extension))
                    break;

                if (checkWrapper(extension, extensionKind))
                    return true;
                extension = getQQmlJSScopeFromSmartPtr<QQmlJSScopePtr>(extension->baseType());
            } while (isValueOrSequenceType || isQObject);

            if (checkWrapper(scope, QQmlJSScope::NotExtension))
                return true;
        }

        return false;
    }

    template<typename Action>
    static void traverseFollowingQmlIrObjectStructure(const QQmlJSScope::Ptr &root, Action act)
    {
        // We *have* to perform DFS here: QmlIR::Object entries within the
        // QmlIR::Document are stored in the order they appear during AST traversal
        // (which does DFS)
        QStack<QQmlJSScope::Ptr> stack;
        stack.push(root);

        while (!stack.isEmpty()) {
            QQmlJSScope::Ptr current = stack.pop();

            act(current);

            auto children = current->childScopes();
            // arrays are special: they are reverse-processed in QmlIRBuilder
            if (!current->isArrayScope())
                std::reverse(children.begin(), children.end()); // left-to-right DFS
            stack.append(std::move(children));
        }
    }

    /*! \internal

        Traverses the base types and extensions of \a scope in the order aligned
        with QMetaObjects created at run time for these types and extensions
        (except that QQmlVMEMetaObject is ignored). \a start is the starting
        type in the hierarchy where \a act is applied.

        \note To call \a act for every type in the hierarchy, use
        scope->extensionType().scope as \a start
    */
    template<typename Action>
    static void traverseFollowingMetaObjectHierarchy(const QQmlJSScope::ConstPtr &scope,
                                                     const QQmlJSScope::ConstPtr &start, Action act)
    {
        // Meta objects are arranged in the following way:
        // * static meta objects are chained first
        // * dynamic meta objects are added on top - they come from extensions.
        //   QQmlVMEMetaObject ignored here
        //
        // Example:
        // ```
        //   class A : public QObject {
        //       QML_EXTENDED(Ext)
        //   };
        //   class B : public A {
        //       QML_EXTENDED(Ext2)
        //   };
        // ```
        // gives: Ext2 -> Ext -> B -> A -> QObject
        //        ^^^^^^^^^^^    ^^^^^^^^^^^^^^^^^
        //        ^^^^^^^^^^^    static meta objects
        //        dynamic meta objects

        using namespace Qt::StringLiterals;
        // ignore special extensions
        const QLatin1String ignoredExtensionNames[] = {
            // QObject extensions: (not related to C++)
            "Object"_L1,
            "ObjectPrototype"_L1,
        };

        QList<QQmlJSScope::AnnotatedScope> types;
        QList<QQmlJSScope::AnnotatedScope> extensions;
        const auto collect = [&](const QQmlJSScope::ConstPtr &type, QQmlJSScope::ExtensionKind m) {
            if (m == QQmlJSScope::NotExtension) {
                types.append(QQmlJSScope::AnnotatedScope { type, m });
                return false;
            }

            for (const auto &name : ignoredExtensionNames) {
                if (type->internalName() == name)
                    return false;
            }
            extensions.append(QQmlJSScope::AnnotatedScope { type, m });
            return false;
        };
        searchBaseAndExtensionTypes(scope, collect);

        QList<QQmlJSScope::AnnotatedScope> all;
        all.reserve(extensions.size() + types.size());
        // first extensions then types
        all.append(std::move(extensions));
        all.append(std::move(types));

        auto begin = all.cbegin();
        // skip to start
        while (begin != all.cend() && !begin->scope->isSameType(start))
            ++begin;

        // iterate over extensions and types starting at a specified point
        for (; begin != all.cend(); ++begin)
            act(begin->scope, begin->extensionSpecifier);
    }

    static std::optional<QQmlJSFixSuggestion> didYouMean(const QString &userInput,
                                                   QStringList candidates,
                                                   QQmlJS::SourceLocation location);

    static std::variant<QString, QQmlJS::DiagnosticMessage>
    sourceDirectoryPath(const QQmlJSImporter *importer, const QString &buildDirectoryPath);
};

bool Q_QMLCOMPILER_EXPORT canStrictlyCompareWithVar(
        const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType,
        const QQmlJSScope::ConstPtr &rhsType);

bool Q_QMLCOMPILER_EXPORT canCompareWithQObject(
        const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType,
        const QQmlJSScope::ConstPtr &rhsType);

bool Q_QMLCOMPILER_EXPORT canCompareWithQUrl(
        const QQmlJSTypeResolver *typeResolver, const QQmlJSScope::ConstPtr &lhsType,
        const QQmlJSScope::ConstPtr &rhsType);

QT_END_NAMESPACE

#endif // QQMLJSUTILS_P_H