aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2023-01-03 15:45:31 +0100
committerUlf Hermann <ulf.hermann@qt.io>2023-01-20 20:39:11 +0100
commitec58c0ddb7fe1ebf33c80335ab9435e53fd00274 (patch)
treed30a8779fa5fb82bcc9e3b31e581d3962dc81d21
parentea5200b82f21e0f4d080d3fc256b218e0ee56f3d (diff)
QML: Add a pragma for value type behavior
Unfortunately value types behave differently when compiled to C++. Document the difference and introduce a pragma to make them behave one way or the other. Pick-to: 6.5 Fixes: QTBUG-109221 Change-Id: Ib2685153c0b4ae209bafbea7a01229377fdb47dd Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r--src/qml/common/qv4compileddata_p.h1
-rw-r--r--src/qml/compiler/qqmlirbuilder.cpp39
-rw-r--r--src/qml/compiler/qqmlirbuilder_p.h8
-rw-r--r--src/qml/doc/src/qmllanguageref/documents/definetypes.qdoc56
-rw-r--r--src/qml/jsruntime/qv4executablecompilationunit_p.h5
-rw-r--r--src/qml/jsruntime/qv4qobjectwrapper.cpp26
-rw-r--r--src/qml/qml/qqmlirloader.cpp9
-rw-r--r--src/qmlcompiler/qqmljscodegenerator.cpp31
-rw-r--r--src/qmlcompiler/qqmljsimportvisitor.cpp37
-rw-r--r--src/qmlcompiler/qqmljsscopesbyid_p.h4
-rw-r--r--src/qmlcompiler/qqmljstyperesolver_p.h1
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt2
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/valueTypeCopy.qml34
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/valueTypeReference.qml34
-rw-r--r--tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp35
15 files changed, 286 insertions, 36 deletions
diff --git a/src/qml/common/qv4compileddata_p.h b/src/qml/common/qv4compileddata_p.h
index 58a7009083..cd96067769 100644
--- a/src/qml/common/qv4compileddata_p.h
+++ b/src/qml/common/qv4compileddata_p.h
@@ -1184,6 +1184,7 @@ struct Unit
ComponentsBound = 0x200,
FunctionSignaturesEnforced = 0x400,
NativeMethodsAcceptThisObject = 0x800,
+ ValueTypesCopied = 0x1000,
};
quint32_le flags;
quint32_le stringTableSize;
diff --git a/src/qml/compiler/qqmlirbuilder.cpp b/src/qml/compiler/qqmlirbuilder.cpp
index b2d5f2c3d2..3fd66d302c 100644
--- a/src/qml/compiler/qqmlirbuilder.cpp
+++ b/src/qml/compiler/qqmlirbuilder.cpp
@@ -788,6 +788,8 @@ private:
return Pragma::FunctionSignatureBehavior;
} else if constexpr (std::is_same_v<Argument, Pragma::NativeMethodBehaviorValue>) {
return Pragma::NativeMethodBehavior;
+ } else if constexpr (std::is_same_v<Argument, Pragma::ValueTypeBehaviorValue>) {
+ return Pragma::ValueTypeBehavior;
}
Q_UNREACHABLE_RETURN(Pragma::PragmaType(-1));
@@ -838,6 +840,15 @@ private:
pragma->nativeMethodBehavior = Pragma::RejectThisObject;
return true;
}
+ } else if constexpr (std::is_same_v<Argument, Pragma::ValueTypeBehaviorValue>) {
+ if (value == "Reference"_L1) {
+ pragma->valueTypeBehavior = Pragma::Reference;
+ return true;
+ }
+ if (value == "Copy"_L1) {
+ pragma->valueTypeBehavior = Pragma::Copy;
+ return true;
+ }
}
return false;
@@ -863,6 +874,8 @@ private:
return "function signature behavior"_L1;
case Pragma::NativeMethodBehavior:
return "native method behavior"_L1;
+ case Pragma::ValueTypeBehavior:
+ return "value type behavior"_L1;
default:
break;
}
@@ -875,22 +888,25 @@ bool IRBuilder::visit(QQmlJS::AST::UiPragma *node)
Pragma *pragma = New<Pragma>();
if (!node->name.isNull()) {
- if (node->name == QStringLiteral("Singleton")) {
+ if (node->name == "Singleton"_L1) {
pragma->type = Pragma::Singleton;
- } else if (node->name == QStringLiteral("Strict")) {
+ } else if (node->name == "Strict"_L1) {
pragma->type = Pragma::Strict;
- } else if (node->name == QStringLiteral("ComponentBehavior")) {
+ } else if (node->name == "ComponentBehavior"_L1) {
if (!PragmaParser<Pragma::ComponentBehaviorValue>::run(this, node, pragma))
return false;
- } else if (node->name == QStringLiteral("ListPropertyAssignBehavior")) {
+ } else if (node->name == "ListPropertyAssignBehavior"_L1) {
if (!PragmaParser<Pragma::ListPropertyAssignBehaviorValue>::run(this, node, pragma))
return false;
- } else if (node->name == QStringLiteral("FunctionSignatureBehavior")) {
+ } else if (node->name == "FunctionSignatureBehavior"_L1) {
if (!PragmaParser<Pragma::FunctionSignatureBehaviorValue>::run(this, node, pragma))
return false;
- } else if (node->name == QStringLiteral("NativeMethodBehavior")) {
+ } else if (node->name == "NativeMethodBehavior"_L1) {
if (!PragmaParser<Pragma::NativeMethodBehaviorValue>::run(this, node, pragma))
return false;
+ } else if (node->name == "ValueTypeBehavior"_L1) {
+ if (!PragmaParser<Pragma::ValueTypeBehaviorValue>::run(this, node, pragma))
+ return false;
} else {
recordError(node->pragmaToken, QCoreApplication::translate(
"QQmlParser", "Unknown pragma '%1'").arg(node->name));
@@ -1671,6 +1687,17 @@ void QmlUnitGenerator::generate(Document &output, const QV4::CompiledData::Depen
// this is the default;
break;
}
+ break;
+ case Pragma::ValueTypeBehavior:
+ switch (p->valueTypeBehavior) {
+ case Pragma::Copy:
+ createdUnit->flags |= Unit::ValueTypesCopied;
+ break;
+ case Pragma::Reference:
+ //this is the default;
+ break;
+ }
+ break;
}
}
diff --git a/src/qml/compiler/qqmlirbuilder_p.h b/src/qml/compiler/qqmlirbuilder_p.h
index 852c2d2244..d4599c3757 100644
--- a/src/qml/compiler/qqmlirbuilder_p.h
+++ b/src/qml/compiler/qqmlirbuilder_p.h
@@ -407,6 +407,7 @@ struct Q_QML_COMPILER_PRIVATE_EXPORT Pragma
ComponentBehavior,
FunctionSignatureBehavior,
NativeMethodBehavior,
+ ValueTypeBehavior,
};
enum ListPropertyAssignBehaviorValue
@@ -434,6 +435,12 @@ struct Q_QML_COMPILER_PRIVATE_EXPORT Pragma
RejectThisObject
};
+ enum ValueTypeBehaviorValue
+ {
+ Reference,
+ Copy
+ };
+
PragmaType type;
union {
@@ -441,6 +448,7 @@ struct Q_QML_COMPILER_PRIVATE_EXPORT Pragma
ComponentBehaviorValue componentBehavior;
FunctionSignatureBehaviorValue functionSignatureBehavior;
NativeMethodBehaviorValue nativeMethodBehavior;
+ ValueTypeBehaviorValue valueTypeBehavior;
};
QV4::CompiledData::Location location;
diff --git a/src/qml/doc/src/qmllanguageref/documents/definetypes.qdoc b/src/qml/doc/src/qmllanguageref/documents/definetypes.qdoc
index d0faf04a04..dde54e2af6 100644
--- a/src/qml/doc/src/qmllanguageref/documents/definetypes.qdoc
+++ b/src/qml/doc/src/qmllanguageref/documents/definetypes.qdoc
@@ -319,17 +319,69 @@ the file, no matter how deeply nested.
With this pragma you can change the way type annotations on functions are
handled. By default the interpreter and JIT ignore type annotations, but
-\l{qmlcachegen} and \l{qmlsc} enforce them when compiling to C++.
+the \l{QML Script Compiler} enforces them when compiling to C++.
Specifying \c{Enforce} as value makes sure the type annotations are always
enforced. The resulting type coercions increase the overhead of calling
typed JavaScript functions.
-Specifying \c{Ignore} as value makes \l{qmlsc} and \l{qmlcachegen} ignore
+Specifying \c{Ignore} as value makes the \l{QML Script Compiler} ignore
any JavaScript functions when compiling the document to C++. This means less
code is compiled to C++ ahead of time, and more code has to be interpreted or
JIT-compiled.
\sa {Type annotations and assertions}
+\section2 ValueTypeBehavior
+
+The behavior of \l{QML Value Types} and list types differs slightly
+depending on whether a QML document is compiled to C++ using the
+\l{QML Script Compiler} or interpreted at run time.
+
+With this pragma you can change the way value types and sequences are handled
+when retrieved as locals from properties. By default, the interpreter and JIT
+treat all value types and sequences as references. This means, if you change
+the local value, the original property is also changed. Furthermore, if you
+write the original property explicitly, the local value is also updated.
+
+When compiled to C++ using the \l{QML Script Compiler}, the local value is not
+updated when the property is written, and the property is only updated when
+written directly, without retrieving it as local value before.
+
+For example, the following code prints "1 1" when compiled to C++ and "5 5"
+when interpreted or JIT-compiled:
+
+\qml
+import QtQml
+
+QtObject {
+ id: root
+
+ property rect r: ({x: 1, y: 2, width: 3, height: 4})
+ property list<double> numbers: [1, 2, 3, 4, 5]
+
+ function manipulate() {
+ root.r = {x: 5, y: 6, width: 7, height: 8};
+ root.numbers = [5, 4, 3, 2, 1];
+ }
+
+ Component.onCompleted: {
+ var numbers = root.numbers;
+ var r = root.r;
+ manipulate()
+ console.log(r.x, numbers[0]);
+ }
+}
+\endqml
+
+You may notice that the behavior when interpreted or JIT-compiled can be rather
+confusing.
+
+Specifying \c{Copy} as value to the pragma makes the interpreter and JIT behave
+like the generated C++ code. This is the recommended way to handle the problem.
+Specifying \c{Reference} makes the \l{QML Script Compiler} skip any functions
+that use value types or sequences when generating C++ code. Those functions are
+then left to be interpreted or JIT-compiled with the default behavior of the
+interpreter and JIT.
+
*/
diff --git a/src/qml/jsruntime/qv4executablecompilationunit_p.h b/src/qml/jsruntime/qv4executablecompilationunit_p.h
index 0b27e68a20..e6472f3f83 100644
--- a/src/qml/jsruntime/qv4executablecompilationunit_p.h
+++ b/src/qml/jsruntime/qv4executablecompilationunit_p.h
@@ -176,6 +176,11 @@ public:
return data->flags & CompiledData::Unit::NativeMethodsAcceptThisObject;
}
+ bool valueTypesAreCopied() const
+ {
+ return data->flags & CompiledData::Unit::ValueTypesCopied;
+ }
+
int objectCount() const { return qmlData->nObjects; }
const CompiledObject *objectAt(int index) const
{
diff --git a/src/qml/jsruntime/qv4qobjectwrapper.cpp b/src/qml/jsruntime/qv4qobjectwrapper.cpp
index d9bbae08a7..694b7828b6 100644
--- a/src/qml/jsruntime/qv4qobjectwrapper.cpp
+++ b/src/qml/jsruntime/qv4qobjectwrapper.cpp
@@ -94,11 +94,20 @@ static QPair<QObject *, int> extractQtSignal(const Value &value)
return qMakePair((QObject *)nullptr, -1);
}
-static Heap::ReferenceObject::Flags referenceFlags(const QQmlPropertyData &property)
+static Heap::ReferenceObject::Flags referenceFlags(
+ ExecutionEngine *v4,
+ const QQmlPropertyData &property)
{
- return property.isWritable()
- ? Heap::ReferenceObject::CanWriteBack
- : Heap::ReferenceObject::NoFlag;
+ Heap::ReferenceObject::Flags flags = Heap::ReferenceObject::NoFlag;
+ if (CppStackFrame *stackFrame = v4->currentStackFrame) {
+ if (stackFrame->v4Function->executableCompilationUnit()->valueTypesAreCopied())
+ flags |= Heap::ReferenceObject::EnforcesLocation;
+ }
+
+ if (property.isWritable())
+ flags |= Heap::ReferenceObject::CanWriteBack;
+
+ return flags;
}
static ReturnedValue loadProperty(
@@ -170,7 +179,7 @@ static ReturnedValue loadProperty(
property.readProperty(object, &v);
return scope.engine->fromVariant(
v, wrapper, property.coreIndex(),
- referenceFlags(property) | Heap::ReferenceObject::IsVariant);
+ referenceFlags(scope.engine, property) | Heap::ReferenceObject::IsVariant);
}
if (!propMetaType.isValid()) {
@@ -190,7 +199,7 @@ static ReturnedValue loadProperty(
// Lazy loaded value type reference. Pass nullptr as data.
return QQmlValueTypeWrapper::create(
v4, nullptr, valueTypeMetaObject, propMetaType, wrapper,
- property.coreIndex(), referenceFlags(property));
+ property.coreIndex(), referenceFlags(scope.engine, property));
}
}
@@ -198,14 +207,15 @@ static ReturnedValue loadProperty(
// Pass nullptr as data. It's lazy-loaded.
QV4::ScopedValue retn(scope, QV4::SequencePrototype::newSequence(
v4, propMetaType, nullptr,
- wrapper, property.coreIndex(), referenceFlags(property)));
+ wrapper, property.coreIndex(),
+ referenceFlags(scope.engine, property)));
if (!retn->isUndefined())
return retn->asReturnedValue();
QVariant v(propMetaType);
property.readProperty(object, v.data());
return scope.engine->fromVariant(
- v, wrapper, property.coreIndex(), referenceFlags(property));
+ v, wrapper, property.coreIndex(), referenceFlags(scope.engine, property));
}
void QObjectWrapper::initializeBindings(ExecutionEngine *engine)
diff --git a/src/qml/qml/qqmlirloader.cpp b/src/qml/qml/qqmlirloader.cpp
index c80f9b4bd1..5137c4c1ab 100644
--- a/src/qml/qml/qqmlirloader.cpp
+++ b/src/qml/qml/qqmlirloader.cpp
@@ -55,6 +55,12 @@ void QQmlIRLoader::load()
createPragma(type)->nativeMethodBehavior = value;
};
+ const auto createValueTypePragma = [&](
+ Pragma::PragmaType type,
+ Pragma::ValueTypeBehaviorValue value) {
+ createPragma(type)->valueTypeBehavior = value;
+ };
+
if (unit->flags & QV4::CompiledData::Unit::IsSingleton)
createPragma(Pragma::Singleton);
if (unit->flags & QV4::CompiledData::Unit::IsStrict)
@@ -74,6 +80,9 @@ void QQmlIRLoader::load()
if (unit->flags & QV4::CompiledData::Unit::NativeMethodsAcceptThisObject)
createNativeMethodPragma(Pragma::NativeMethodBehavior, Pragma::AcceptThisObject);
+ if (unit->flags & QV4::CompiledData::Unit::ValueTypesCopied)
+ createValueTypePragma(Pragma::ValueTypeBehavior, Pragma::Copy);
+
for (uint i = 0; i < qmlUnit->nObjects; ++i) {
const QV4::CompiledData::Object *serializedObject = qmlUnit->objectAt(i);
QmlIR::Object *object = loadObject(serializedObject);
diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp
index 8d8a7c9118..8ebd38d753 100644
--- a/src/qmlcompiler/qqmljscodegenerator.cpp
+++ b/src/qmlcompiler/qqmljscodegenerator.cpp
@@ -719,6 +719,8 @@ void QQmlJSCodeGenerator::generate_LoadElement(int base)
// TODO: Once we get a char type in QML, use it here.
if (m_typeResolver->registerIsStoredIn(baseType, m_typeResolver->stringType()))
access = u"QString("_s + access + u")"_s;
+ else if (!m_typeResolver->canUseValueTypes())
+ reject(u"LoadElement in sequence type reference"_s);
m_body += u"if ("_s + indexName + u" >= 0 && "_s + indexName
+ u" < "_s + baseName + u".size())\n"_s;
@@ -741,7 +743,10 @@ void QQmlJSCodeGenerator::generate_StoreElement(int base, int index)
}
if (!m_typeResolver->registerIsStoredIn(baseType, m_typeResolver->listPropertyType())) {
- reject(u"indirect StoreElement"_s);
+ if (m_typeResolver->canUseValueTypes())
+ reject(u"indirect StoreElement"_s);
+ else
+ reject(u"StoreElement in sequence type reference"_s);
return;
}
@@ -1041,7 +1046,7 @@ void QQmlJSCodeGenerator::generate_GetLookup(int index)
+ u";\n"_s;
} else if (m_typeResolver->registerIsStoredIn(accumulatorIn, m_typeResolver->jsValueType())) {
reject(u"lookup in QJSValue"_s);
- } else {
+ } else if (m_typeResolver->canUseValueTypes()) {
const QString lookup = u"aotContext->getValueLookup("_s + indexString
+ u", "_s + contentPointer(m_state.accumulatorIn(),
m_state.accumulatorVariableIn)
@@ -1055,6 +1060,8 @@ void QQmlJSCodeGenerator::generate_GetLookup(int index)
const QString preparation = getLookupPreparation(
m_state.accumulatorOut(), m_state.accumulatorVariableOut, index);
generateLookup(lookup, initialization, preparation);
+ } else {
+ reject(u"lookup in value type reference"_s);
}
}
@@ -1148,7 +1155,10 @@ void QQmlJSCodeGenerator::generate_SetLookup(int index, int baseReg)
}
if (!m_typeResolver->registerIsStoredIn(callBase, m_typeResolver->listPropertyType())) {
- reject(u"SetLookup on sequence types (because of missing write-back)"_s);
+ if (m_typeResolver->canUseValueTypes())
+ reject(u"resizing sequence types (because of missing write-back)"_s);
+ else
+ reject(u"resizing sequence type references"_s);
break;
}
@@ -1179,7 +1189,10 @@ void QQmlJSCodeGenerator::generate_SetLookup(int index, int baseReg)
+ u", "_s + contentType(registerType(baseReg), object) + u')';
generateLookup(lookup, initialization, preparation);
- reject(u"SetLookup on value types (because of missing write-back)"_s);
+ if (m_typeResolver->canUseValueTypes())
+ reject(u"SetLookup on value types (because of missing write-back)"_s);
+ else
+ reject(u"SetLookup on value type references"_s);
break;
}
case QQmlJSScope::AccessSemantics::None:
@@ -1654,8 +1667,14 @@ void QQmlJSCodeGenerator::generate_CallPropertyLookup(int index, int base, int a
return;
}
-
- reject(u"call to property '%1' of %2"_s.arg(name, baseType.descriptiveName()));
+ 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()));
+ } else {
+ // This is not possible.
+ reject(u"call to property '%1' of value type reference %2"_s
+ .arg(name, baseType.descriptiveName()));
+ }
}
const QString indexString = QString::number(index);
diff --git a/src/qmlcompiler/qqmljsimportvisitor.cpp b/src/qmlcompiler/qqmljsimportvisitor.cpp
index 7401767e66..ee189f0788 100644
--- a/src/qmlcompiler/qqmljsimportvisitor.cpp
+++ b/src/qmlcompiler/qqmljsimportvisitor.cpp
@@ -2268,19 +2268,19 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiImport *import)
bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiPragma *pragma)
{
- // If a file uses pragma Strict, it expects to be compiled, so automatically
- // enable compiler warnings unless the level is set explicitly already (e.g.
- // by the user).
- if (pragma->name == u"Strict"_s && !m_logger->wasCategoryChanged(qmlCompiler)) {
- // TODO: the logic here is rather complicated and may be buggy
- m_logger->setCategoryLevel(qmlCompiler, QtWarningMsg);
- m_logger->setCategoryIgnored(qmlCompiler, false);
- }
+ if (pragma->name == u"Strict"_s) {
+ // If a file uses pragma Strict, it expects to be compiled, so automatically
+ // enable compiler warnings unless the level is set explicitly already (e.g.
+ // by the user).
- if (pragma->name == u"Singleton")
+ if (!m_logger->wasCategoryChanged(qmlCompiler)) {
+ // TODO: the logic here is rather complicated and may be buggy
+ m_logger->setCategoryLevel(qmlCompiler, QtWarningMsg);
+ m_logger->setCategoryIgnored(qmlCompiler, false);
+ }
+ } else if (pragma->name == u"Singleton") {
m_rootIsSingleton = true;
-
- if (pragma->name == u"ComponentBehavior") {
+ } else if (pragma->name == u"ComponentBehavior") {
if (pragma->value == u"Bound") {
m_scopesById.setComponentsAreBound(true);
} else if (pragma->value == u"Unbound") {
@@ -2290,9 +2290,7 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiPragma *pragma)
u"Unkonwn argument \"%s\" to pragma ComponentBehavior"_s.arg(pragma->value),
qmlSyntax, pragma->firstSourceLocation());
}
- }
-
- if (pragma->name == u"FunctionSignatureBehavior") {
+ } else if (pragma->name == u"FunctionSignatureBehavior") {
if (pragma->value == u"Enforced") {
m_scopesById.setSignaturesAreEnforced(true);
} else if (pragma->value == u"Ignored") {
@@ -2303,6 +2301,17 @@ bool QQmlJSImportVisitor::visit(QQmlJS::AST::UiPragma *pragma)
.arg(pragma->value),
qmlSyntax, pragma->firstSourceLocation());
}
+ } else if (pragma->name == u"ValueTypeBehavior") {
+ if (pragma->value == u"Copy") {
+ m_scopesById.setValueTypesAreCopied(true);
+ } else if (pragma->value == u"Reference") {
+ m_scopesById.setValueTypesAreCopied(false);
+ } else {
+ m_logger->log(
+ u"Unkonwn argument \"%s\" to pragma ValueTypeBehavior"_s
+ .arg(pragma->value),
+ qmlSyntax, pragma->firstSourceLocation());
+ }
}
return true;
diff --git a/src/qmlcompiler/qqmljsscopesbyid_p.h b/src/qmlcompiler/qqmljsscopesbyid_p.h
index dcf72d5003..77ad1947ab 100644
--- a/src/qmlcompiler/qqmljsscopesbyid_p.h
+++ b/src/qmlcompiler/qqmljsscopesbyid_p.h
@@ -31,6 +31,9 @@ public:
void setSignaturesAreEnforced(bool enforced) { m_signaturesAreEnforced = enforced; }
bool signaturesAreEnforced() const { return m_signaturesAreEnforced; }
+ void setValueTypesAreCopied(bool copied) { m_valueTypesAreCopied = copied; }
+ bool valueTypesAreCopied() const { return m_valueTypesAreCopied; }
+
QString id(const QQmlJSScope::ConstPtr &scope) const
{
for (auto it = m_scopesById.begin(), end = m_scopesById.end(); it != end; ++it) {
@@ -108,6 +111,7 @@ private:
QMultiHash<QString, QQmlJSScope::ConstPtr> m_scopesById;
bool m_componentsAreBound = false;
bool m_signaturesAreEnforced = true;
+ bool m_valueTypesAreCopied = true;
};
QT_END_NAMESPACE
diff --git a/src/qmlcompiler/qqmljstyperesolver_p.h b/src/qmlcompiler/qqmljstyperesolver_p.h
index 453e06ac52..6c3583ca73 100644
--- a/src/qmlcompiler/qqmljstyperesolver_p.h
+++ b/src/qmlcompiler/qqmljstyperesolver_p.h
@@ -144,6 +144,7 @@ public:
const QQmlJSScopesById &objectsById() const { return m_objectsById; }
bool canCallJSFunctions() const { return m_objectsById.signaturesAreEnforced(); }
+ bool canUseValueTypes() const { return m_objectsById.valueTypesAreCopied(); }
const QHash<QQmlJS::SourceLocation, QQmlJSMetaSignalHandler> &signalHandlers() const
{
diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
index e66af303ca..be74e062c0 100644
--- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
+++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
@@ -190,8 +190,10 @@ set(qml_files
unusedAttached.qml
urlString.qml
usingCxxTypesFromFileImports.qml
+ valueTypeCopy.qml
valueTypeLists.qml
valueTypeProperty.qml
+ valueTypeReference.qml
variantlist.qml
versionmismatch.qml
voidfunction.qml
diff --git a/tests/auto/qml/qmlcppcodegen/data/valueTypeCopy.qml b/tests/auto/qml/qmlcppcodegen/data/valueTypeCopy.qml
new file mode 100644
index 0000000000..cca634753d
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/valueTypeCopy.qml
@@ -0,0 +1,34 @@
+pragma ValueTypeBehavior: Copy
+import QtQml
+
+QtObject {
+ id: root
+
+ property list<double> numbers: {
+ var result = [];
+ for (var i = 0; i < 10; ++i)
+ result[i] = i;
+ return result;
+ }
+
+ property rect r: ({x: 1, y: 2, width: 3, height: 4})
+
+ function evil() : double {
+ var numbers = root.numbers;
+ root.numbers = [];
+ var a = 0;
+ for (var j = 0; j < 10; ++j) {
+ a += numbers[j];
+ }
+ return a;
+ }
+
+ function fvil() : double {
+ var r = root.r;
+ root.r = {x: 5, y: 6, width: 7, height: 8};
+ return r.x;
+ }
+
+ property double e: evil()
+ property double f: fvil()
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/valueTypeReference.qml b/tests/auto/qml/qmlcppcodegen/data/valueTypeReference.qml
new file mode 100644
index 0000000000..568f39820c
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/valueTypeReference.qml
@@ -0,0 +1,34 @@
+pragma ValueTypeBehavior: Reference
+import QtQml
+
+QtObject {
+ id: root
+
+ property list<double> numbers: {
+ var result = [];
+ for (var i = 0; i < 10; ++i)
+ result[i] = i;
+ return result;
+ }
+
+ property rect r: ({x: 1, y: 2, width: 3, height: 4})
+
+ function evil() : double {
+ var numbers = root.numbers;
+ root.numbers = [];
+ var a = 0;
+ for (var j = 0; j < 10; ++j) {
+ a += numbers[j];
+ }
+ return a;
+ }
+
+ function fvil() : double {
+ var r = root.r;
+ root.r = {x: 5, y: 6, width: 7, height: 8};
+ return r.x;
+ }
+
+ property double e: evil()
+ property double f: fvil()
+}
diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
index 1e88ecd7b7..405e65853a 100644
--- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
+++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
@@ -162,6 +162,7 @@ private slots:
void equalityVarAndNonStorable();
void equalityQObjects();
void dateConversions();
+ void valueTypeBehavior();
};
void tst_QmlCppCodegen::initTestCase()
@@ -3125,6 +3126,40 @@ void tst_QmlCppCodegen::dateConversions()
QCOMPARE(ref->myTime(), (engine.coerceValue<QDate, QTime>(date)));
}
+static QRegularExpression bindingLoopMessage(const QUrl &url, char var)
+{
+ // The actual string depends on how many times QObject* was registered with what parameters.
+ return QRegularExpression(
+ "%1:4:1: QML [^:]+: Binding loop detected for property \"%2\""_L1
+ .arg(url.toString()).arg(QLatin1Char(var)));
+}
+
+void tst_QmlCppCodegen::valueTypeBehavior()
+{
+ QQmlEngine engine;
+
+ const QUrl copy(u"qrc:/qt/qml/TestTypes/valueTypeCopy.qml"_s);
+
+ QQmlComponent c1(&engine, copy);
+ QVERIFY2(c1.isReady(), qPrintable(c1.errorString()));
+ QTest::ignoreMessage(QtWarningMsg, bindingLoopMessage(copy, 'e'));
+ QTest::ignoreMessage(QtWarningMsg, bindingLoopMessage(copy, 'f'));
+ QScopedPointer<QObject> o1(c1.create());
+ QVERIFY(!o1.isNull());
+ QCOMPARE(o1->property("e").toDouble(), 45.0);
+ QCOMPARE(o1->property("f").toDouble(), 1.0);
+
+ const QUrl reference(u"qrc:/qt/qml/TestTypes/valueTypeReference.qml"_s);
+ QQmlComponent c2(&engine, reference);
+ QVERIFY2(c2.isReady(), qPrintable(c2.errorString()));
+ QTest::ignoreMessage(QtWarningMsg, bindingLoopMessage(reference, 'e'));
+ QTest::ignoreMessage(QtWarningMsg, bindingLoopMessage(reference, 'f'));
+ QScopedPointer<QObject> o2(c2.create());
+ QVERIFY(!o2.isNull());
+ QVERIFY(qIsNaN(o2->property("e").toDouble()));
+ QCOMPARE(o2->property("f").toDouble(), 5.0);
+}
+
QTEST_MAIN(tst_QmlCppCodegen)
#include "tst_qmlcppcodegen.moc"