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 QtQml
|
||||||
import QtQuick // for matrix4x4, vectorNd, rect, etc.
|
import QtQuick // for matrix4x4, vectorNd, rect, etc.
|
||||||
QtObject {
|
QtObject {
|
||||||
property bool boolP
|
property bool boolP: true
|
||||||
property double doubleP
|
property double doubleP: 0.5
|
||||||
property int intP
|
property int intP: 42
|
||||||
property list<QtObject> listQtObjP // always list of QML objects
|
property list<QtObject> listQtObjP // always list of QML objects
|
||||||
property real realP
|
property real realP: 2.32
|
||||||
property string stringP
|
property string stringP: "hello, world"
|
||||||
property url urlP
|
property url urlP: "https://www.qt.io/"
|
||||||
property var varP
|
property var varP: 42.42
|
||||||
|
|
||||||
property color colorP
|
property color colorP: "blue"
|
||||||
property date dateP
|
property date dateP
|
||||||
property font fontP
|
property font fontP
|
||||||
property matrix4x4 matrix4x4P
|
property matrix4x4 matrix4x4P
|
||||||
|
@ -29,4 +29,8 @@ QtObject {
|
||||||
// extra:
|
// extra:
|
||||||
property Timer timerP
|
property Timer timerP
|
||||||
property list<Component> listNumP
|
property list<Component> listNumP
|
||||||
|
|
||||||
|
// special:
|
||||||
|
property QtObject nullObjP: null
|
||||||
|
property var nullVarP: null
|
||||||
}
|
}
|
||||||
|
|
|
@ -107,7 +107,8 @@ void tst_qmltc::helloWorld()
|
||||||
{
|
{
|
||||||
QQmlEngine e;
|
QQmlEngine e;
|
||||||
PREPEND_NAMESPACE(HelloWorld) created(&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()
|
void tst_qmltc::qtQuickIncludes()
|
||||||
|
@ -219,6 +220,10 @@ void tst_qmltc::properties()
|
||||||
QCOMPARE(propertyMetaType("urlP"), QMetaType::fromType<QUrl>());
|
QCOMPARE(propertyMetaType("urlP"), QMetaType::fromType<QUrl>());
|
||||||
QCOMPARE(propertyMetaType("varP"), QMetaType::fromType<QVariant>());
|
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("colorP"), QMetaType::fromType<QColor>());
|
||||||
QCOMPARE(propertyMetaType("dateP"), QMetaType::fromType<QDateTime>());
|
QCOMPARE(propertyMetaType("dateP"), QMetaType::fromType<QDateTime>());
|
||||||
QCOMPARE(propertyMetaType("fontP"), QMetaType::fromType<QFont>());
|
QCOMPARE(propertyMetaType("fontP"), QMetaType::fromType<QFont>());
|
||||||
|
@ -241,6 +246,26 @@ void tst_qmltc::properties()
|
||||||
// extra:
|
// extra:
|
||||||
QCOMPARE(propertyMetaType("timerP"), QMetaType::fromType<QQmlTimer *>());
|
QCOMPARE(propertyMetaType("timerP"), QMetaType::fromType<QQmlTimer *>());
|
||||||
QCOMPARE(propertyMetaType("listNumP"), QMetaType::fromType<QQmlListProperty<QQmlComponent>>());
|
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()
|
void tst_qmltc::id()
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
|
|
||||||
void setupLogger(QQmlJSLogger &logger) // prepare logger to work with compiler
|
void setupLogger(QQmlJSLogger &logger) // prepare logger to work with compiler
|
||||||
{
|
{
|
||||||
|
// TODO: support object bindings and change to setCategoryLevel(QtInfoMsg)
|
||||||
logger.setCategoryError(Log_Compiler, true);
|
logger.setCategoryError(Log_Compiler, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -251,6 +251,10 @@ void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr
|
||||||
compileProperty(current, p, type);
|
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)
|
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());
|
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
|
QT_END_NAMESPACE
|
||||||
|
|
|
@ -70,6 +70,27 @@ private:
|
||||||
void compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p,
|
void compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p,
|
||||||
const QQmlJSScope::ConstPtr &owner);
|
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(); }
|
bool hasErrors() const { return m_logger->hasErrors() || m_logger->hasWarnings(); }
|
||||||
void recordError(const QQmlJS::SourceLocation &location, const QString &message,
|
void recordError(const QQmlJS::SourceLocation &location, const QString &message,
|
||||||
QQmlJSLoggerCategory category = Log_Compiler)
|
QQmlJSLoggerCategory category = Log_Compiler)
|
||||||
|
|
|
@ -51,6 +51,18 @@ struct QmltcCodeGenerator
|
||||||
|
|
||||||
QQmlJSScope::ConstPtr documentRoot;
|
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
|
\internal
|
||||||
|
|
||||||
|
@ -61,6 +73,10 @@ struct QmltcCodeGenerator
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] inline decltype(auto) generate_qmlContextSetup(QmltcType ¤t,
|
[[nodiscard]] inline decltype(auto) generate_qmlContextSetup(QmltcType ¤t,
|
||||||
const QQmlJSScope::ConstPtr &type);
|
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)
|
inline decltype(auto)
|
||||||
|
@ -152,6 +168,32 @@ QmltcCodeGenerator::generate_qmlContextSetup(QmltcType ¤t, const QQmlJSSco
|
||||||
return QScopeGuard(generateFinalLines);
|
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
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // QMLTCCOMPILERPIECES_H
|
#endif // QMLTCCOMPILERPIECES_H
|
||||||
|
|
|
@ -190,7 +190,8 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiPublicMember *publicMember)
|
||||||
QQmlJSMetaProperty prop = m_currentScope->ownProperty(name);
|
QQmlJSMetaProperty prop = m_currentScope->ownProperty(name);
|
||||||
const QString nameWithUppercase = name[0].toUpper() + name.sliced(1);
|
const QString nameWithUppercase = name[0].toUpper() + name.sliced(1);
|
||||||
prop.setRead(name);
|
prop.setRead(name);
|
||||||
prop.setWrite(u"set" + nameWithUppercase);
|
if (prop.isWritable())
|
||||||
|
prop.setWrite(u"set" + nameWithUppercase);
|
||||||
prop.setBindable(u"bindable" + nameWithUppercase);
|
prop.setBindable(u"bindable" + nameWithUppercase);
|
||||||
prop.setNotify(name + u"Changed");
|
prop.setNotify(name + u"Changed");
|
||||||
// also check that notify is already a method of m_currentScope
|
// also check that notify is already a method of m_currentScope
|
||||||
|
|
Loading…
Reference in New Issue