QmlCompiler: Allow any conversion possible via QJSPrimitiveValue

All of those are legal in ECMAScript, and so we need to support them in
script bindings. As we have stricter rules for literal bindings, add an
extra method there to check for what QQmlPropertyValidator does.

Pick-to: 6.4
Fixes: QTBUG-105252
Task-number: QTBUG-105188
Change-Id: I0621b2c3aa196414f669873e93670557284a8bca
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Ulf Hermann 2022-07-29 16:19:21 +02:00
parent ed3da31fd0
commit b184d02648
7 changed files with 69 additions and 33 deletions

View File

@ -11,6 +11,34 @@ QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
// This makes no sense, but we want to warn about things QQmlPropertyResolver complains about.
static bool canConvertForLiteralBinding(
QQmlJSTypeResolver *resolver, const QQmlJSScope::ConstPtr &from,
const QQmlJSScope::ConstPtr &to) {
if (resolver->equals(from, to))
return true;
if (!resolver->canConvertFromTo(from, to))
return false;
const bool fromIsString = resolver->equals(from, resolver->stringType());
if (resolver->equals(to, resolver->stringType())
|| resolver->equals(to, resolver->stringListType())
|| resolver->equals(to, resolver->byteArrayType())
|| resolver->equals(to, resolver->urlType())) {
return fromIsString;
}
if (resolver->isNumeric(to))
return resolver->isNumeric(from);
if (resolver->equals(to, resolver->boolType()))
return resolver->equals(from, resolver->boolType());
return true;
}
void QQmlJSLiteralBindingCheck::run(QQmlJSImportVisitor *visitor, QQmlJSTypeResolver *resolver)
{
QQmlJSLogger *logger = visitor->logger();
@ -34,19 +62,14 @@ void QQmlJSLiteralBindingCheck::run(QQmlJSImportVisitor *visitor, QQmlJSTypeReso
continue;
}
if (!resolver->canConvertFromTo(binding.literalType(resolver), property.type())) {
logger->log(u"Cannot assign binding of type %1 to %2"_s.arg(
if (!canConvertForLiteralBinding(
resolver, binding.literalType(resolver), property.type())) {
logger->log(u"Cannot assign literal of type %1 to %2"_s.arg(
QQmlJSScope::prettyName(binding.literalTypeName()),
QQmlJSScope::prettyName(property.typeName())),
qmlIncompatibleType, binding.sourceLocation());
continue;
}
if (resolver->equals(property.type(), resolver->stringType())
&& resolver->isNumeric(binding.literalType(resolver))) {
logger->log(u"Cannot assign a numeric constant to a string property"_s,
qmlIncompatibleType, binding.sourceLocation());
}
}
}
}

View File

@ -977,6 +977,10 @@ bool QQmlJSTypeResolver::canPrimitivelyConvertFromTo(
return true;
}
// We can convert anything that fits into QJSPrimitiveValue
if (canConvertFromTo(from, m_jsPrimitiveType) && canConvertFromTo(m_jsPrimitiveType, to))
return true;
return false;
}

View File

@ -117,6 +117,7 @@ set(qml_files
nonNotifyable.qml
noscope.qml
notEqualsInt.qml
notNotString.qml
nullAccess.qml
objectInVar.qml
outOfBounds.qml

View File

@ -0,0 +1,7 @@
pragma Strict
import QML
QtObject {
id: self
property bool notNotString: !!self.objectName
}

View File

@ -134,6 +134,7 @@ private slots:
void trivialSignalHandler();
void stringToByteArray();
void listPropertyAsModel();
void notNotString();
};
void tst_QmlCppCodegen::simpleBinding()
@ -2465,6 +2466,18 @@ void tst_QmlCppCodegen::listPropertyAsModel()
QCOMPARE(children.count(), 5);
}
void tst_QmlCppCodegen::notNotString()
{
QQmlEngine engine;
QQmlComponent c(&engine, QUrl(u"qrc:/qt/qml/TestTypes/notNotString.qml"_s));
QVERIFY2(c.isReady(), qPrintable(c.errorString()));
QScopedPointer<QObject> o(c.create());
QCOMPARE(o->property("notNotString").value<bool>(), false);
o->setObjectName(u"a"_s);
QCOMPARE(o->property("notNotString").value<bool>(), true);
}
void tst_QmlCppCodegen::runInterpreted()
{
#ifdef Q_OS_ANDROID

View File

@ -569,24 +569,20 @@ void TestQmllint::dirtyQmlCode_data()
QTest::newRow("bad template literal (simple)")
<< QStringLiteral("badTemplateStringSimple.qml")
<< Result { { Message {
QStringLiteral("Cannot assign binding of type string to int") } } };
QTest::newRow("bad template literal (substitution)")
<< QStringLiteral("badTemplateStringSubstitution.qml")
<< Result { { Message {
QStringLiteral("Cannot assign binding of type QString to int") } } };
QStringLiteral("Cannot assign literal of type string to int") } } };
QTest::newRow("bad constant number to string")
<< QStringLiteral("numberToStringProperty.qml")
<< Result { { Message { QStringLiteral(
"Cannot assign a numeric constant to a string property") } } };
"Cannot assign literal of type double to QString") } } };
QTest::newRow("bad unary minus to string")
<< QStringLiteral("unaryMinusToStringProperty.qml")
<< Result { { Message { QStringLiteral(
"Cannot assign a numeric constant to a string property") } } };
"Cannot assign literal of type double to QString") } } };
QTest::newRow("bad tranlsation binding (qsTr)") << QStringLiteral("bad_qsTr.qml") << Result {};
QTest::newRow("bad string binding (QT_TR_NOOP)")
<< QStringLiteral("bad_QT_TR_NOOP.qml")
<< Result { { Message {
QStringLiteral("Cannot assign binding of type string to int") } } };
QStringLiteral("Cannot assign literal of type string to int") } } };
QTest::newRow("BadScriptBindingOnGroup")
<< QStringLiteral("badScriptBinding.group.qml")
<< Result { { Message {
@ -686,7 +682,7 @@ void TestQmllint::dirtyQmlCode_data()
QTest::newRow("badAttachedPropertyTypeString")
<< QStringLiteral("badAttachedPropertyTypeString.qml")
<< Result { { Message {
QStringLiteral("Cannot assign binding of type string to int") } } };
QStringLiteral("Cannot assign literal of type string to int") } } };
QTest::newRow("badAttachedPropertyTypeQtObject")
<< QStringLiteral("badAttachedPropertyTypeQtObject.qml")
<< Result { { Message { QStringLiteral(
@ -795,18 +791,10 @@ expression: \${expr} \${expr} \\\${expr} \\\${expr}`)",
QTest::newRow("WithStatement") << QStringLiteral("WithStatement.qml")
<< Result { { Message { QStringLiteral(
"with statements are strongly discouraged") } } };
QTest::newRow("BindingTypeMismatch")
<< QStringLiteral("bindingTypeMismatch.qml")
<< Result { { Message {
QStringLiteral("Cannot assign binding of type QString to int") } } };
QTest::newRow("BindingTypeMismatchFunction")
<< QStringLiteral("bindingTypeMismatchFunction.qml")
<< Result { { Message {
QStringLiteral("Cannot assign binding of type QString to int") } } };
QTest::newRow("BadLiteralBinding")
<< QStringLiteral("badLiteralBinding.qml")
<< Result { { Message {
QStringLiteral("Cannot assign binding of type string to int") } } };
QStringLiteral("Cannot assign literal of type string to int") } } };
QTest::newRow("BadLiteralBindingDate")
<< QStringLiteral("badLiteralBindingDate.qml")
<< Result { { Message {
@ -945,11 +933,7 @@ expression: \${expr} \${expr} \\\${expr} \\\${expr}`)",
{ Message { QStringLiteral("Ready") } } } };
QTest::newRow("nullBinding") << QStringLiteral("nullBinding.qml")
<< Result{ { Message{ QStringLiteral(
"Cannot assign binding of type null to double") } } };
QTest::newRow("nullBindingFunction")
<< QStringLiteral("nullBindingFunction.qml")
<< Result{ { Message{
QStringLiteral("Cannot assign binding of type null to double") } } };
"Cannot assign literal of type null to double") } } };
QTest::newRow("missingRequiredAlias")
<< QStringLiteral("missingRequiredAlias.qml")
<< Result { { Message {
@ -1166,6 +1150,10 @@ void TestQmllint::cleanQmlCode_data()
QTest::newRow("v4SequenceMethods") << QStringLiteral("v4SequenceMethods.qml");
QTest::newRow("stringToByteArray") << QStringLiteral("stringToByteArray.qml");
QTest::newRow("jsLibrary") << QStringLiteral("jsLibrary.qml");
QTest::newRow("nullBindingFunction") << QStringLiteral("nullBindingFunction.qml");
QTest::newRow("BindingTypeMismatchFunction") << QStringLiteral("bindingTypeMismatchFunction.qml");
QTest::newRow("BindingTypeMismatch") << QStringLiteral("bindingTypeMismatch.qml");
QTest::newRow("template literal (substitution)") << QStringLiteral("templateStringSubstitution.qml");
}
void TestQmllint::cleanQmlCode()
@ -1748,9 +1736,9 @@ void TestQmllint::quickPlugin()
u"Cannot specify top, bottom, and verticalCenter anchors at the same time."_s },
Message{
u"Baseline anchor cannot be used in conjunction with top, bottom, or verticalCenter anchors."_s },
Message { u"Cannot assign binding of type null to QQuickAnchorLine"_s, 5,
Message { u"Cannot assign literal of type null to QQuickAnchorLine"_s, 5,
35 },
Message { u"Cannot assign binding of type null to QQuickAnchorLine"_s, 6,
Message { u"Cannot assign literal of type null to QQuickAnchorLine"_s, 6,
33 } } });
runTest("pluginQuick_anchorsUndefined.qml", Result::clean());
runTest("pluginQuick_layoutChildren.qml",