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:
parent
a12d59bfdf
commit
147b659a94
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -251,6 +251,10 @@ void QmltcCompiler::compileType(QmltcType ¤t, 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 ¤t, const QQmlJSMetaEnum &e)
|
||||
|
@ -405,4 +409,87 @@ void QmltcCompiler::compileProperty(QmltcType ¤t, const QQmlJSMetaProperty
|
|||
current.properties.emplaceBack(underlyingType, variableName, current.cppType, p.notify());
|
||||
}
|
||||
|
||||
void QmltcCompiler::compileBinding(QmltcType ¤t, 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
|
||||
|
|
|
@ -70,6 +70,27 @@ private:
|
|||
void compileProperty(QmltcType ¤t, 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 ¤t, 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)
|
||||
|
|
|
@ -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 ¤t,
|
||||
const QQmlJSScope::ConstPtr &type);
|
||||
|
||||
inline void generate_assignToProperty(QmltcType ¤t, const QQmlJSScope::ConstPtr &type,
|
||||
const QQmlJSMetaProperty &p, const QString &value,
|
||||
const QString &accessor);
|
||||
};
|
||||
|
||||
inline decltype(auto)
|
||||
|
@ -152,6 +168,32 @@ QmltcCodeGenerator::generate_qmlContextSetup(QmltcType ¤t, const QQmlJSSco
|
|||
return QScopeGuard(generateFinalLines);
|
||||
}
|
||||
|
||||
inline void QmltcCodeGenerator::generate_assignToProperty(QmltcType ¤t,
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue