QmlCompiler: Perform QVariant conversion in JavaScript semantics

In JavaScript we have a number of extra conversions not covered by
qvariant_cast. Therefore, add a method to perform a QVariant conversion
in JavaScript semantics to QJSEngine, and use that in the compiler.

Pick-to: 6.3
Fixes: QTBUG-100883
Change-Id: I8b0bfa0974bc6b339d2601fb373859bc710788c8
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
Reviewed-by: Jarkko Koivikko <jarkko.koivikko@code-q.fi>
This commit is contained in:
Ulf Hermann 2022-02-16 10:47:04 +01:00
parent bd968fa6ff
commit d0f4e0c037
6 changed files with 148 additions and 58 deletions

View File

@ -861,73 +861,87 @@ bool QJSEngine::convertV2(const QJSValue &value, int type, void *ptr)
return convertV2(value, QMetaType(type), ptr);
}
static bool convertString(const QString &string, QMetaType metaType, void *ptr)
{
// have a string based value without engine. Do conversion manually
if (metaType == QMetaType::fromType<bool>()) {
*reinterpret_cast<bool*>(ptr) = string.length() != 0;
return true;
}
if (metaType == QMetaType::fromType<QString>()) {
*reinterpret_cast<QString*>(ptr) = string;
return true;
}
if (metaType == QMetaType::fromType<QUrl>()) {
*reinterpret_cast<QUrl *>(ptr) = QUrl(string);
return true;
}
double d = QV4::RuntimeHelpers::stringToNumber(string);
switch (metaType.id()) {
case QMetaType::Int:
*reinterpret_cast<int*>(ptr) = QV4::Value::toInt32(d);
return true;
case QMetaType::UInt:
*reinterpret_cast<uint*>(ptr) = QV4::Value::toUInt32(d);
return true;
case QMetaType::LongLong:
*reinterpret_cast<qlonglong*>(ptr) = QV4::Value::toInteger(d);
return true;
case QMetaType::ULongLong:
*reinterpret_cast<qulonglong*>(ptr) = QV4::Value::toInteger(d);
return true;
case QMetaType::Double:
*reinterpret_cast<double*>(ptr) = d;
return true;
case QMetaType::Float:
*reinterpret_cast<float*>(ptr) = d;
return true;
case QMetaType::Short:
*reinterpret_cast<short*>(ptr) = QV4::Value::toInt32(d);
return true;
case QMetaType::UShort:
*reinterpret_cast<unsigned short*>(ptr) = QV4::Value::toUInt32(d);
return true;
case QMetaType::Char:
*reinterpret_cast<char*>(ptr) = QV4::Value::toInt32(d);
return true;
case QMetaType::UChar:
*reinterpret_cast<unsigned char*>(ptr) = QV4::Value::toUInt32(d);
return true;
case QMetaType::QChar:
*reinterpret_cast<QChar*>(ptr) = QChar(QV4::Value::toUInt32(d));
return true;
case QMetaType::Char16:
*reinterpret_cast<char16_t *>(ptr) = QV4::Value::toUInt32(d);
return true;
default:
return false;
}
}
/*!
\internal
convert \a value to \a type, store the result in \a ptr
*/
bool QJSEngine::convertV2(const QJSValue &value, QMetaType metaType, void *ptr)
{
if (const QString *string = QJSValuePrivate::asQString(&value)) {
// have a string based value without engine. Do conversion manually
if (metaType == QMetaType::fromType<bool>()) {
*reinterpret_cast<bool*>(ptr) = string->length() != 0;
return true;
}
if (metaType == QMetaType::fromType<QString>()) {
*reinterpret_cast<QString*>(ptr) = *string;
return true;
}
if (metaType == QMetaType::fromType<QUrl>()) {
*reinterpret_cast<QUrl *>(ptr) = QUrl(*string);
return true;
}
double d = QV4::RuntimeHelpers::stringToNumber(*string);
switch (metaType.id()) {
case QMetaType::Int:
*reinterpret_cast<int*>(ptr) = QV4::Value::toInt32(d);
return true;
case QMetaType::UInt:
*reinterpret_cast<uint*>(ptr) = QV4::Value::toUInt32(d);
return true;
case QMetaType::LongLong:
*reinterpret_cast<qlonglong*>(ptr) = QV4::Value::toInteger(d);
return true;
case QMetaType::ULongLong:
*reinterpret_cast<qulonglong*>(ptr) = QV4::Value::toInteger(d);
return true;
case QMetaType::Double:
*reinterpret_cast<double*>(ptr) = d;
return true;
case QMetaType::Float:
*reinterpret_cast<float*>(ptr) = d;
return true;
case QMetaType::Short:
*reinterpret_cast<short*>(ptr) = QV4::Value::toInt32(d);
return true;
case QMetaType::UShort:
*reinterpret_cast<unsigned short*>(ptr) = QV4::Value::toUInt32(d);
return true;
case QMetaType::Char:
*reinterpret_cast<char*>(ptr) = QV4::Value::toInt32(d);
return true;
case QMetaType::UChar:
*reinterpret_cast<unsigned char*>(ptr) = QV4::Value::toUInt32(d);
return true;
case QMetaType::QChar:
*reinterpret_cast<QChar*>(ptr) = QChar(QV4::Value::toUInt32(d));
return true;
case QMetaType::Char16:
*reinterpret_cast<char16_t *>(ptr) = QV4::Value::toUInt32(d);
return true;
default:
return false;
}
}
if (const QString *string = QJSValuePrivate::asQString(&value))
return convertString(*string, metaType, ptr);
return QV4::ExecutionEngine::metaTypeFromJS(QJSValuePrivate::asReturnedValue(&value), metaType, ptr);
}
bool QJSEngine::convertVariant(const QVariant &value, QMetaType metaType, void *ptr)
{
if (value.metaType() == QMetaType::fromType<QString>())
return convertString(value.toString(), metaType, ptr);
// TODO: We could probably avoid creating a QV4::Value in many cases, but we'd have to
// duplicate much of metaTypeFromJS and some methods of QV4::Value itself here.
return QV4::ExecutionEngine::metaTypeFromJS(handle()->fromVariant(value), metaType, ptr);
}
/*! \fn template <typename T> QJSValue QJSEngine::toScriptValue(const T &value)
Creates a QJSValue with the given \a value.
@ -944,6 +958,18 @@ bool QJSEngine::convertV2(const QJSValue &value, QMetaType metaType, void *ptr)
\sa toScriptValue()
*/
/*! \fn template <typename T> T QJSEngine::fromVariant(const QVariant &value)
Returns the given \a value converted to the template type \c{T}.
This works with any type \c{T} that has a \c{QMetaType}. The
conversion is done in JavaScript semantics. Those differ from
qvariant_cast's semantics. There are a number of implicit
conversions between JavaScript-equivalent types that are not
performed by qvariant_cast by default.
\sa fromScriptValue() qvariant_cast()
*/
/*!
Throws a run-time error (exception) with the given \a message.

View File

@ -113,6 +113,33 @@ public:
return qjsvalue_cast<T>(value);
}
template <typename T>
inline T fromVariant(const QVariant &value)
{
if constexpr (std::is_same_v<T, QVariant>)
return value;
const QMetaType targetType = QMetaType::fromType<T>();
if (value.metaType() == targetType)
return *reinterpret_cast<const T *>(value.constData());
if constexpr (std::is_same_v<T,std::remove_const_t<std::remove_pointer_t<T>> const *>) {
using nonConstT = std::remove_const_t<std::remove_pointer_t<T>> *;
const QMetaType nonConstTargetType = QMetaType::fromType<nonConstT>();
if (value.metaType() == nonConstTargetType)
return *reinterpret_cast<const nonConstT *>(value.constData());
}
{
T t{};
if (convertVariant(value, QMetaType::fromType<T>(), &t))
return t;
QMetaType::convert(value.metaType(), value.constData(), targetType, &t);
return t;
}
}
void collectGarbage();
enum ObjectOwnership { CppOwnership, JavaScriptOwnership };
@ -157,6 +184,7 @@ private:
static bool convertManaged(const QJSManagedValue &value, QMetaType type, void *ptr);
static bool convertV2(const QJSValue &value, int type, void *ptr);
static bool convertV2(const QJSValue &value, QMetaType metaType, void *ptr);
bool convertVariant(const QVariant &value, QMetaType metaType, void *ptr);
template<typename T>
friend inline T qjsvalue_cast(const QJSValue &);

View File

@ -2626,7 +2626,8 @@ QString QQmlJSCodeGenerator::conversion(const QQmlJSScope::ConstPtr &from,
if (m_typeResolver->equals(from, varType)) {
if (m_typeResolver->equals(to, m_typeResolver->listPropertyType()))
return u"QQmlListReference("_qs + variable + u", aotContext->qmlEngine())"_qs;
return u"qvariant_cast<"_qs + castTargetName(to) + u">("_qs + variable + u')';
return u"aotContext->engine->fromVariant<"_qs + castTargetName(to) + u">("_qs
+ variable + u')';
}
if (m_typeResolver->equals(to, varType))

View File

@ -91,6 +91,7 @@ set(qml_files
noscope.qml
notEqualsInt.qml
nullAccess.qml
objectInVar.qml
outOfBounds.qml
overriddenMember.qml
ownProperty.qml

View File

@ -0,0 +1,15 @@
pragma Strict
import QtQml
QtObject {
id: self
property var thing: self
function doThing() : bool {
if (self.thing)
return true;
else
return false;
}
}

View File

@ -120,6 +120,7 @@ private slots:
void infinities();
void blockComments();
void functionLookup();
void objectInVar();
};
void tst_QmlCppCodegen::simpleBinding()
@ -1804,6 +1805,24 @@ void tst_QmlCppCodegen::functionLookup()
QCOMPARE(result.toString(), QStringLiteral("a99"));
}
void tst_QmlCppCodegen::objectInVar()
{
QQmlEngine engine;
QQmlComponent c(&engine, QUrl(u"qrc:/TestTypes/objectInVar.qml"_qs));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QObject> o(c.create());
QVERIFY(o);
QCOMPARE(qvariant_cast<QObject*>(o->property("thing")), o.data());
bool result = false;
QVERIFY(QMetaObject::invokeMethod(o.data(), "doThing", Q_RETURN_ARG(bool, result)));
QVERIFY(result);
o->setProperty("thing", QVariant::fromValue<std::nullptr_t>(nullptr));
QVERIFY(QMetaObject::invokeMethod(o.data(), "doThing", Q_RETURN_ARG(bool, result)));
QVERIFY(!result);
}
void tst_QmlCppCodegen::runInterpreted()
{
if (qEnvironmentVariableIsSet("QV4_FORCE_INTERPRETER"))