diff options
-rw-r--r-- | src/qml/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/qml/jsapi/qjslist.cpp | 18 | ||||
-rw-r--r-- | src/qml/jsapi/qjslist.h | 282 | ||||
-rw-r--r-- | src/qml/qml/qqmllist.h | 2 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscodegenerator.cpp | 84 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljscodegenerator_p.h | 6 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljstypepropagator.cpp | 178 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljstypepropagator_p.h | 1 | ||||
-rw-r--r-- | src/qmlcompiler/qqmljstyperesolver_p.h | 1 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt | 4 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/jsArrayMethods.qml | 28 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsUntyped.qml | 17 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParams.qml | 26 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParamsUntyped.qml | 18 | ||||
-rw-r--r-- | tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp | 125 |
15 files changed, 787 insertions, 4 deletions
diff --git a/src/qml/CMakeLists.txt b/src/qml/CMakeLists.txt index eeb65be999..f1916a2eb2 100644 --- a/src/qml/CMakeLists.txt +++ b/src/qml/CMakeLists.txt @@ -147,6 +147,7 @@ qt_internal_add_qml_module(Qml debugger/qqmlprofiler_p.h inlinecomponentutils_p.h jsapi/qjsengine.cpp jsapi/qjsengine.h jsapi/qjsengine_p.h + jsapi/qjslist.cpp jsapi/qjslist.h jsapi/qjsmanagedvalue.cpp jsapi/qjsmanagedvalue.h jsapi/qjsprimitivevalue.cpp jsapi/qjsprimitivevalue.h jsapi/qjsvalue.cpp jsapi/qjsvalue.h jsapi/qjsvalue_p.h diff --git a/src/qml/jsapi/qjslist.cpp b/src/qml/jsapi/qjslist.cpp new file mode 100644 index 0000000000..b3a4eed3c0 --- /dev/null +++ b/src/qml/jsapi/qjslist.cpp @@ -0,0 +1,18 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include <QtQml/qjslist.h> + +QT_BEGIN_NAMESPACE + +/*! + * \class QJSListIndexClamp + * \internal + */ + +/*! + * \class QJSList + * \internal + */ + +QT_END_NAMESPACE diff --git a/src/qml/jsapi/qjslist.h b/src/qml/jsapi/qjslist.h new file mode 100644 index 0000000000..cff8ccc203 --- /dev/null +++ b/src/qml/jsapi/qjslist.h @@ -0,0 +1,282 @@ +// Copyright (C) 2023 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#ifndef QJSLIST_H +#define QJSLIST_H + +#include <QtQml/qtqmlglobal.h> +#include <QtQml/qqmllist.h> +#include <QtQml/qjsengine.h> +#include <QtCore/qobject.h> +#include <QtCore/qstring.h> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +struct QJSListIndexClamp +{ + static qsizetype clamp(int start, qsizetype max, qsizetype min = 0) + { + Q_ASSERT(min >= 0); + Q_ASSERT(min <= max); + return std::clamp(start < 0 ? max + qsizetype(start) : qsizetype(start), min, max); + } +}; + +template<typename List, typename Value = typename List::value_type> +struct QJSList : private QJSListIndexClamp +{ + Q_DISABLE_COPY_MOVE(QJSList) + + QJSList(List *list, QJSEngine *engine) : m_list(list), m_engine(engine) {} + + bool includes(const Value &value) const + { + return std::find(m_list->cbegin(), m_list->cend(), value) != m_list->cend(); + } + + bool includes(const Value &value, int start) const + { + return std::find(m_list->cbegin() + clamp(start, m_list->size()), m_list->cend(), value) + != m_list->cend(); + } + + QString join(const QString &separator = QStringLiteral(",")) const + { + QString result; + bool atBegin = true; + std::for_each(m_list->cbegin(), m_list->cend(), [&](const Value &value) { + if (atBegin) + atBegin = false; + else + result += separator; + result += m_engine->coerceValue<Value, QString>(value); + }); + return result; + } + + List slice() const + { + return *m_list; + } + List slice(int start) const + { + List result; + std::copy(m_list->cbegin() + clamp(start, m_list->size()), m_list->cend(), + std::back_inserter(result)); + return result; + } + List slice(int start, int end) const + { + const qsizetype size = m_list->size(); + const qsizetype clampedStart = clamp(start, size); + const qsizetype clampedEnd = clamp(end, size, clampedStart); + + List result; + std::copy(m_list->cbegin() + clampedStart, m_list->cbegin() + clampedEnd, + std::back_inserter(result)); + return result; + } + + int indexOf(const Value &value) const + { + const auto begin = m_list->cbegin(); + const auto end = m_list->cend(); + const auto it = std::find(begin, end, value); + if (it == end) + return -1; + const qsizetype result = it - begin; + Q_ASSERT(result >= 0); + return result > std::numeric_limits<int>::max() ? -1 : int(result); + } + int indexOf(const Value &value, int start) const + { + const auto begin = m_list->cbegin(); + const auto end = m_list->cend(); + const auto it = std::find(begin + clamp(start, m_list->size()), end, value); + if (it == end) + return -1; + const qsizetype result = it - begin; + Q_ASSERT(result >= 0); + return result > std::numeric_limits<int>::max() ? -1 : int(result); + } + + int lastIndexOf(const Value &value) const + { + const auto begin = std::make_reverse_iterator(m_list->cend()); + const auto end = std::make_reverse_iterator(m_list->cbegin()); + const auto it = std::find(begin, end, value); + const qsizetype result = (end - it) - 1; + return result > std::numeric_limits<int>::max() ? -1 : int(result); + } + int lastIndexOf(const Value &value, int start) const + { + const qsizetype size = m_list->size(); + if (size == 0) + return -1; + + // Construct a one-past-end iterator as input. + const qsizetype clampedStart = std::min(clamp(start, size), size - 1); + const auto begin = std::make_reverse_iterator(m_list->cbegin() + clampedStart + 1); + + const auto end = std::make_reverse_iterator(m_list->cbegin()); + const auto it = std::find(begin, end, value); + const qsizetype result = (end - it) - 1; + return result > std::numeric_limits<int>::max() ? -1 : int(result); + } + + QString toString() const { return join(); } + +private: + List *m_list = nullptr; + QJSEngine *m_engine = nullptr; +}; + +template<> +struct QJSList<QQmlListProperty<QObject>, QObject *> : private QJSListIndexClamp +{ + Q_DISABLE_COPY_MOVE(QJSList) + + QJSList(QQmlListProperty<QObject> *list, QJSEngine *engine) : m_list(list), m_engine(engine) {} + + bool includes(const QObject *value) const + { + if (!m_list->count || !m_list->at) + return false; + + const qsizetype size = m_list->count(m_list); + for (qsizetype i = 0; i < size; ++i) { + if (m_list->at(m_list, i) == value) + return true; + } + + return false; + } + bool includes(const QObject *value, int start) const + { + if (!m_list->count || !m_list->at) + return false; + + const qsizetype size = m_list->count(m_list); + for (qsizetype i = clamp(start, size); i < size; ++i) { + if (m_list->at(m_list, i) == value) + return true; + } + + return false; + } + + QString join(const QString &separator = QStringLiteral(",")) const + { + if (!m_list->count || !m_list->at) + return QString(); + + QString result; + for (qsizetype i = 0, end = m_list->count(m_list); i < end; ++i) { + if (i != 0) + result += separator; + result += m_engine->coerceValue<QObject *, QString>(m_list->at(m_list, i)); + } + + return result; + } + + QObjectList slice() const + { + return m_list->toList<QObjectList>(); + } + QObjectList slice(int start) const + { + if (!m_list->count || !m_list->at) + return QObjectList(); + + const qsizetype size = m_list->count(m_list); + const qsizetype clampedStart = clamp(start, size); + QObjectList result; + result.reserve(size - clampedStart); + for (qsizetype i = clampedStart; i < size; ++i) + result.append(m_list->at(m_list, i)); + return result; + } + QObjectList slice(int start, int end) const + { + if (!m_list->count || !m_list->at) + return QObjectList(); + + const qsizetype size = m_list->count(m_list); + const qsizetype clampedStart = clamp(start, size); + const qsizetype clampedEnd = clamp(end, size, clampedStart); + QObjectList result; + result.reserve(clampedEnd - clampedStart); + for (qsizetype i = clampedStart; i < clampedEnd; ++i) + result.append(m_list->at(m_list, i)); + return result; + } + + int indexOf(const QObject *value) const + { + if (!m_list->count || !m_list->at) + return -1; + + const qsizetype end + = std::min(m_list->count(m_list), qsizetype(std::numeric_limits<int>::max())); + for (qsizetype i = 0; i < end; ++i) { + if (m_list->at(m_list, i) == value) + return int(i); + } + return -1; + } + int indexOf(const QObject *value, int start) const + { + if (!m_list->count || !m_list->at) + return -1; + + const qsizetype size = m_list->count(m_list); + for (qsizetype i = clamp(start, size), + end = std::min(size, qsizetype(std::numeric_limits<int>::max())); + i < end; ++i) { + if (m_list->at(m_list, i) == value) + return int(i); + } + return -1; + } + + int lastIndexOf(const QObject *value) const + { + if (!m_list->count || !m_list->at) + return -1; + + for (qsizetype i = m_list->count(m_list) - 1; i >= 0; --i) { + if (m_list->at(m_list, i) == value) + return i > std::numeric_limits<int>::max() ? -1 : int(i); + } + return -1; + } + int lastIndexOf(const QObject *value, int start) const + { + if (!m_list->count || !m_list->at) + return -1; + + const qsizetype size = m_list->count(m_list); + if (size == 0) + return -1; + + qsizetype clampedStart = std::min(clamp(start, size), size - 1); + for (qsizetype i = clampedStart; i >= 0; --i) { + if (m_list->at(m_list, i) == value) + return i > std::numeric_limits<int>::max() ? -1 : int(i); + } + return -1; + } + + QString toString() const { return join(); } + +private: + QQmlListProperty<QObject> *m_list = nullptr; + QJSEngine *m_engine = nullptr; +}; + +QT_END_NAMESPACE + +#endif // QJSLIST_H diff --git a/src/qml/qml/qqmllist.h b/src/qml/qml/qqmllist.h index eae5e03eff..6f2c077764 100644 --- a/src/qml/qml/qqmllist.h +++ b/src/qml/qml/qqmllist.h @@ -23,6 +23,8 @@ struct QMetaObject; template<typename T> class QQmlListProperty { public: + using value_type = T*; + using AppendFunction = void (*)(QQmlListProperty<T> *, T *); using CountFunction = qsizetype (*)(QQmlListProperty<T> *); using AtFunction = T *(*)(QQmlListProperty<T> *, qsizetype); diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp index 55dc3867e6..724ac50ca8 100644 --- a/src/qmlcompiler/qqmljscodegenerator.cpp +++ b/src/qmlcompiler/qqmljscodegenerator.cpp @@ -1737,6 +1737,85 @@ bool QQmlJSCodeGenerator::inlineConsoleMethod(const QString &name, int argc, int return true; } +bool QQmlJSCodeGenerator::inlineArrayMethod(const QString &name, int base, int argc, int argv) +{ + const auto intType = m_typeResolver->int32Type(); + const auto valueType = registerType(base).storedType()->valueType(); + const auto boolType = m_typeResolver->boolType(); + const auto stringType = m_typeResolver->stringType(); + const auto baseType = registerType(base); + + const QString baseVar = registerVariable(base); + const QString qjsListMethod = u"QJSList(&"_s + baseVar + u", aotContext->engine)." + + name + u"("; + + addInclude(u"qjslist.h"_s); + + if (name == u"includes" && argc > 0 && argc < 3) { + QString call = qjsListMethod + + convertStored(registerType(argv).storedType(), valueType, + consumedRegisterVariable(argv)); + if (argc == 2) { + call += u", " + convertStored(registerType(argv + 1).storedType(), intType, + consumedRegisterVariable(argv + 1)); + } + call += u")"; + + m_body += m_state.accumulatorVariableOut + u" = "_s + + conversion(boolType, m_state.accumulatorOut(), call) + u";\n"_s; + return true; + } + + if (name == u"toString" || (name == u"join" && argc < 2)) { + QString call = qjsListMethod; + if (argc == 1) { + call += convertStored(registerType(argv).storedType(), stringType, + consumedRegisterVariable(argv)); + } + call += u")"; + + m_body += m_state.accumulatorVariableOut + u" = "_s + + conversion(stringType, m_state.accumulatorOut(), call) + u";\n"_s; + return true; + } + + if (name == u"slice" && argc < 3) { + QString call = qjsListMethod; + for (int i = 0; i < argc; ++i) { + if (i > 0) + call += u", "; + call += convertStored(registerType(argv + i).storedType(), intType, + consumedRegisterVariable(argv + i)); + } + call += u")"; + + const auto outType = baseType.storedType()->isListProperty() + ? m_typeResolver->globalType(m_typeResolver->qObjectListType()) + : baseType; + + m_body += m_state.accumulatorVariableOut + u" = "_s + + conversion(outType, m_state.accumulatorOut(), call) + u";\n"_s; + return true; + } + + if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) { + QString call = qjsListMethod + + convertStored(registerType(argv).storedType(), valueType, + consumedRegisterVariable(argv)); + if (argc == 2) { + call += u", " + convertStored(registerType(argv + 1).storedType(), intType, + consumedRegisterVariable(argv + 1)); + } + call += u")"; + + m_body += m_state.accumulatorVariableOut + u" = "_s + + conversion(intType, m_state.accumulatorOut(), call) + u";\n"_s; + return true; + } + + return false; +} + void QQmlJSCodeGenerator::generate_CallPropertyLookup(int index, int base, int argc, int argv) { INJECT_TRACE_INFO(generate_CallPropertyLookup); @@ -1766,6 +1845,11 @@ void QQmlJSCodeGenerator::generate_CallPropertyLookup(int index, int base, int a return; } + if (baseType.storedType()->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence) { + if (inlineArrayMethod(name, base, argc, argv)) + return; + } + if (m_typeResolver->canUseValueTypes()) { // This is possible, once we establish the right kind of lookup for it reject(u"call to property '%1' of %2"_s.arg(name, baseType.descriptiveName())); diff --git a/src/qmlcompiler/qqmljscodegenerator_p.h b/src/qmlcompiler/qqmljscodegenerator_p.h index 04245fc30b..5427f942f4 100644 --- a/src/qmlcompiler/qqmljscodegenerator_p.h +++ b/src/qmlcompiler/qqmljscodegenerator_p.h @@ -295,6 +295,7 @@ private: bool inlineTranslateMethod(const QString &name, int argc, int argv); bool inlineMathMethod(const QString &name, int argc, int argv); bool inlineConsoleMethod(const QString &name, int argc, int argv); + bool inlineArrayMethod(const QString &name, int base, int argc, int argv); QQmlJSScope::ConstPtr mathObject() const { @@ -308,6 +309,11 @@ private: return m_typeResolver->jsGlobalObject()->property(u"console"_s).type(); } + QQmlJSScope::ConstPtr arrayPrototype() const + { + return m_typeResolver->arrayType()->baseType(); + } + QString resolveValueTypeContentPointer( const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual, const QString &variable, const QString &errorMessage); diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp index 674071f218..e86f8eb9e8 100644 --- a/src/qmlcompiler/qqmljstypepropagator.cpp +++ b/src/qmlcompiler/qqmljstypepropagator.cpp @@ -1038,6 +1038,7 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar return; } + const auto baseType = m_typeResolver->containedType(callBase); const auto member = m_typeResolver->memberType(callBase, propertyName); if (!member.isMethod()) { setError(u"Type %1 does not have a property %2 for calling"_s @@ -1051,8 +1052,6 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar std::optional<QQmlJSFixSuggestion> fixSuggestion; - const auto baseType = m_typeResolver->containedType(callBase); - if (auto suggestion = QQmlJSUtils::didYouMean(propertyName, baseType->methods().keys(), getCurrentSourceLocation()); suggestion.has_value()) { @@ -1065,12 +1064,12 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar return; } - checkDeprecated(m_typeResolver->containedType(callBase), propertyName, true); + checkDeprecated(baseType, propertyName, true); if (m_passManager != nullptr) { // TODO: Should there be an analyzeCall() in the future? (w. corresponding onCall in Pass) m_passManager->analyzeRead( - m_typeResolver->containedType(callBase), + baseType, propertyName, m_function->qmlScope, getCurrentSourceLocation()); } @@ -1083,6 +1082,14 @@ void QQmlJSTypePropagator::generate_CallProperty(int nameIndex, int base, int ar } } + if (baseType->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence + && m_typeResolver->equals( + member.scopeType(), + m_typeResolver->arrayType()->baseType()) + && propagateArrayMethod(propertyName, argc, argv, callBase)) { + return; + } + propagateCall(member.method(), argc, argv, member.scopeType()); } @@ -1401,6 +1408,169 @@ void QQmlJSTypePropagator::propagateStringArgCall(int argv) addReadRegister(argv, m_typeResolver->globalType(m_typeResolver->stringType())); } +bool QQmlJSTypePropagator::propagateArrayMethod( + const QString &name, int argc, int argv, const QQmlJSRegisterContent &baseType) +{ + // TODO: + // * For concat() we need to decide what kind of array to return and what kinds of arguments to + // accept. + // * For entries(), keys(), and values() we need iterators. + // * For find(), findIndex(), sort(), every(), some(), forEach(), map(), filter(), reduce(), + // and reduceRight() we need typed function pointers. + + const auto intType = m_typeResolver->globalType(m_typeResolver->int32Type()); + const auto boolType = m_typeResolver->globalType(m_typeResolver->boolType()); + const auto stringType = m_typeResolver->globalType(m_typeResolver->stringType()); + const auto valueType = m_typeResolver->globalType( + m_typeResolver->containedType(baseType)->valueType()); + + // TODO: We should remember whether a register content can be written back when + // converting and merging. Also, we need a way to detect the "only in same statement" + // write back case. To do this, we should store the statementNumber(s) in + // Property and Conversion RegisterContents. + const bool canHaveSideEffects = (baseType.isProperty() && baseType.isWritable()) + || baseType.isConversion(); + + if (name == u"copyWithin" && argc > 0 && argc < 4) { + for (int i = 0; i < argc; ++i) { + if (!canConvertFromTo(m_state.registers[argv + i].content, intType)) + return false; + } + + for (int i = 0; i < argc; ++i) + addReadRegister(argv + i, intType); + + setAccumulator(baseType); + m_state.setHasSideEffects(canHaveSideEffects); + return true; + } + + if (name == u"fill" && argc > 0 && argc < 4) { + if (!canConvertFromTo(m_state.registers[argv].content, valueType)) + return false; + + for (int i = 1; i < argc; ++i) { + if (!canConvertFromTo(m_state.registers[argv + i].content, intType)) + return false; + } + + addReadRegister(argv, valueType); + + for (int i = 1; i < argc; ++i) + addReadRegister(argv + i, intType); + + setAccumulator(baseType); + m_state.setHasSideEffects(canHaveSideEffects); + return true; + } + + if (name == u"includes" && argc > 0 && argc < 3) { + if (!canConvertFromTo(m_state.registers[argv].content, valueType)) + return false; + + if (argc == 2) { + if (!canConvertFromTo(m_state.registers[argv + 1].content, intType)) + return false; + addReadRegister(argv + 1, intType); + } + + addReadRegister(argv, valueType); + setAccumulator(boolType); + return true; + } + + if (name == u"toString" || (name == u"join" && argc < 2)) { + if (argc == 1) { + if (!canConvertFromTo(m_state.registers[argv].content, stringType)) + return false; + addReadRegister(argv, stringType); + } + + setAccumulator(stringType); + return true; + } + + if ((name == u"pop" || name == u"shift") && argc == 0) { + setAccumulator(valueType); + m_state.setHasSideEffects(canHaveSideEffects); + return true; + } + + if (name == u"push" || name == u"unshift") { + for (int i = 0; i < argc; ++i) { + if (!canConvertFromTo(m_state.registers[argv + i].content, valueType)) + return false; + } + + for (int i = 0; i < argc; ++i) + addReadRegister(argv + i, valueType); + + setAccumulator(intType); + m_state.setHasSideEffects(canHaveSideEffects); + return true; + } + + if (name == u"reverse" && argc == 0) { + setAccumulator(baseType); + m_state.setHasSideEffects(canHaveSideEffects); + return true; + } + + if (name == u"slice" && argc < 3) { + for (int i = 0; i < argc; ++i) { + if (!canConvertFromTo(m_state.registers[argv + i].content, intType)) + return false; + } + + for (int i = 0; i < argc; ++i) + addReadRegister(argv + i, intType); + + setAccumulator(baseType.storedType()->isListProperty() + ? m_typeResolver->globalType(m_typeResolver->qObjectListType()) + : baseType); + return true; + } + + if (name == u"splice" && argc > 0) { + for (int i = 0; i < 2; ++i) { + if (!canConvertFromTo(m_state.registers[argv + i].content, intType)) + return false; + } + + for (int i = 2; i < argc; ++i) { + if (!canConvertFromTo(m_state.registers[argv + i].content, valueType)) + return false; + } + + for (int i = 0; i < 2; ++i) + addReadRegister(argv + i, intType); + + for (int i = 2; i < argc; ++i) + addReadRegister(argv + i, valueType); + + setAccumulator(baseType); + m_state.setHasSideEffects(canHaveSideEffects); + return true; + } + + if ((name == u"indexOf" || name == u"lastIndexOf") && argc > 0 && argc < 3) { + if (!canConvertFromTo(m_state.registers[argv].content, valueType)) + return false; + + if (argc == 2) { + if (!canConvertFromTo(m_state.registers[argv + 1].content, intType)) + return false; + addReadRegister(argv + 1, intType); + } + + addReadRegister(argv, valueType); + setAccumulator(intType); + return true; + } + + return false; +} + void QQmlJSTypePropagator::generate_CallPropertyLookup(int lookupIndex, int base, int argc, int argv) { diff --git a/src/qmlcompiler/qqmljstypepropagator_p.h b/src/qmlcompiler/qqmljstypepropagator_p.h index be72d03d94..f83114b669 100644 --- a/src/qmlcompiler/qqmljstypepropagator_p.h +++ b/src/qmlcompiler/qqmljstypepropagator_p.h @@ -213,6 +213,7 @@ private: const QQmlJSScope::ConstPtr &scope); bool propagateTranslationMethod(const QList<QQmlJSMetaMethod> &methods, int argc, int argv); void propagateStringArgCall(int argv); + bool propagateArrayMethod(const QString &name, int argc, int argv, const QQmlJSRegisterContent &valueType); void propagatePropertyLookup(const QString &name); void propagateScopeLookupCall(const QString &functionName, int argc, int argv); void saveRegisterStateForJump(int offset); diff --git a/src/qmlcompiler/qqmljstyperesolver_p.h b/src/qmlcompiler/qqmljstyperesolver_p.h index 57020920d5..1879f8db25 100644 --- a/src/qmlcompiler/qqmljstyperesolver_p.h +++ b/src/qmlcompiler/qqmljstyperesolver_p.h @@ -71,6 +71,7 @@ public: QQmlJSScope::ConstPtr jsGlobalObject() const { return m_jsGlobalObject; } QQmlJSScope::ConstPtr qObjectType() const { return m_qObjectType; } QQmlJSScope::ConstPtr qObjectListType() const { return m_qObjectListType; } + QQmlJSScope::ConstPtr arrayType() const { return m_arrayType; } QQmlJSScope::ConstPtr scopeForLocation(const QV4::CompiledData::Location &location) const; QQmlJSScope::ConstPtr scopeForId( diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt index 7ed6e8f624..85ac7e6e2e 100644 --- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt +++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt @@ -130,6 +130,10 @@ set(qml_files interceptor.qml isnan.qml javaScriptArgument.qml + jsArrayMethods.qml + jsArrayMethodsUntyped.qml + jsArrayMethodsWithParams.qml + jsArrayMethodsWithParamsUntyped.qml jsMathObject.qml jsimport.qml jsmoduleimport.qml diff --git a/tests/auto/qml/qmlcppcodegen/data/jsArrayMethods.qml b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethods.qml new file mode 100644 index 0000000000..ff372bca45 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethods.qml @@ -0,0 +1,28 @@ +pragma Strict +import QML + +QtObject { + id: self + + property QtObject l1: QtObject { objectName: "klaus" } + property QtObject l2: QtObject { function toString(): string { return "teil" } } + property QtObject l3: QtObject { } + + function jsArray() : list<var> { return [l1, l2, l3, l1, l2, l3] } + property list<QtObject> listProperty: [l1, l2, l3, l1, l2, l3] + + property string jsArrayToString: jsArray().toString() + property string listPropertyToString: listProperty.toString() + + property bool listPropertyIncludes: listProperty.includes(l3) + property bool jsArrayIncludes: jsArray().includes(l3) + + property string listPropertyJoin: listProperty.join() + property string jsArrayJoin: jsArray().join() + + property int listPropertyIndexOf: listProperty.indexOf(l2) + property int jsArrayIndexOf: jsArray().indexOf(l2) + + property int listPropertyLastIndexOf: listProperty.lastIndexOf(l3) + property int jsArrayLastIndexOf: jsArray().lastIndexOf(l3) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsUntyped.qml b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsUntyped.qml new file mode 100644 index 0000000000..7426c692fe --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsUntyped.qml @@ -0,0 +1,17 @@ +import QML + +QtObject { + id: self + + property QtObject l1 + property QtObject l2 + property QtObject l3 + + function jsArray() { return [l1, l2, l3, l1, l2, l3] } + + property string jsArrayToString: jsArray().toString() + property bool jsArrayIncludes: jsArray().includes(l3) + property string jsArrayJoin: jsArray().join() + property int jsArrayIndexOf: jsArray().indexOf(l2) + property int jsArrayLastIndexOf: jsArray().lastIndexOf(l3) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParams.qml b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParams.qml new file mode 100644 index 0000000000..293e7cbda5 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParams.qml @@ -0,0 +1,26 @@ +pragma Strict +import QML + +QtObject { + id: self + + required property int i + required property int j + required property int k + + property QtObject l1: QtObject { objectName: "klaus" } + property QtObject l2: QtObject { function toString(): string { return "teil" } } + property QtObject l3: QtObject { } + + function jsArray() : list<var> { return [l1, l2, l3, l1, l2, l3] } + property list<QtObject> listProperty: [l1, l2, l3, l1, l2, l3] + + property list<QtObject> listPropertySlice: listProperty.slice(i, j) + property list<var> jsArraySlice: jsArray().slice(i, j) + + property int listPropertyIndexOf: listProperty.indexOf(l2, i) + property int jsArrayIndexOf: jsArray().indexOf(l2, i) + + property int listPropertyLastIndexOf: listProperty.lastIndexOf(l3, i) + property int jsArrayLastIndexOf: jsArray().lastIndexOf(l3, i) +} diff --git a/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParamsUntyped.qml b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParamsUntyped.qml new file mode 100644 index 0000000000..9e928bd6f6 --- /dev/null +++ b/tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParamsUntyped.qml @@ -0,0 +1,18 @@ +import QML + +QtObject { + id: self + + required property int i + required property int j + required property int k + + property QtObject l1 + property QtObject l2 + property QtObject l3 + + function jsArray() { return [l1, l2, l3, l1, l2, l3] } + property var jsArraySlice: jsArray().slice(i, j) + property int jsArrayIndexOf: jsArray().indexOf(l2, i) + property int jsArrayLastIndexOf: jsArray().lastIndexOf(l3, i) +} diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp index 6ab4cf33cc..25002acd3f 100644 --- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp +++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp @@ -180,6 +180,9 @@ private slots: void mergedObjectReadWrite(); void listConversion(); void thisObject(); + void jsArrayMethods(); + void jsArrayMethodsWithParams_data(); + void jsArrayMethodsWithParams(); }; void tst_QmlCppCodegen::initTestCase() @@ -3629,6 +3632,128 @@ void tst_QmlCppCodegen::thisObject() QCOMPARE(o->property("warned").value<QObject *>(), o.data()); } +static void listsEqual(QObject *listProp, QObject *array, const char *method) +{ + const QByteArray listPropertyPropertyName = QByteArray("listProperty") + method; + const QByteArray jsArrayPropertyName = QByteArray("jsArray") + method; + + const QQmlListReference listPropertyProperty(listProp, listPropertyPropertyName.constData()); + const QVariantList jsArrayProperty = array->property(jsArrayPropertyName.constData()).toList(); + + const qsizetype listPropertyCount = listPropertyProperty.count(); + QCOMPARE(listPropertyCount, jsArrayProperty.count()); + + for (qsizetype i = 0; i < listPropertyCount; ++i) + QCOMPARE(listPropertyProperty.at(i), jsArrayProperty.at(i).value<QObject *>()); +} + +void tst_QmlCppCodegen::jsArrayMethods() +{ + QQmlEngine engine; + QQmlComponent component(&engine, QUrl(u"qrc:/qt/qml/TestTypes/jsArrayMethods.qml"_s)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.create()); + QVERIFY(!object.isNull()); + + QQmlComponent untyped(&engine, QUrl(u"qrc:/qt/qml/TestTypes/jsArrayMethodsUntyped.qml"_s)); + QVERIFY2(untyped.isReady(), qPrintable(untyped.errorString())); + QScopedPointer<QObject> check(untyped.create()); + QVERIFY(!check.isNull()); + + check->setProperty("l1", object->property("l1")); + check->setProperty("l2", object->property("l2")); + check->setProperty("l3", object->property("l3")); + + QCOMPARE(object->property("listPropertyToString"), object->property("jsArrayToString")); + QCOMPARE(object->property("listPropertyToString"), check->property("jsArrayToString")); + + QCOMPARE(object->property("listPropertyIncludes"), object->property("jsArrayIncludes")); + QVERIFY(object->property("listPropertyIncludes").toBool()); + + QCOMPARE(object->property("listPropertyJoin"), object->property("jsArrayJoin")); + QCOMPARE(object->property("listPropertyJoin"), check->property("jsArrayJoin")); + QVERIFY(object->property("listPropertyJoin").toString().contains(QStringLiteral("klaus"))); + + QCOMPARE(object->property("listPropertyIndexOf"), object->property("jsArrayIndexOf")); + QCOMPARE(object->property("listPropertyIndexOf").toInt(), 1); + + QCOMPARE(object->property("listPropertyLastIndexOf"), object->property("jsArrayLastIndexOf")); + QCOMPARE(object->property("listPropertyLastIndexOf").toInt(), 5); +} + +void tst_QmlCppCodegen::jsArrayMethodsWithParams_data() +{ + QTest::addColumn<int>("i"); + QTest::addColumn<int>("j"); + QTest::addColumn<int>("k"); + + const int indices[] = { + std::numeric_limits<int>::min(), + -10, -3, -2, -1, 0, 1, 2, 3, 10, + std::numeric_limits<int>::max(), + }; + + // We cannot test the full cross product. So, take a random sample instead. + const qsizetype numIndices = sizeof(indices) / sizeof(int); + qsizetype seed = QRandomGenerator::global()->generate(); + const int numSamples = 4; + for (int i = 0; i < numSamples; ++i) { + seed = qHash(i, seed); + const int vi = indices[qAbs(seed) % numIndices]; + for (int j = 0; j < numSamples; ++j) { + seed = qHash(j, seed); + const int vj = indices[qAbs(seed) % numIndices]; + for (int k = 0; k < numSamples; ++k) { + seed = qHash(k, seed); + const int vk = indices[qAbs(seed) % numIndices]; + const QString tag = QLatin1String("%1/%2/%3").arg( + QString::number(vi), QString::number(vj), QString::number(vk)); + QTest::newRow(qPrintable(tag)) << vi << vj << vk; + + // output all the tags so that we can find out + // what combination caused a test to hang. + qDebug().noquote() << "scheduling" << tag; + } + } + } +} + +void tst_QmlCppCodegen::jsArrayMethodsWithParams() +{ + QFETCH(int, i); + QFETCH(int, j); + QFETCH(int, k); + QQmlEngine engine; + QQmlComponent component + (&engine, QUrl(u"qrc:/qt/qml/TestTypes/jsArrayMethodsWithParams.qml"_s)); + QVERIFY2(component.isReady(), qPrintable(component.errorString())); + QQmlComponent untyped( + &engine, QUrl(u"qrc:/qt/qml/TestTypes/jsArrayMethodsWithParamsUntyped.qml"_s)); + QVERIFY2(untyped.isReady(), qPrintable(component.errorString())); + QScopedPointer<QObject> object(component.createWithInitialProperties({ + {QStringLiteral("i"), i}, + {QStringLiteral("j"), j}, + {QStringLiteral("k"), k} + })); + QVERIFY(!object.isNull()); + QScopedPointer<QObject> check(untyped.createWithInitialProperties({ + {QStringLiteral("i"), i}, + {QStringLiteral("j"), j}, + {QStringLiteral("k"), k} + })); + QVERIFY(!check.isNull()); + check->setProperty("l1", object->property("l1")); + check->setProperty("l2", object->property("l2")); + check->setProperty("l3", object->property("l3")); + + listsEqual(object.data(), object.data(), "Slice"); + listsEqual(object.data(), check.data(), "Slice"); + QCOMPARE(object->property("listPropertyIndexOf"), object->property("jsArrayIndexOf")); + QCOMPARE(object->property("listPropertyIndexOf"), check->property("jsArrayIndexOf")); + QCOMPARE(object->property("listPropertyLastIndexOf"), object->property("jsArrayLastIndexOf")); + QCOMPARE(object->property("listPropertyLastIndexOf"), check->property("jsArrayLastIndexOf")); +} + QTEST_MAIN(tst_QmlCppCodegen) #include "tst_qmlcppcodegen.moc" |