aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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"