aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorUlf Hermann <ulf.hermann@qt.io>2023-02-16 10:54:34 +0100
committerUlf Hermann <ulf.hermann@qt.io>2023-03-03 12:02:00 +0100
commit05f56d7c78754855c643470ad4e8dfd35c96f927 (patch)
treefe23c5afec83efd75f06046bc0fdb7b7da465999
parente625e39845ccecda871659a8ff39ac081f4aee82 (diff)
QML: Allow as-casting to value types
If the "Addressable" option to ValueTypeBehavior is set, you can use the "as" operator to cast a previously unknown type into either undefined or the given type. We can use this in qmlcachegen to generate efficient code for further operations on the same type. In the generated C++ it in fact only works for GetLookup because: a, We generally don't do SetLookup on value types, yet. b, We generally don't call methods on value types, yet. c, We cannot store a union of undefined and a sequence type, yet. However, getting properties of value types is the most important application of the new casts so this is well worth it. As a side effect we can also look up things in potentially undefined results of other operations now. For example list lookups. Task-number: QTBUG-94807 Change-Id: Ifdf34f1f3f67b7a0a8953b9ed0e947b74638a28c Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
-rw-r--r--src/qml/doc/src/qmllanguageref/documents/definetypes.qdoc40
-rw-r--r--src/qml/jsruntime/qv4qmlcontext.cpp2
-rw-r--r--src/qml/jsruntime/qv4runtime.cpp36
-rw-r--r--src/qml/qml/qqmltypewrapper.cpp37
-rw-r--r--src/qml/qml/qqmlvaluetypewrapper_p.h1
-rw-r--r--src/qmlcompiler/qqmljscodegenerator.cpp167
-rw-r--r--src/qmlcompiler/qqmljscodegenerator_p.h11
-rw-r--r--src/qmlcompiler/qqmljstypepropagator.cpp21
-rw-r--r--src/qmlcompiler/qqmljstyperesolver.cpp78
-rw-r--r--src/qmlcompiler/qqmljstyperesolver_p.h6
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt2
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/math.qml1
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/objectLookupOnListElement.qml34
-rw-r--r--tests/auto/qml/qmlcppcodegen/data/valueTypeCast.qml9
-rw-r--r--tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp45
-rw-r--r--tests/auto/qml/qqmllanguage/data/asValueType.qml10
-rw-r--r--tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp26
17 files changed, 463 insertions, 63 deletions
diff --git a/src/qml/doc/src/qmllanguageref/documents/definetypes.qdoc b/src/qml/doc/src/qmllanguageref/documents/definetypes.qdoc
index dde54e2af6..a1bd1452d0 100644
--- a/src/qml/doc/src/qmllanguageref/documents/definetypes.qdoc
+++ b/src/qml/doc/src/qmllanguageref/documents/definetypes.qdoc
@@ -384,4 +384,44 @@ 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.
+In addition, you can specify \c{Addressable} to refer to value types in all
+places. This allows a JavaScript value to be explicitly coerced to a specific,
+named, value type. This is done using the \c as operator, like you would do
+with object types. Furthermore, you can also check for value types using the
+\c instanceof operator:
+
+\qml
+pragma ValueTypeBehavior: Addressable
+import QtQml
+
+QtObject {
+ property var a
+ property real b: (a as rect).x
+ property bool c: a instanceof rect
+
+ property var rect // inaccessible. "rect" is a type name.
+}
+\endqml
+
+If the type does not match, casting returns \c undefined. \c instanceof
+only checks for inheritance, not for all possible type coercions. So, for
+example, a \l{QRect} is not a \c rect value type since \c rect is \l{QRectF}
+in C++, and therefore not related by inheritance. With \c as you can cast
+to any type compatible via coercion.
+
+Since \c rect in the above example is now a type name, it will shadow any
+properties called \c{rect}.
+
+Explicitly casting to the desired type helps tooling. It can allow the
+\l{Qt Quick Compiler} generate efficient code where it otherwise would not be
+able to. You can use \l{qmllint} to find such occurrences.
+
+There is also a \c{Inaddressable} value you can use to explicitly specify the
+default behavior. The pragma values can be combined as comma-separated list:
+
+\qml
+pragma ValueTypeBehavior: Copy, Addressable
+\endqml
+
+\sa {Type annotations and assertions}
*/
diff --git a/src/qml/jsruntime/qv4qmlcontext.cpp b/src/qml/jsruntime/qv4qmlcontext.cpp
index 1cabf7c6c2..e585adfa5b 100644
--- a/src/qml/jsruntime/qv4qmlcontext.cpp
+++ b/src/qml/jsruntime/qv4qmlcontext.cpp
@@ -196,7 +196,7 @@ ReturnedValue QQmlContextWrapper::getPropertyAndBase(const QQmlContextWrapper *r
return result->asReturnedValue();
}
- if (context->imports() && name->startsWithUpper()) {
+ if (context->imports() && (name->startsWithUpper() || context->valueTypesAreAddressable())) {
// Search for attached properties, enums and imported scripts
QQmlTypeNameCache::Result r = context->imports()->query<QQmlImport::AllowRecursion>(name);
diff --git a/src/qml/jsruntime/qv4runtime.cpp b/src/qml/jsruntime/qv4runtime.cpp
index aa4d5c875a..c7ee35b810 100644
--- a/src/qml/jsruntime/qv4runtime.cpp
+++ b/src/qml/jsruntime/qv4runtime.cpp
@@ -329,7 +329,7 @@ ReturnedValue Runtime::DeleteName::call(ExecutionEngine *engine, Function *funct
}
}
-QV4::ReturnedValue Runtime::Instanceof::call(ExecutionEngine *engine, const Value &lval, const Value &rval)
+static QV4::ReturnedValue doInstanceof(ExecutionEngine *engine, const Value &lval, const Value &rval)
{
// 11.8.6, 5: rval must be an Object
const Object *rhs = rval.as<Object>();
@@ -345,26 +345,48 @@ QV4::ReturnedValue Runtime::Instanceof::call(ExecutionEngine *engine, const Valu
Scope scope(engine);
ScopedValue hasInstance(scope, rhs->get(engine->symbol_hasInstance()));
if (hasInstance->isUndefined())
- return rhs->instanceOf(lval);
+ return Encode(rhs->instanceOf(lval));
+
FunctionObject *fHasInstance = hasInstance->as<FunctionObject>();
if (!fHasInstance)
return engine->throwTypeError();
- ScopedValue result(scope, fHasInstance->call(&rval, &lval, 1));
+ return Encode(fHasInstance->call(&rval, &lval, 1));
+}
+
+QV4::ReturnedValue Runtime::Instanceof::call(ExecutionEngine *engine, const Value &lval, const Value &rval)
+{
+ Scope scope(engine);
+ ScopedValue result(scope, doInstanceof(engine, lval, rval));
return scope.hasException() ? Encode::undefined() : Encode(result->toBoolean());
}
QV4::ReturnedValue Runtime::As::call(ExecutionEngine *engine, const Value &lval, const Value &rval)
{
Scope scope(engine);
- ScopedValue result(scope, Runtime::Instanceof::call(engine, lval, rval));
+ ScopedValue result(scope, doInstanceof(engine, lval, rval));
- if (scope.hasException())
+ if (scope.hasException()) {
+ // "foo instanceof valueType" must not throw an exception.
+ // So this can only be an object type.
engine->catchException();
- else if (result->toBoolean())
+ return Encode::null();
+ }
+
+ if (result->toBoolean())
return lval.asReturnedValue();
+ else if (result->isBoolean())
+ return Encode::null();
- return Encode::null();
+ // Try to convert the value type
+ if (Scoped<QQmlTypeWrapper> typeWrapper(scope, rval); typeWrapper) {
+ const QMetaType metaType = typeWrapper->d()->type().typeId();
+ const QVariant result = engine->toVariant(lval, metaType);
+ if (result.metaType() == metaType)
+ return engine->metaTypeToJS(metaType, result.constData());
+ }
+
+ return Encode::undefined();
}
QV4::ReturnedValue Runtime::In::call(ExecutionEngine *engine, const Value &left, const Value &right)
diff --git a/src/qml/qml/qqmltypewrapper.cpp b/src/qml/qml/qqmltypewrapper.cpp
index 5a3127104c..5c0806e57a 100644
--- a/src/qml/qml/qqmltypewrapper.cpp
+++ b/src/qml/qml/qqmltypewrapper.cpp
@@ -357,19 +357,11 @@ bool QQmlTypeWrapper::virtualIsEqualTo(Managed *a, Managed *b)
return false;
}
-ReturnedValue QQmlTypeWrapper::virtualInstanceOf(const Object *typeObject, const Value &var)
+static ReturnedValue instanceOfQObject(const QV4::QQmlTypeWrapper *typeWrapper, const QObjectWrapper *objectWrapper)
{
- Q_ASSERT(typeObject->as<QV4::QQmlTypeWrapper>());
- const QV4::QQmlTypeWrapper *typeWrapper = static_cast<const QV4::QQmlTypeWrapper *>(typeObject);
-
- // can only compare a QObject* against a QML type
- const QObjectWrapper *wrapper = var.as<QObjectWrapper>();
- if (!wrapper)
- return QV4::Encode(false);
-
- QV4::ExecutionEngine *engine = typeObject->internalClass()->engine;
+ QV4::ExecutionEngine *engine = typeWrapper->internalClass()->engine;
// in case the wrapper outlived the QObject*
- const QObject *wrapperObject = wrapper->object();
+ const QObject *wrapperObject = objectWrapper->object();
if (!wrapperObject)
return engine->throwTypeError();
@@ -399,6 +391,29 @@ ReturnedValue QQmlTypeWrapper::virtualInstanceOf(const Object *typeObject, const
return QV4::Encode(QQmlMetaObject::canConvert(theirType, myQmlType));
}
+ReturnedValue QQmlTypeWrapper::virtualInstanceOf(const Object *typeObject, const Value &var)
+{
+ Q_ASSERT(typeObject->as<QV4::QQmlTypeWrapper>());
+ const QV4::QQmlTypeWrapper *typeWrapper = static_cast<const QV4::QQmlTypeWrapper *>(typeObject);
+
+ if (const QObjectWrapper *objectWrapper = var.as<QObjectWrapper>())
+ return instanceOfQObject(typeWrapper, objectWrapper);
+
+ if (const QMetaObject *valueTypeMetaObject
+ = QQmlMetaType::metaObjectForValueType(typeWrapper->d()->type())) {
+ if (const QQmlValueTypeWrapper *valueWrapper = var.as<QQmlValueTypeWrapper>()) {
+ return QV4::Encode(QQmlMetaObject::canConvert(valueWrapper->metaObject(),
+ valueTypeMetaObject));
+ }
+
+ // We want "foo as valuetype" to return undefined if it doesn't match.
+ return Encode::undefined();
+ }
+
+ // If the target type is an object type we want null.
+ return Encode(false);
+}
+
ReturnedValue QQmlTypeWrapper::virtualResolveLookupGetter(const Object *object, ExecutionEngine *engine, Lookup *lookup)
{
// Keep this code in sync with ::virtualGet
diff --git a/src/qml/qml/qqmlvaluetypewrapper_p.h b/src/qml/qml/qqmlvaluetypewrapper_p.h
index 0c4cbb7dd3..7a1fa02279 100644
--- a/src/qml/qml/qqmlvaluetypewrapper_p.h
+++ b/src/qml/qml/qqmlvaluetypewrapper_p.h
@@ -113,6 +113,7 @@ public:
QMetaType type() const;
bool write(QObject *target, int propertyIndex) const;
bool readReferenceValue() const { return d()->readReference(); }
+ const QMetaObject *metaObject() const { return d()->metaObject(); }
QQmlPropertyData dataForPropertyKey(PropertyKey id) const;
diff --git a/src/qmlcompiler/qqmljscodegenerator.cpp b/src/qmlcompiler/qqmljscodegenerator.cpp
index 41c174d0c8..a4f15054ec 100644
--- a/src/qmlcompiler/qqmljscodegenerator.cpp
+++ b/src/qmlcompiler/qqmljscodegenerator.cpp
@@ -87,6 +87,13 @@ QString QQmlJSCodeGenerator::metaObject(const QQmlJSScope::ConstPtr &objectType)
return QString();
}
+QString QQmlJSCodeGenerator::metaType(const QQmlJSScope::ConstPtr &type)
+{
+ return m_typeResolver->equals(m_typeResolver->genericType(type), type)
+ ? metaTypeFromType(type)
+ : metaTypeFromName(type);
+}
+
QQmlJSAotFunction QQmlJSCodeGenerator::run(
const Function *function, const InstructionAnnotations *annotations,
QQmlJS::DiagnosticMessage *error)
@@ -948,6 +955,59 @@ void QQmlJSCodeGenerator::rejectIfNonQObjectOut(const QString &error)
}
}
+bool QQmlJSCodeGenerator::generateContentPointerCheck(
+ const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
+ const QString &variable, const QString &errorMessage)
+{
+ const QQmlJSScope::ConstPtr scope = required;
+ const QQmlJSScope::ConstPtr input = m_typeResolver->containedType(actual);
+ if (QQmlJSUtils::searchBaseAndExtensionTypes(input,
+ [&](const QQmlJSScope::ConstPtr &base) {
+ return m_typeResolver->equals(base, scope);
+ })) {
+ return false;
+ }
+
+ if (!m_typeResolver->canHold(input, scope)) {
+ reject(u"lookup of members of %1 in %2"_s.arg(
+ scope->internalName(), input->internalName()));
+ }
+ if (!m_typeResolver->equals(actual.storedType(),
+ m_typeResolver->varType())) {
+ reject(u"retrieving metatype from %1"_s.arg(actual.descriptiveName()));
+ }
+
+ // Since we have verified the type in qqmljstypepropagator.cpp we now know
+ // that we can only have either undefined or the actual type here. Therefore,
+ // it's enough to check the QVariant for isValid().
+
+ m_body += u"if (!"_s + variable + u".isValid()) {\n "_s;
+ generateSetInstructionPointer();
+ m_body += u" aotContext->engine->throwError(QJSValue::TypeError, "_s;
+ m_body += u"QLatin1String(\"%1\"));\n"_s.arg(errorMessage);
+ m_body += u" return "_s + errorReturnValue() + u";\n"_s;
+ m_body += u"}\n"_s;
+ return true;
+}
+
+QString QQmlJSCodeGenerator::resolveValueTypeContentPointer(
+ const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
+ const QString &variable, const QString &errorMessage)
+{
+ if (generateContentPointerCheck(required, actual, variable, errorMessage))
+ return variable + u".data()"_s;
+ return contentPointer(actual, variable);
+}
+
+QString QQmlJSCodeGenerator::resolveQObjectPointer(
+ const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
+ const QString &variable, const QString &errorMessage)
+{
+ if (generateContentPointerCheck(required, actual, variable, errorMessage))
+ return u"*static_cast<QObject *const *>("_s + variable + u".constData())"_s;
+ return variable;
+}
+
void QQmlJSCodeGenerator::generate_GetLookup(int index)
{
INJECT_TRACE_INFO(generate_GetLookup);
@@ -981,8 +1041,8 @@ void QQmlJSCodeGenerator::generate_GetLookup(int index)
? QString::number(m_state.accumulatorIn().importNamespace())
: u"QQmlPrivate::AOTCompiledContext::InvalidStringId"_s;
const auto accumulatorIn = m_state.accumulatorIn();
- const bool isReferenceType = (accumulatorIn.storedType()->accessSemantics()
- == QQmlJSScope::AccessSemantics::Reference);
+ const QQmlJSScope::ConstPtr scope = m_state.accumulatorOut().scopeType();
+ const bool isReferenceType = scope->isReferenceType();
switch (m_state.accumulatorOut().variant()) {
case QQmlJSRegisterContent::ObjectAttached: {
@@ -1016,12 +1076,18 @@ void QQmlJSCodeGenerator::generate_GetLookup(int index)
Q_ASSERT(m_state.accumulatorOut().isProperty());
- if (isReferenceType) {
+ if (m_typeResolver->registerIsStoredIn(accumulatorIn, m_typeResolver->jsValueType())) {
+ reject(u"lookup in QJSValue"_s);
+ } else if (isReferenceType) {
+ const QString inputPointer = resolveQObjectPointer(
+ scope, accumulatorIn, m_state.accumulatorVariableIn,
+ u"Cannot read property '%1' of undefined"_s.arg(
+ m_jsUnitGenerator->lookupName(index)));
const QString lookup = u"aotContext->getObjectLookup("_s + indexString
- + u", "_s + m_state.accumulatorVariableIn + u", "_s
+ + u", "_s + inputPointer + u", "_s
+ contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut) + u')';
const QString initialization = u"aotContext->initGetObjectLookup("_s
- + indexString + u", "_s + m_state.accumulatorVariableIn
+ + indexString + u", "_s + inputPointer
+ u", "_s + contentType(m_state.accumulatorOut(), m_state.accumulatorVariableOut)
+ u')';
const QString preparation = getLookupPreparation(
@@ -1052,18 +1118,20 @@ void QQmlJSCodeGenerator::generate_GetLookup(int index)
m_state.accumulatorOut(),
m_state.accumulatorVariableIn + u".length()"_s)
+ u";\n"_s;
- } else if (m_typeResolver->registerIsStoredIn(accumulatorIn, m_typeResolver->jsValueType())) {
- reject(u"lookup in QJSValue"_s);
} else if (m_typeResolver->canUseValueTypes()) {
+
+ const QString inputContentPointer = resolveValueTypeContentPointer(
+ scope, accumulatorIn, m_state.accumulatorVariableIn,
+ u"Cannot read property '%1' of undefined"_s.arg(
+ m_jsUnitGenerator->lookupName(index)));
+
const QString lookup = u"aotContext->getValueLookup("_s + indexString
- + u", "_s + contentPointer(m_state.accumulatorIn(),
- m_state.accumulatorVariableIn)
- + u", "_s + contentPointer(m_state.accumulatorOut(),
- m_state.accumulatorVariableOut)
+ + u", "_s + inputContentPointer
+ + u", "_s + contentPointer(m_state.accumulatorOut(), m_state.accumulatorVariableOut)
+ u')';
const QString initialization = u"aotContext->initGetValueLookup("_s
+ indexString + u", "_s
- + metaObject(m_state.accumulatorOut().scopeType()) + u", "_s
+ + metaObject(scope) + u", "_s
+ contentType(m_state.accumulatorOut(), m_state.accumulatorVariableOut) + u')';
const QString preparation = getLookupPreparation(
m_state.accumulatorOut(), m_state.accumulatorVariableOut, index);
@@ -1146,12 +1214,16 @@ void QQmlJSCodeGenerator::generate_SetLookup(int index, int baseReg)
argType = variableInType;
}
- switch (callBase.storedType()->accessSemantics()) {
+ switch (property.scopeType()->accessSemantics()) {
case QQmlJSScope::AccessSemantics::Reference: {
+ const QString basePointer = resolveQObjectPointer(
+ property.scopeType(), registerType(baseReg), object,
+ u"TypeError: Value is undefined and could not be converted to an object"_s);
+
const QString lookup = u"aotContext->setObjectLookup("_s + indexString
- + u", "_s + object + u", "_s + variableIn + u')';
+ + u", "_s + basePointer + u", "_s + variableIn + u')';
const QString initialization = u"aotContext->initSetObjectLookup("_s
- + indexString + u", "_s + object + u", "_s + argType + u')';
+ + indexString + u", "_s + basePointer + u", "_s + argType + u')';
generateLookup(lookup, initialization, preparation);
break;
}
@@ -1189,12 +1261,16 @@ void QQmlJSCodeGenerator::generate_SetLookup(int index, int baseReg)
const QQmlJSRegisterContent property = specific.storedIn(
m_typeResolver->genericType(specific.storedType()));
+ const QString baseContentPointer = resolveValueTypeContentPointer(
+ property.scopeType(), registerType(baseReg), object,
+ u"TypeError: Value is undefined and could not be converted to an object"_s);
+
const QString lookup = u"aotContext->setValueLookup("_s + indexString
- + u", "_s + contentPointer(registerType(baseReg), object)
+ + u", "_s + baseContentPointer
+ u", "_s + variableIn + u')';
const QString initialization = u"aotContext->initSetValueLookup("_s
+ indexString + u", "_s + metaObject(property.scopeType())
- + u", "_s + contentType(registerType(baseReg), object) + u')';
+ + u", "_s + argType + u')';
generateLookup(lookup, initialization, preparation);
if (m_typeResolver->canUseValueTypes())
@@ -2338,22 +2414,51 @@ void QQmlJSCodeGenerator::generate_As(int lhs)
INJECT_TRACE_INFO(generate_As);
const QString input = registerVariable(lhs);
- const QQmlJSScope::ConstPtr contained
- = m_typeResolver->containedType(m_state.readRegister(lhs));
+ const QQmlJSRegisterContent inputContent = m_state.readRegister(lhs);
+ const QQmlJSScope::ConstPtr contained = m_typeResolver->containedType(inputContent);
+ const QQmlJSRegisterContent outputContent = m_state.accumulatorOut();
- m_body += m_state.accumulatorVariableOut + u" = "_s;
- if (m_typeResolver->equals(
- m_state.accumulatorIn().storedType(), m_typeResolver->metaObjectType())
- && contained->isComposite()) {
- m_body += conversion(
- m_typeResolver->genericType(contained), m_state.accumulatorOut().storedType(),
- m_state.accumulatorVariableIn + u"->cast("_s + input + u')');
- } else {
- m_body += conversion(
- m_typeResolver->genericType(contained), m_state.accumulatorOut().storedType(),
- u'(' + metaObject(contained) + u")->cast("_s + input + u')');
+ if (contained->isReferenceType()) {
+ m_body += m_state.accumulatorVariableOut + u" = "_s;
+ if (m_typeResolver->equals(
+ m_state.accumulatorIn().storedType(), m_typeResolver->metaObjectType())
+ && contained->isComposite()) {
+ m_body += conversion(
+ m_typeResolver->genericType(contained), outputContent.storedType(),
+ m_state.accumulatorVariableIn + u"->cast("_s + input + u')');
+ } else {
+ m_body += conversion(
+ m_typeResolver->genericType(contained), outputContent.storedType(),
+ u'(' + metaObject(contained) + u")->cast("_s + input + u')');
+ }
+ m_body += u";\n"_s;
+ return;
+ } else if (m_typeResolver->equals(inputContent.storedType(), m_typeResolver->varType())) {
+ // If the original output is a conversion, we're supposed to check for the contained
+ // type and if it doesn't match, set the result to undefined.
+ const auto originalContent = m_typeResolver->original(outputContent);
+ if (originalContent.isConversion()) {
+ const auto origins = originalContent.conversionOrigins();
+ Q_ASSERT(origins.size() == 2);
+
+ const auto target = m_typeResolver->equals(origins[0], m_typeResolver->voidType())
+ ? origins[1]
+ : origins[0];
+
+ Q_ASSERT(!m_typeResolver->equals(target, m_typeResolver->voidType()));
+
+ m_body += m_state.accumulatorVariableOut + u" = "_s;
+ m_body += input + u".metaType() == "_s + metaType(target)
+ + u" ? " + conversion(inputContent, outputContent, input)
+ + u" : " + conversion(m_typeResolver->globalType(m_typeResolver->voidType()),
+ outputContent, QString());
+ m_body += u";\n"_s;
+ return;
+ }
}
- m_body += u";\n"_s;
+
+ reject(u"unsupported type assertion"_s);
+
}
void QQmlJSCodeGenerator::generate_UNot()
diff --git a/src/qmlcompiler/qqmljscodegenerator_p.h b/src/qmlcompiler/qqmljscodegenerator_p.h
index 4a7e1dc1dc..debda73ada 100644
--- a/src/qmlcompiler/qqmljscodegenerator_p.h
+++ b/src/qmlcompiler/qqmljscodegenerator_p.h
@@ -63,6 +63,7 @@ protected:
};
virtual QString metaObject(const QQmlJSScope::ConstPtr &objectType);
+ virtual QString metaType(const QQmlJSScope::ConstPtr &type);
void generate_Ret() override;
void generate_Debug() override;
@@ -288,6 +289,16 @@ private:
return m_typeResolver->jsGlobalObject()->property(u"console"_s).type();
}
+ QString resolveValueTypeContentPointer(
+ const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
+ const QString &variable, const QString &errorMessage);
+ QString resolveQObjectPointer(
+ const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
+ const QString &variable, const QString &errorMessage);
+ bool generateContentPointerCheck(
+ const QQmlJSScope::ConstPtr &required, const QQmlJSRegisterContent &actual,
+ const QString &variable, const QString &errorMessage);
+
// map from instruction offset to sequential label number
QHash<int, QString> m_labels;
diff --git a/src/qmlcompiler/qqmljstypepropagator.cpp b/src/qmlcompiler/qqmljstypepropagator.cpp
index 83241dbd90..be6fb24cb7 100644
--- a/src/qmlcompiler/qqmljstypepropagator.cpp
+++ b/src/qmlcompiler/qqmljstypepropagator.cpp
@@ -1968,16 +1968,25 @@ void QQmlJSTypePropagator::generate_As(int lhs)
break;
}
- addReadRegister(lhs, m_typeResolver->globalType(contained));
+ QQmlJSRegisterContent output;
- if (m_typeResolver->containedType(input)->accessSemantics()
- != QQmlJSScope::AccessSemantics::Reference
- || contained->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
+ if (contained->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
+ // A referece type cast can result in either the type or null.
+ // Reference tpyes can hold null. We don't need to special case that.
+ output = m_typeResolver->globalType(contained);
+ } else if (!m_typeResolver->canAddressValueTypes()) {
setError(u"invalid cast from %1 to %2. You can only cast object types."_s
- .arg(input.descriptiveName(), m_state.accumulatorIn().descriptiveName()));
+ .arg(input.descriptiveName(), m_state.accumulatorIn().descriptiveName()));
+ return;
} else {
- setAccumulator(m_typeResolver->globalType(contained));
+ // A value type cast can result in either the type or undefined.
+ output = m_typeResolver->merge(
+ m_typeResolver->globalType(contained),
+ m_typeResolver->globalType(m_typeResolver->voidType()));
}
+
+ addReadRegister(lhs, output);
+ setAccumulator(output);
}
void QQmlJSTypePropagator::checkConversion(
diff --git a/src/qmlcompiler/qqmljstyperesolver.cpp b/src/qmlcompiler/qqmljstyperesolver.cpp
index 278e15bcc1..b868495971 100644
--- a/src/qmlcompiler/qqmljstyperesolver.cpp
+++ b/src/qmlcompiler/qqmljstyperesolver.cpp
@@ -365,7 +365,7 @@ QQmlJSRegisterContent QQmlJSTypeResolver::transformed(
Q_UNREACHABLE_RETURN({});
}
-QQmlJSRegisterContent QQmlJSTypeResolver::referenceTypeForName(
+QQmlJSRegisterContent QQmlJSTypeResolver::registerContentForName(
const QString &name, const QQmlJSScope::ConstPtr &scopeType,
bool hasObjectModulePrefix) const
{
@@ -415,7 +415,11 @@ QQmlJSRegisterContent QQmlJSTypeResolver::referenceTypeForName(
QQmlJSRegisterContent::MetaType, type);
case QQmlJSScope::AccessSemantics::Sequence:
case QQmlJSScope::AccessSemantics::Value:
- // This is not actually a type reference. You cannot get the metaobject
+ if (canAddressValueTypes()) {
+ return QQmlJSRegisterContent::create(metaObjectType(), metaObjectType(),
+ QQmlJSRegisterContent::MetaType, type);
+ }
+ // Else this is not actually a type reference. You cannot get the metaobject
// of a value type in QML and sequences don't even have metaobjects.
break;
}
@@ -642,6 +646,49 @@ QQmlJSScope::ConstPtr QQmlJSTypeResolver::merge(const QQmlJSScope::ConstPtr &a,
return varType();
}
+bool QQmlJSTypeResolver::canHold(
+ const QQmlJSScope::ConstPtr &container, const QQmlJSScope::ConstPtr &contained) const
+{
+ if (equals(container, contained)
+ || equals(container, m_varType)
+ || equals(container, m_jsValueType)) {
+ return true;
+ }
+
+ if (equals(container, m_jsPrimitiveType))
+ return isPrimitive(contained);
+
+ if (equals(container, m_variantListType))
+ return contained->accessSemantics() == QQmlJSScope::AccessSemantics::Sequence;
+
+ if (equals(container, m_qObjectListType) || equals(container, m_listPropertyType)) {
+ if (contained->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence)
+ return false;
+ if (QQmlJSScope::ConstPtr value = contained->valueType())
+ return value->isReferenceType();
+ return false;
+ }
+
+ if (QQmlJSUtils::searchBaseAndExtensionTypes(
+ container, [&](const QQmlJSScope::ConstPtr &base) {
+ return equals(base, contained);
+ })) {
+ return true;
+ }
+
+ if (container->isReferenceType()) {
+ if (QQmlJSUtils::searchBaseAndExtensionTypes(
+ contained, [&](const QQmlJSScope::ConstPtr &base) {
+ return equals(base, container);
+ })) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
bool QQmlJSTypeResolver::canHoldUndefined(const QQmlJSRegisterContent &content) const
{
const auto canBeUndefined = [this](const QQmlJSScope::ConstPtr &type) {
@@ -857,7 +904,8 @@ QQmlJSRegisterContent QQmlJSTypeResolver::scopedType(const QQmlJSScope::ConstPtr
}
}
- QQmlJSRegisterContent result = referenceTypeForName(name);
+ QQmlJSRegisterContent result = registerContentForName(name);
+
if (result.isValid())
return result;
@@ -1165,13 +1213,31 @@ QQmlJSRegisterContent QQmlJSTypeResolver::memberType(const QQmlJSRegisterContent
return {};
}
- return referenceTypeForName(
+ return registerContentForName(
name, type.scopeType(),
type.variant() == QQmlJSRegisterContent::ObjectModulePrefix);
}
if (type.isConversion()) {
- const auto result = memberType(type.conversionResult(), name);
- return result.isValid() ? result : memberEnumType(type.scopeType(), name);
+ if (const auto result = memberType(type.conversionResult(), name); result.isValid())
+ return result;
+ if (const auto result = memberEnumType(type.scopeType(), name); result.isValid())
+ return result;
+
+ // If the conversion consists of only undefined and one actual type,
+ // we can produce the members of that one type.
+ // If the value is then actually undefined, the result is an exception.
+
+ auto origins = type.conversionOrigins();
+ const auto begin = origins.begin();
+ const auto end = std::remove_if(begin, origins.end(),
+ [this](const QQmlJSScope::ConstPtr &origin) {
+ return equals(origin, m_voidType);
+ });
+
+ // If the conversion cannot hold the original type, it loses information.
+ return (end - begin == 1 && canHold(type.conversionResult(), *begin))
+ ? memberType(*begin, name)
+ : QQmlJSRegisterContent();
}
Q_UNREACHABLE_RETURN({});
diff --git a/src/qmlcompiler/qqmljstyperesolver_p.h b/src/qmlcompiler/qqmljstyperesolver_p.h
index bcd4276390..1276a6b0fd 100644
--- a/src/qmlcompiler/qqmljstyperesolver_p.h
+++ b/src/qmlcompiler/qqmljstyperesolver_p.h
@@ -148,6 +148,7 @@ public:
const QQmlJSScopesById &objectsById() const { return m_objectsById; }
bool canCallJSFunctions() const { return m_objectsById.signaturesAreEnforced(); }
bool canUseValueTypes() const { return m_objectsById.valueTypesAreCopied(); }
+ bool canAddressValueTypes() const { return m_objectsById.valueTypesAreAddressable(); }
const QHash<QQmlJS::SourceLocation, QQmlJSMetaSignalHandler> &signalHandlers() const
{
@@ -165,6 +166,9 @@ public:
bool canHoldUndefined(const QQmlJSRegisterContent &content) const;
bool isNumeric(const QQmlJSScope::ConstPtr &type) const;
+ bool canHold(const QQmlJSScope::ConstPtr &container,
+ const QQmlJSScope::ConstPtr &contained) const;
+
protected:
QQmlJSRegisterContent memberType(const QQmlJSScope::ConstPtr &type, const QString &name) const;
@@ -180,7 +184,7 @@ protected:
const QQmlJSRegisterContent &origin,
QQmlJSScope::ConstPtr (QQmlJSTypeResolver::*op)(const QQmlJSScope::ConstPtr &) const) const;
- QQmlJSRegisterContent referenceTypeForName(
+ QQmlJSRegisterContent registerContentForName(
const QString &name,
const QQmlJSScope::ConstPtr &scopeType = QQmlJSScope::ConstPtr(),
bool hasObjectModuelPrefix = false) const;
diff --git a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
index fb7968892c..8b9b2e786e 100644
--- a/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
+++ b/tests/auto/qml/qmlcppcodegen/data/CMakeLists.txt
@@ -155,6 +155,7 @@ set(qml_files
nullComparison.qml
numbersInJsPrimitive.qml
objectInVar.qml
+ objectLookupOnListElement.qml
outOfBounds.qml
overriddenMember.qml
ownProperty.qml
@@ -199,6 +200,7 @@ set(qml_files
unusedAttached.qml
urlString.qml
usingCxxTypesFromFileImports.qml
+ valueTypeCast.qml
valueTypeCopy.qml
valueTypeLists.qml
valueTypeProperty.qml
diff --git a/tests/auto/qml/qmlcppcodegen/data/math.qml b/tests/auto/qml/qmlcppcodegen/data/math.qml
index cc6cd3741a..ad6303e682 100644
--- a/tests/auto/qml/qmlcppcodegen/data/math.qml
+++ b/tests/auto/qml/qmlcppcodegen/data/math.qml
@@ -3,4 +3,5 @@ import QML
QtObject {
property int a: Math.max(5, 7, 9, -111)
property var b: 50 / 22
+ property real c: Math.PI * 2
}
diff --git a/tests/auto/qml/qmlcppcodegen/data/objectLookupOnListElement.qml b/tests/auto/qml/qmlcppcodegen/data/objectLookupOnListElement.qml
new file mode 100644
index 0000000000..4804921b02
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/objectLookupOnListElement.qml
@@ -0,0 +1,34 @@
+pragma Strict
+import QtQuick
+
+Item {
+ id: stack
+
+ property int current: 0
+
+ onCurrentChanged: setZOrders()
+ Component.onCompleted: setZOrders()
+
+ function setZOrders() {
+ for (var i = 0; i < Math.max(stack.children.length, 3); ++i) {
+ stack.children[i].z = (i == current ? 1 : 0)
+ stack.children[i].enabled = (i == current)
+ }
+ }
+
+ function zOrders() : list<int> {
+ return [
+ stack.children[0].z,
+ stack.children[1].z,
+ stack.children[2].z
+ ]
+ }
+
+ function clearChildren() {
+ children.length = 0;
+ }
+
+ Item {}
+ Item {}
+ Item {}
+}
diff --git a/tests/auto/qml/qmlcppcodegen/data/valueTypeCast.qml b/tests/auto/qml/qmlcppcodegen/data/valueTypeCast.qml
new file mode 100644
index 0000000000..d1c9198cbd
--- /dev/null
+++ b/tests/auto/qml/qmlcppcodegen/data/valueTypeCast.qml
@@ -0,0 +1,9 @@
+pragma Strict
+pragma ValueTypeBehavior: Addressable
+import QtQml
+
+QtObject {
+ property rect r: Qt.rect(10, 20, 3, 4)
+ property var v: r
+ property real x: (v as rect).x
+}
diff --git a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
index f19e33017c..131724c5ec 100644
--- a/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
+++ b/tests/auto/qml/qmlcppcodegen/tst_qmlcppcodegen.cpp
@@ -171,6 +171,7 @@ private slots:
void variantMapLookup();
void mathMinMax();
void enumFromBadSingleton();
+ void objectLookupOnListElement();
};
void tst_QmlCppCodegen::initTestCase()
@@ -533,6 +534,7 @@ void tst_QmlCppCodegen::math()
QVERIFY(!object.isNull());
QCOMPARE(object->property("a").toInt(), 9);
QCOMPARE(object->property("b").toDouble(), 50.0 / 22.0);
+ QCOMPARE(object->property("c").toDouble(), std::atan(1.0) * 8.0);
}
void tst_QmlCppCodegen::unknownParameter()
@@ -3222,6 +3224,22 @@ void tst_QmlCppCodegen::valueTypeBehavior()
QVERIFY(!o2.isNull());
QVERIFY(qIsNaN(o2->property("e").toDouble()));
QCOMPARE(o2->property("f").toDouble(), 5.0);
+
+ const QUrl cast(u"qrc:/qt/qml/TestTypes/valueTypeCast.qml"_s);
+ QQmlComponent c3(&engine, cast);
+ QVERIFY2(c3.isReady(), qPrintable(c3.errorString()));
+ QScopedPointer o3(c3.create());
+ QVERIFY(!o3.isNull());
+ QCOMPARE(o3->property("x"), 10);
+
+ QTest::ignoreMessage(
+ QtWarningMsg,
+ qPrintable(cast.toString()
+ + u":8: TypeError: Cannot read property 'x' of undefined"_s));
+ o3->setProperty("v", QLatin1String("not a rect"));
+
+ // If the binding throws an exception, the value doesn't change.
+ QCOMPARE(o3->property("x"), 10);
}
void tst_QmlCppCodegen::invisibleSingleton()
@@ -3380,6 +3398,33 @@ void tst_QmlCppCodegen::enumFromBadSingleton()
QVERIFY(o->objectName().isEmpty());
}
+void tst_QmlCppCodegen::objectLookupOnListElement()
+{
+ QQmlEngine engine;
+
+ const QUrl url(u"qrc:/qt/qml/TestTypes/objectLookupOnListElement.qml"_s);
+ QQmlComponent c1(&engine, url);
+ QVERIFY2(c1.isReady(), qPrintable(c1.errorString()));
+
+ QScopedPointer<QObject> object(c1.create());
+ QVERIFY(!object.isNull());
+
+ QList<int> zOrders;
+ QMetaObject::invokeMethod(object.data(), "zOrders", Q_RETURN_ARG(QList<int>, zOrders));
+ QCOMPARE(zOrders, (QList<int>{1, 0, 0}));
+ object->setProperty("current", 1);
+ QMetaObject::invokeMethod(object.data(), "zOrders", Q_RETURN_ARG(QList<int>, zOrders));
+ QCOMPARE(zOrders, (QList<int>{0, 1, 0}));
+
+ QMetaObject::invokeMethod(object.data(), "clearChildren");
+ QTest::ignoreMessage(
+ QtWarningMsg,
+ qPrintable(url.toString()
+ + u":21: TypeError: Cannot read property 'z' of undefined"_s));
+ QMetaObject::invokeMethod(object.data(), "zOrders", Q_RETURN_ARG(QList<int>, zOrders));
+ QCOMPARE(zOrders, (QList<int>()));
+}
+
QTEST_MAIN(tst_QmlCppCodegen)
#include "tst_qmlcppcodegen.moc"
diff --git a/tests/auto/qml/qqmllanguage/data/asValueType.qml b/tests/auto/qml/qqmllanguage/data/asValueType.qml
new file mode 100644
index 0000000000..5c5cb44ceb
--- /dev/null
+++ b/tests/auto/qml/qqmllanguage/data/asValueType.qml
@@ -0,0 +1,10 @@
+pragma ValueTypeBehavior: Addressable
+import QtQml
+
+QtObject {
+ property var a
+ property rect b: a as rect
+ property bool c: a instanceof rect
+ property bool d: ({x: 10, y: 20}) instanceof point
+ property var e: ({x: 10, y: 20}) as point
+}
diff --git a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
index 87fe0eb3b8..ce7e0a1b1c 100644
--- a/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
+++ b/tests/auto/qml/qqmllanguage/tst_qqmllanguage.cpp
@@ -406,6 +406,7 @@ private slots:
void objectAndGadgetMethodCallsRejectThisObject();
void objectAndGadgetMethodCallsAcceptThisObject();
+ void asValueType();
void longConversion();
@@ -7898,6 +7899,31 @@ void tst_qqmllanguage::longConversion()
}
}
+void tst_qqmllanguage::asValueType()
+{
+ QQmlEngine engine;
+ const QUrl url = testFileUrl("asValueType.qml");
+ QQmlComponent c(&engine, url);
+ QVERIFY2(c.isReady(), qPrintable(c.errorString()));
+
+ QTest::ignoreMessage(QtWarningMsg, qPrintable(url.toString() + ":6:5: Unable to assign [undefined] to QRectF"_L1));
+ QScopedPointer<QObject> o(c.create());
+
+ QCOMPARE(o->property("a"), QVariant());
+ QCOMPARE(o->property("b").value<QRectF>(), QRectF());
+ QVERIFY(!o->property("c").toBool());
+
+ const QRectF rect(1, 2, 3, 4);
+ o->setProperty("a", QVariant(rect));
+ QCOMPARE(o->property("b").value<QRectF>(), rect);
+ QVERIFY(o->property("c").toBool());
+
+ QVERIFY(!o->property("d").toBool());
+ const QPointF point = o->property("e").value<QPointF>();
+ QCOMPARE(point.x(), 10.0);
+ QCOMPARE(point.y(), 20.0);
+}
+
QTEST_MAIN(tst_qqmllanguage)
#include "tst_qqmllanguage.moc"