diff options
author | Andrei Golubev <andrei.golubev@qt.io> | 2021-10-29 11:56:25 +0200 |
---|---|---|
committer | Andrei Golubev <andrei.golubev@qt.io> | 2021-11-17 18:04:41 +0100 |
commit | a67eba2513504836348f3e07a47c855ea4be413e (patch) | |
tree | 6d87081b1e7d30c4c1e6e0780c573b57504de2ca /tools/qmltc | |
parent | dd153ad17475ba561b02c343dd9f0ce95a41390b (diff) |
qmltc: Compile QML properties into C++
Update the QQmlJSScope::insertPropertyIdentifier along the way
since it doesn't mark a signal method as a signal
Task-number: QTBUG-84368
Change-Id: I4594520e46e2d85cc7fa82357d9cc1c62c66b732
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Diffstat (limited to 'tools/qmltc')
-rw-r--r-- | tools/qmltc/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tools/qmltc/main.cpp | 5 | ||||
-rw-r--r-- | tools/qmltc/qmltccodewriter.cpp | 25 | ||||
-rw-r--r-- | tools/qmltc/qmltccodewriter.h | 1 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.cpp | 95 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.h | 4 | ||||
-rw-r--r-- | tools/qmltc/qmltcoutputir.h | 15 | ||||
-rw-r--r-- | tools/qmltc/qmltcpropertyutils.h | 68 | ||||
-rw-r--r-- | tools/qmltc/qmltcvisitor.cpp | 38 | ||||
-rw-r--r-- | tools/qmltc/qmltcvisitor.h | 2 |
10 files changed, 243 insertions, 11 deletions
diff --git a/tools/qmltc/CMakeLists.txt b/tools/qmltc/CMakeLists.txt index 64c071c583..415dc0f245 100644 --- a/tools/qmltc/CMakeLists.txt +++ b/tools/qmltc/CMakeLists.txt @@ -12,6 +12,7 @@ qt_internal_add_tool(${target_name} qmltcvisitor.h qmltcvisitor.cpp qmltccompiler.h qmltccompiler.cpp qmltccompilerutils.h + qmltcpropertyutils.h DEFINES QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII diff --git a/tools/qmltc/main.cpp b/tools/qmltc/main.cpp index f6feb9ce2d..f8f4c984fa 100644 --- a/tools/qmltc/main.cpp +++ b/tools/qmltc/main.cpp @@ -182,6 +182,11 @@ int main(int argc, char **argv) if (logger.hasWarnings() || logger.hasErrors()) return EXIT_FAILURE; + if (logger.hasWarnings() || logger.hasErrors()) { + // TODO: how do we print errors/warnings/etc.? + return EXIT_FAILURE; + } + QmltcCompiler compiler(url, &typeResolver, &visitor, &logger); compiler.compile(info); if (logger.hasWarnings() || logger.hasErrors()) diff --git a/tools/qmltc/qmltccodewriter.cpp b/tools/qmltc/qmltccodewriter.cpp index df9d11a6f7..02cb47243b 100644 --- a/tools/qmltc/qmltccodewriter.cpp +++ b/tools/qmltc/qmltccodewriter.cpp @@ -253,14 +253,6 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type) for (const auto &child : qAsConst(type.children)) write(code, child); - // variables (mostly properties) - if (!type.variables.isEmpty()) { - code.rawAppendToHeader(u""); // blank line - code.rawAppendToHeader(u"protected:", -1); // TODO: or private? - } - for (const auto &variable : qAsConst(type.variables)) - write(code, variable); - // functions (special case due to functions/signals/slots, etc.) QHash<QString, QList<const QmltcMethod *>> functionsByCategory; for (const auto &function : qAsConst(type.functions)) @@ -273,6 +265,16 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type) for (const QmltcMethod *function : qAsConst(it.value())) write(code, *function); } + + // variables and properties + if (!type.variables.isEmpty() || !type.properties.isEmpty()) { + code.rawAppendToHeader(u""); // blank line + code.rawAppendToHeader(u"protected:", -1); + } + for (const auto &property : qAsConst(type.properties)) + write(code, property); + for (const auto &variable : qAsConst(type.variables)) + write(code, variable); } if (type.typeCount) { @@ -363,4 +365,11 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcVariable &var) code.rawAppendToHeader(var.cppType + u" " + var.name + optionalPart + u";"); } +void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProperty &prop) +{ + Q_ASSERT(prop.defaultValue.isEmpty()); // we don't support it yet + code.rawAppendToHeader(u"Q_OBJECT_BINDABLE_PROPERTY(%1, %2, %3, &%1::%4)"_qs.arg( + prop.containingClass, prop.cppType, prop.name, prop.signalName)); +} + QT_END_NAMESPACE diff --git a/tools/qmltc/qmltccodewriter.h b/tools/qmltc/qmltccodewriter.h index a6f38da0a9..f577d1a7be 100644 --- a/tools/qmltc/qmltccodewriter.h +++ b/tools/qmltc/qmltccodewriter.h @@ -50,6 +50,7 @@ struct QmltcCodeWriter static void write(QmltcOutputWrapper &code, const QmltcMethod &method); static void write(QmltcOutputWrapper &code, const QmltcCtor &ctor); static void write(QmltcOutputWrapper &code, const QmltcVariable &var); + static void write(QmltcOutputWrapper &code, const QmltcProperty &prop); }; QT_END_NAMESPACE diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp index 7c9c7b1734..e134258eea 100644 --- a/tools/qmltc/qmltccompiler.cpp +++ b/tools/qmltc/qmltccompiler.cpp @@ -30,6 +30,7 @@ #include "qmltcoutputir.h" #include "qmltccodewriter.h" #include "qmltccompilerutils.h" +#include "qmltcpropertyutils.h" #include <QtCore/qloggingcategory.h> @@ -209,10 +210,31 @@ void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr compileEnum(current, it.value()); const auto methods = type->ownMethods(); - const auto properties = type->ownProperties(); + auto properties = type->ownProperties().values(); current.functions.reserve(methods.size() + properties.size() * 3); // sensible default for (const QQmlJSMetaMethod &m : methods) compileMethod(current, m); + + current.variables.reserve(properties.size()); + // Note: index() is the (future) meta property index, so make sure given + // properties are ordered by that index before compiling + std::sort(properties.begin(), properties.end(), + [](const QQmlJSMetaProperty &x, const QQmlJSMetaProperty &y) { + return x.index() < y.index(); + }); + for (const QQmlJSMetaProperty &p : qAsConst(properties)) { + if (p.index() == -1) { + recordError(type->sourceLocation(), + u"Internal error: property '%1' has incomplete information"_qs.arg( + p.propertyName())); + continue; + } + if (p.isAlias()) { + recordError(type->sourceLocation(), u"Property aliases are not supported"_qs); + } else { + compileProperty(current, p, type); + } + } } void QmltcCompiler::compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e) @@ -296,4 +318,75 @@ void QmltcCompiler::compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m) current.functions.emplaceBack(compiled); } +void QmltcCompiler::compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p, + const QQmlJSScope::ConstPtr &owner) +{ + Q_ASSERT(!p.isAlias()); // will be handled separately + Q_ASSERT(p.type()); + + const QString name = p.propertyName(); + const QString variableName = u"m_" + name; + const QString underlyingType = getUnderlyingType(p); + // only check for isList() here as it needs some special arrangements. + // otherwise, getUnderlyingType() handles the specifics of a type in C++ + if (p.isList()) { + const QString storageName = variableName + u"_storage"; + current.variables.emplaceBack(u"QList<" + p.type()->internalName() + u" *>", storageName, + QString()); + current.basicCtor.initializerList.emplaceBack(variableName + u"(" + underlyingType + + u"(this, std::addressof(" + storageName + + u")))"); + } + + // along with property, also add relevant moc code, so that we can use the + // property in Qt/QML contexts + QStringList mocPieces; + mocPieces.reserve(10); + mocPieces << underlyingType << name; + + // 1. add setter and getter + if (p.isWritable()) { + QmltcMethod setter {}; + setter.returnType = u"void"_qs; + setter.name = p.write(); + // QQmlJSAotVariable + setter.parameterList.emplaceBack(wrapInConstRef(underlyingType), name + u"_", u""_qs); + setter.body << variableName + u".setValue(" + name + u"_);"; + current.functions.emplaceBack(setter); + mocPieces << u"WRITE"_qs << setter.name; + } + + QmltcMethod getter {}; + getter.returnType = underlyingType; + getter.name = p.read(); + getter.body << u"return " + variableName + u".value();"; + current.functions.emplaceBack(getter); + mocPieces << u"READ"_qs << getter.name; + + // 2. add bindable + QmltcMethod bindable {}; + bindable.returnType = u"QBindable<" + underlyingType + u">"; + bindable.name = p.bindable(); + bindable.body << u"return QBindable<" + underlyingType + u">(std::addressof(" + variableName + + u"));"; + current.functions.emplaceBack(bindable); + mocPieces << u"BINDABLE"_qs << bindable.name; + + // 3. add/check notify (actually, this is already done inside QmltcVisitor) + + if (owner->isPropertyRequired(name)) + mocPieces << u"REQUIRED"_qs; + + // 4. add moc entry + // e.g. Q_PROPERTY(QString p READ getP WRITE setP BINDABLE bindableP) + current.mocCode << u"Q_PROPERTY(" + mocPieces.join(u" "_qs) + u")"; + + // 5. add extra moc entry if this property is marked default + if (name == owner->defaultPropertyName()) + current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_qs.arg(name); + + // structure: (C++ type name, name, C++ class name, C++ signal name) + current.properties.emplaceBack(underlyingType, variableName, current.cppType, p.notify()); +} + QT_END_NAMESPACE diff --git a/tools/qmltc/qmltccompiler.h b/tools/qmltc/qmltccompiler.h index 94247fbfea..2997c3c7d1 100644 --- a/tools/qmltc/qmltccompiler.h +++ b/tools/qmltc/qmltccompiler.h @@ -66,8 +66,10 @@ private: void compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr &type); void compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e); void compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m); + void compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p, + const QQmlJSScope::ConstPtr &owner); - bool hasErrors() const { return m_logger->hasErrors(); } // TODO: count warnings as errors? + 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/qmltcoutputir.h b/tools/qmltc/qmltcoutputir.h index cddbb8dada..4b66a8de1b 100644 --- a/tools/qmltc/qmltcoutputir.h +++ b/tools/qmltc/qmltcoutputir.h @@ -58,6 +58,18 @@ struct QmltcVariable } }; +struct QmltcProperty : QmltcVariable +{ + QString containingClass; + QString signalName; + + QmltcProperty() = default; + QmltcProperty(const QString t, const QString &n, const QString &c, const QString &s) + : QmltcVariable(t, n), containingClass(c), signalName(s) + { + } +}; + // Represents QML -> C++ compiled enumeration type struct QmltcEnum { @@ -115,8 +127,9 @@ struct QmltcType // member functions: methods, signals and slots QList<QmltcMethod> functions; - // member variables: properties and just variables + // member variables QList<QmltcVariable> variables; + QList<QmltcProperty> properties; // QML document root specific: std::optional<QmltcVariable> typeCount; // the number of QML types defined in a document diff --git a/tools/qmltc/qmltcpropertyutils.h b/tools/qmltc/qmltcpropertyutils.h new file mode 100644 index 0000000000..caf031cfbc --- /dev/null +++ b/tools/qmltc/qmltcpropertyutils.h @@ -0,0 +1,68 @@ +/**************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QMLTCPROPERTYUTILS_H +#define QMLTCPROPERTYUTILS_H + +#include <private/qqmljsmetatypes_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \internal + + Tells whether property \a p is a pointer type. +*/ +inline bool isPointer(const QQmlJSMetaProperty &p) +{ + Q_ASSERT(p.type()); + return p.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Reference; +} + +/*! + \internal + + Returns an underlying C++ type of \a p property. +*/ +inline QString getUnderlyingType(const QQmlJSMetaProperty &p) +{ + QString underlyingType = p.type()->internalName(); + // NB: can be a pointer or a list, can't be both (list automatically assumes + // that it holds pointers though). check isList() first, as list<QtObject> + // would be both a list and a pointer (weird). + if (p.isList()) { + underlyingType = u"QQmlListProperty<" + underlyingType + u">"; + } else if (isPointer(p)) { + underlyingType += u"*"_qs; + } + return underlyingType; +} + +QT_END_NAMESPACE + +#endif // QMLTCPROPERTYUTILS_H diff --git a/tools/qmltc/qmltcvisitor.cpp b/tools/qmltc/qmltcvisitor.cpp index 04208da4a4..30b28653c1 100644 --- a/tools/qmltc/qmltcvisitor.cpp +++ b/tools/qmltc/qmltcvisitor.cpp @@ -179,6 +179,44 @@ void QmltcVisitor::endVisit(QQmlJS::AST::UiObjectBinding *uiob) QQmlJSImportVisitor::endVisit(uiob); } +bool QmltcVisitor::visit(QQmlJS::AST::UiPublicMember *publicMember) +{ + if (!QQmlJSImportVisitor::visit(publicMember)) + return false; + + // augment property: set its write/read/etc. methods + if (publicMember->type == QQmlJS::AST::UiPublicMember::Property) { + const auto name = publicMember->name.toString(); + QQmlJSMetaProperty prop = m_currentScope->ownProperty(name); + const QString nameWithUppercase = name[0].toUpper() + name.sliced(1); + prop.setRead(name); + 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 + { + const auto methods = m_currentScope->ownMethods(prop.notify()); + if (methods.size() != 1) { + const QString errorString = + methods.isEmpty() ? u"no signal"_qs : u"too many signals"_qs; + m_logger->logCritical( + u"internal error: %1 found for property '%2'"_qs.arg(errorString, name), + Log_Compiler, publicMember->identifierToken); + return false; + } else if (methods[0].methodType() != QQmlJSMetaMethod::Signal) { + m_logger->logCritical( + u"internal error: method %1 of property %2 must be a signal"_qs.arg( + prop.notify(), name), + Log_Compiler, publicMember->identifierToken); + return false; + } + } + m_currentScope->addOwnProperty(prop); + } + + return true; +} + void QmltcVisitor::endVisit(QQmlJS::AST::UiProgram *program) { QQmlJSImportVisitor::endVisit(program); diff --git a/tools/qmltc/qmltcvisitor.h b/tools/qmltc/qmltcvisitor.h index de04f6977e..17d5f02ebf 100644 --- a/tools/qmltc/qmltcvisitor.h +++ b/tools/qmltc/qmltcvisitor.h @@ -54,6 +54,8 @@ public: bool visit(QQmlJS::AST::UiObjectBinding *) override; void endVisit(QQmlJS::AST::UiObjectBinding *) override; + bool visit(QQmlJS::AST::UiPublicMember *) override; + void endVisit(QQmlJS::AST::UiProgram *) override; // NB: overwrite result() method to return ConstPtr |