aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qmltc
diff options
context:
space:
mode:
authorAndrei Golubev <andrei.golubev@qt.io>2021-10-29 11:56:25 +0200
committerAndrei Golubev <andrei.golubev@qt.io>2021-11-17 18:04:41 +0100
commita67eba2513504836348f3e07a47c855ea4be413e (patch)
tree6d87081b1e7d30c4c1e6e0780c573b57504de2ca /tools/qmltc
parentdd153ad17475ba561b02c343dd9f0ce95a41390b (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.txt1
-rw-r--r--tools/qmltc/main.cpp5
-rw-r--r--tools/qmltc/qmltccodewriter.cpp25
-rw-r--r--tools/qmltc/qmltccodewriter.h1
-rw-r--r--tools/qmltc/qmltccompiler.cpp95
-rw-r--r--tools/qmltc/qmltccompiler.h4
-rw-r--r--tools/qmltc/qmltcoutputir.h15
-rw-r--r--tools/qmltc/qmltcpropertyutils.h68
-rw-r--r--tools/qmltc/qmltcvisitor.cpp38
-rw-r--r--tools/qmltc/qmltcvisitor.h2
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 &current, 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 &current, const QQmlJSMetaEnum &e)
@@ -296,4 +318,75 @@ void QmltcCompiler::compileMethod(QmltcType &current, const QQmlJSMetaMethod &m)
current.functions.emplaceBack(compiled);
}
+void QmltcCompiler::compileProperty(QmltcType &current, 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 &current, const QQmlJSScope::ConstPtr &type);
void compileEnum(QmltcType &current, const QQmlJSMetaEnum &e);
void compileMethod(QmltcType &current, const QQmlJSMetaMethod &m);
+ void compileProperty(QmltcType &current, 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