diff --git a/tests/auto/qml/qmltc/data/properties.qml b/tests/auto/qml/qmltc/data/properties.qml index 0c9684af3c..77ab781e5d 100644 --- a/tests/auto/qml/qmltc/data/properties.qml +++ b/tests/auto/qml/qmltc/data/properties.qml @@ -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 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 listNumP + + // special: + property QtObject nullObjP: null + property var nullVarP: null } diff --git a/tests/auto/qml/qmltc/tst_qmltc.cpp b/tests/auto/qml/qmltc/tst_qmltc.cpp index 5fc6550479..027d08c4a8 100644 --- a/tests/auto/qml/qmltc/tst_qmltc.cpp +++ b/tests/auto/qml/qmltc/tst_qmltc.cpp @@ -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()); QCOMPARE(propertyMetaType("varP"), QMetaType::fromType()); + QCOMPARE(propertyMetaType("nullObjP"), QMetaType::fromType()); + QCOMPARE(propertyMetaType("nullVarP"), QMetaType::fromType()); + + QCOMPARE(propertyMetaType("varP"), QMetaType::fromType()); QCOMPARE(propertyMetaType("colorP"), QMetaType::fromType()); QCOMPARE(propertyMetaType("dateP"), QMetaType::fromType()); QCOMPARE(propertyMetaType("fontP"), QMetaType::fromType()); @@ -241,6 +246,26 @@ void tst_qmltc::properties() // extra: QCOMPARE(propertyMetaType("timerP"), QMetaType::fromType()); QCOMPARE(propertyMetaType("listNumP"), QMetaType::fromType>()); + + // 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() diff --git a/tools/qmltc/main.cpp b/tools/qmltc/main.cpp index 1b48623e2d..7167e03962 100644 --- a/tools/qmltc/main.cpp +++ b/tools/qmltc/main.cpp @@ -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); } diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp index a6671423b7..65029dbd32 100644 --- a/tools/qmltc/qmltccompiler.cpp +++ b/tools/qmltc/qmltccompiler.cpp @@ -251,6 +251,10 @@ void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr compileProperty(current, p, type); } } + + const QMultiHash 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 diff --git a/tools/qmltc/qmltccompiler.h b/tools/qmltc/qmltccompiler.h index c934dfffac..0574b03088 100644 --- a/tools/qmltc/qmltccompiler.h +++ b/tools/qmltc/qmltccompiler.h @@ -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) diff --git a/tools/qmltc/qmltccompilerpieces.h b/tools/qmltc/qmltccompilerpieces.h index 94d14a3abd..bdb732feb5 100644 --- a/tools/qmltc/qmltccompilerpieces.h +++ b/tools/qmltc/qmltccompilerpieces.h @@ -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 diff --git a/tools/qmltc/qmltcvisitor.cpp b/tools/qmltc/qmltcvisitor.cpp index 30b28653c1..7d9062a4db 100644 --- a/tools/qmltc/qmltcvisitor.cpp +++ b/tools/qmltc/qmltcvisitor.cpp @@ -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