aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2023-04-18 15:54:49 +0200
committerUlf Hermann <ulf.hermann@qt.io>2023-04-28 21:24:17 +0200
commite84686415187455a7153d61ca82478053f13e3f9 (patch)
tree3c6d6c123c0f8094a68efc67864437c43e093cc2
parenta63347100c74ea857d3595aa564d95e5e2416508 (diff)
QmlCompiler: Inline some array methods
So far we can only deal with methods that don't change the source array and don't use iterators or functions as parameters. We also omit concat() for now. However, indexOf(), lastIndexOf(), includes(), join(), slice() and toString() are possible already now. Task-number: QTBUG-112722 Change-Id: Id19c74e8ad25af876bc954c040c767823b7e3259 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r--src/qml/CMakeLists.txt1
-rw-r--r--src/qml/jsapi/qjslist.cpp18
-rw-r--r--src/qml/jsapi/qjslist.h282
-rw-r--r--src/qml/qml/qqmllist.h2
-rw-r--r--src/qmlcompiler/qqmljscodegenerator.cpp84
-rw-r--r--src/qmlcompiler/qqmljscodegenerator_p.h6
-rw-r--r--src/qmlcompiler/qqmljstypepropagator.cpp178
-rw-r--r--src/qmlcompiler/qqmljstypepropagator_p.h1
-rw-r--r--src/qmlcompiler/qqmljstyperesolver_p.h1
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt4
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/jsArrayMethods.qml28
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsUntyped.qml17
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParams.qml26
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/jsArrayMethodsWithParamsUntyped.qml18
-rw-r--r--tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp125
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"