qmltc: Support literal bindings

Ignore type conversions for now. Expect C++ to handle them
magically for us. The simple binding type dependent conversions
that are performed already should suffice

Task-number: QTBUG-84368
Change-Id: I62bd36ccf6c60fd62c2a50b6e011c637c5bcfbce
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
This commit is contained in:
Andrei Golubev 2021-11-15 17:10:31 +01:00
parent a12d59bfdf
commit 147b659a94
7 changed files with 191 additions and 10 deletions

View File

@ -1,16 +1,16 @@
import QtQml
import QtQuick // for matrix4x4, vectorNd, rect, etc.
QtObject {
property bool boolP
property double doubleP
property int intP
property bool boolP: true
property double doubleP: 0.5
property int intP: 42
property list<QtObject> listQtObjP // always list of QML objects
property real realP
property string stringP
property url urlP
property var varP
property real realP: 2.32
property string stringP: "hello, world"
property url urlP: "https://www.qt.io/"
property var varP: 42.42
property color colorP
property color colorP: "blue"
property date dateP
property font fontP
property matrix4x4 matrix4x4P
@ -29,4 +29,8 @@ QtObject {
// extra:
property Timer timerP
property list<Component> listNumP
// special:
property QtObject nullObjP: null
property var nullVarP: null
}

View File

@ -107,7 +107,8 @@ void tst_qmltc::helloWorld()
{
QQmlEngine e;
PREPEND_NAMESPACE(HelloWorld) created(&e);
QSKIP("Nothing is supported yet.");
QCOMPARE(created.hello(), u"Hello, World");
QSKIP("Not everything is supported yet.");
}
void tst_qmltc::qtQuickIncludes()
@ -219,6 +220,10 @@ void tst_qmltc::properties()
QCOMPARE(propertyMetaType("urlP"), QMetaType::fromType<QUrl>());
QCOMPARE(propertyMetaType("varP"), QMetaType::fromType<QVariant>());
QCOMPARE(propertyMetaType("nullObjP"), QMetaType::fromType<QObject *>());
QCOMPARE(propertyMetaType("nullVarP"), QMetaType::fromType<QVariant>());
QCOMPARE(propertyMetaType("varP"), QMetaType::fromType<QVariant>());
QCOMPARE(propertyMetaType("colorP"), QMetaType::fromType<QColor>());
QCOMPARE(propertyMetaType("dateP"), QMetaType::fromType<QDateTime>());
QCOMPARE(propertyMetaType("fontP"), QMetaType::fromType<QFont>());
@ -241,6 +246,26 @@ void tst_qmltc::properties()
// extra:
QCOMPARE(propertyMetaType("timerP"), QMetaType::fromType<QQmlTimer *>());
QCOMPARE(propertyMetaType("listNumP"), QMetaType::fromType<QQmlListProperty<QQmlComponent>>());
// now, test property values:
QCOMPARE(created.boolP(), true);
QCOMPARE(created.doubleP(), 0.5);
QCOMPARE(created.intP(), 42);
QCOMPARE(created.realP(), 2.32);
QCOMPARE(created.stringP(), u"hello, world"_qs);
QCOMPARE(created.urlP(), u"https://www.qt.io/"_qs);
QCOMPARE(created.varP(), 42.42);
QCOMPARE(created.boolP(), true);
QCOMPARE(created.boolP(), true);
QCOMPARE(created.colorP(), QColor(u"blue"_qs));
QCOMPARE(created.readonlyStringP(), u"foobar"_qs);
// nulls:
QCOMPARE(created.nullObjP(), nullptr);
QCOMPARE(created.nullVarP(), QVariant::fromValue(nullptr));
}
void tst_qmltc::id()

View File

@ -46,6 +46,7 @@
void setupLogger(QQmlJSLogger &logger) // prepare logger to work with compiler
{
// TODO: support object bindings and change to setCategoryLevel(QtInfoMsg)
logger.setCategoryError(Log_Compiler, true);
}

View File

@ -251,6 +251,10 @@ void QmltcCompiler::compileType(QmltcType &current, const QQmlJSScope::ConstPtr
compileProperty(current, p, type);
}
}
const QMultiHash<QString, QQmlJSMetaPropertyBinding> allBindings = type->ownPropertyBindings();
for (auto it = allBindings.begin(); it != allBindings.end(); ++it)
compileBinding(current, it.value(), type, BindingAccessorData { type });
}
void QmltcCompiler::compileEnum(QmltcType &current, const QQmlJSMetaEnum &e)
@ -405,4 +409,87 @@ void QmltcCompiler::compileProperty(QmltcType &current, const QQmlJSMetaProperty
current.properties.emplaceBack(underlyingType, variableName, current.cppType, p.notify());
}
void QmltcCompiler::compileBinding(QmltcType &current, const QQmlJSMetaPropertyBinding &binding,
const QQmlJSScope::ConstPtr &type,
const BindingAccessorData &accessor)
{
Q_UNUSED(current);
Q_UNUSED(accessor);
QString propertyName = binding.propertyName();
if (propertyName.isEmpty()) {
// if empty, try default property
for (QQmlJSScope::ConstPtr t = type->baseType(); t && propertyName.isEmpty();
t = t->baseType()) {
propertyName = t->defaultPropertyName();
}
}
Q_ASSERT(!propertyName.isEmpty());
QQmlJSMetaProperty p = type->property(propertyName);
Q_ASSERT(p.isValid());
QQmlJSScope::ConstPtr propertyType = p.type();
Q_ASSERT(propertyType);
// NB: we assume here that QmltcVisitor took care of type mismatches and
// other errors, so the compiler just needs to add correct instructions,
// without if-checking every type
QmltcCodeGenerator generator {
QQmlJSScope::ConstPtr()
}; // NB: we don't need document root here
switch (binding.bindingType()) {
case QQmlJSMetaPropertyBinding::BoolLiteral: {
const bool value = binding.literalValue().toBool();
generator.generate_assignToProperty(current, type, p, value ? u"true"_qs : u"false"_qs,
accessor.name);
break;
}
case QQmlJSMetaPropertyBinding::NumberLiteral: {
const QString value = QString::number(binding.literalValue().toDouble());
generator.generate_assignToProperty(current, type, p, value, accessor.name);
break;
}
case QQmlJSMetaPropertyBinding::StringLiteral: {
const QString value = binding.literalValue().toString();
generator.generate_assignToProperty(
current, type, p, QmltcCodeGenerator::toStringLiteral(value), accessor.name);
break;
}
case QQmlJSMetaPropertyBinding::Null: {
// poor check: null bindings are only supported for var and objects
if (propertyType != m_typeResolver->varType()
&& propertyType->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
// TODO: this should really be done before the compiler here
recordError(binding.sourceLocation(),
u"Cannot assign null to incompatible property"_qs);
} else if (propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
generator.generate_assignToProperty(current, type, p, u"nullptr"_qs, accessor.name);
} else {
generator.generate_assignToProperty(current, type, p,
u"QVariant::fromValue(nullptr)"_qs, accessor.name);
}
break;
}
// case QQmlJSMetaPropertyBinding::RegExpLiteral:
// case QQmlJSMetaPropertyBinding::Translation:
// case QQmlJSMetaPropertyBinding::TranslationById:
// case QQmlJSMetaPropertyBinding::Script:
// case QQmlJSMetaPropertyBinding::Object:
// case QQmlJSMetaPropertyBinding::Interceptor:
// case QQmlJSMetaPropertyBinding::ValueSource:
// case QQmlJSMetaPropertyBinding::AttachedProperty:
// case QQmlJSMetaPropertyBinding::GroupProperty:
case QQmlJSMetaPropertyBinding::Invalid: {
Q_UNREACHABLE(); // this is truly something that must not happen here
break;
}
default: {
m_logger->logWarning(u"Binding type is not supported (yet)"_qs, Log_Compiler,
binding.sourceLocation());
break;
}
}
}
QT_END_NAMESPACE

View File

@ -70,6 +70,27 @@ private:
void compileProperty(QmltcType &current, const QQmlJSMetaProperty &p,
const QQmlJSScope::ConstPtr &owner);
/*!
\internal
Helper structure that holds the information necessary for most bindings,
such as accessor name, which is used to reference the properties. For
example:
> (accessor.name)->(propertyName) results in "this->myProperty"
This data is also used in more advanced scenarios by attached and
grouped properties
*/
struct BindingAccessorData
{
QQmlJSScope::ConstPtr scope; // usually the current type
QString name = u"this"_qs;
QString propertyName = QString();
bool isValueType = false;
};
void compileBinding(QmltcType &current, const QQmlJSMetaPropertyBinding &binding,
const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor);
bool hasErrors() const { return m_logger->hasErrors() || m_logger->hasWarnings(); }
void recordError(const QQmlJS::SourceLocation &location, const QString &message,
QQmlJSLoggerCategory category = Log_Compiler)

View File

@ -51,6 +51,18 @@ struct QmltcCodeGenerator
QQmlJSScope::ConstPtr documentRoot;
static QString escapeString(QString s)
{
return s.replace(u"\\"_qs, u"\\\\"_qs)
.replace(u"\""_qs, u"\\\""_qs)
.replace(u"\n"_qs, u"\\n"_qs);
}
static QString toStringLiteral(const QString &s)
{
return u"QStringLiteral(\"" + escapeString(s) + u"\")";
}
/*!
\internal
@ -61,6 +73,10 @@ struct QmltcCodeGenerator
*/
[[nodiscard]] inline decltype(auto) generate_qmlContextSetup(QmltcType &current,
const QQmlJSScope::ConstPtr &type);
inline void generate_assignToProperty(QmltcType &current, const QQmlJSScope::ConstPtr &type,
const QQmlJSMetaProperty &p, const QString &value,
const QString &accessor);
};
inline decltype(auto)
@ -152,6 +168,32 @@ QmltcCodeGenerator::generate_qmlContextSetup(QmltcType &current, const QQmlJSSco
return QScopeGuard(generateFinalLines);
}
inline void QmltcCodeGenerator::generate_assignToProperty(QmltcType &current,
const QQmlJSScope::ConstPtr &type,
const QQmlJSMetaProperty &p,
const QString &value,
const QString &accessor)
{
Q_ASSERT(p.isValid());
Q_ASSERT(!p.isList()); // TODO: check this one
Q_ASSERT(!p.isPrivate());
Q_UNUSED(type);
QStringList code;
code.reserve(6); // always should be enough
if (!p.isWritable()) {
Q_ASSERT(type->hasOwnProperty(p.propertyName())); // otherwise we cannot set it
code << u"%1->m_%2 = %3;"_qs.arg(accessor, p.propertyName(), value);
} else {
const QString setter = p.write();
Q_ASSERT(!setter.isEmpty()); // in practice could be empty, but ignore it for now
code << u"%1->%2(%3);"_qs.arg(accessor, setter, value);
}
current.init.body += code;
}
QT_END_NAMESPACE
#endif // QMLTCCOMPILERPIECES_H

View File

@ -190,7 +190,8 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiPublicMember *publicMember)
QQmlJSMetaProperty prop = m_currentScope->ownProperty(name);
const QString nameWithUppercase = name[0].toUpper() + name.sliced(1);
prop.setRead(name);
prop.setWrite(u"set" + nameWithUppercase);
if (prop.isWritable())
prop.setWrite(u"set" + nameWithUppercase);
prop.setBindable(u"bindable" + nameWithUppercase);
prop.setNotify(name + u"Changed");
// also check that notify is already a method of m_currentScope