aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qmltc
diff options
context:
space:
mode:
authorAndrei Golubev <andrei.golubev@qt.io>2021-09-21 10:52:24 +0200
committerAndrei Golubev <andrei.golubev@qt.io>2021-10-22 18:37:20 +0200
commitee469d528a350f1fbcebc12a3728f7ed6f4a1302 (patch)
treed76e2f9667ef79812639ebc7892cb2de0ebe82b5 /tools/qmltc
parent8dab4a81a0bf6cf1765ecf15f6f8c5b9ac2f5100 (diff)
qmltc: learn to generate minimum amount of code
- Finish output ir and the writer for it - Add simple compiler logic to generate dull class declarations Task-number: QTBUG-84368 Change-Id: I75be0f44b84ad3cfb1d862072d58b3bf87063d31 Reviewed-by: Ulf Hermann <ulf.hermann@qt.io> Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Diffstat (limited to 'tools/qmltc')
-rw-r--r--tools/qmltc/CMakeLists.txt2
-rw-r--r--tools/qmltc/main.cpp9
-rw-r--r--tools/qmltc/qmltccodewriter.cpp220
-rw-r--r--tools/qmltc/qmltccodewriter.h5
-rw-r--r--tools/qmltc/qmltccompiler.cpp95
-rw-r--r--tools/qmltc/qmltccompiler.h22
-rw-r--r--tools/qmltc/qmltcoutputir.h96
-rw-r--r--tools/qmltc/qmltcoutputprimitives.h49
-rw-r--r--tools/qmltc/qmltcvisitor.cpp76
-rw-r--r--tools/qmltc/qmltcvisitor.h16
10 files changed, 574 insertions, 16 deletions
diff --git a/tools/qmltc/CMakeLists.txt b/tools/qmltc/CMakeLists.txt
index e0ce1d8f99..74f0ab5f84 100644
--- a/tools/qmltc/CMakeLists.txt
+++ b/tools/qmltc/CMakeLists.txt
@@ -9,7 +9,7 @@ qt_internal_add_tool(${target_name}
qmltccodewriter.h qmltccodewriter.cpp
qmltcoutputir.h
qmltctyperesolver.h
- qmltcvisitor.h
+ qmltcvisitor.h qmltcvisitor.cpp
qmltccompiler.h qmltccompiler.cpp
DEFINES
QT_NO_CAST_FROM_ASCII
diff --git a/tools/qmltc/main.cpp b/tools/qmltc/main.cpp
index 451a31517a..ddc3e51c84 100644
--- a/tools/qmltc/main.cpp
+++ b/tools/qmltc/main.cpp
@@ -28,6 +28,7 @@
#include "qmltccommandlineutils.h"
#include "qmltccompiler.h"
+#include "qmltcvisitor.h"
#include <QtQml/private/qqmlirbuilder_p.h>
#include <private/qqmljscompiler_p.h>
@@ -43,6 +44,11 @@
#include <cstdio>
+void setupLogger(QQmlJSLogger &logger) // prepare logger to work with compiler
+{
+ logger.setCategoryError(Log_Compiler, true);
+}
+
int main(int argc, char **argv)
{
// Produce reliably the same output for the same input by disabling QHash's
@@ -158,6 +164,7 @@ int main(int argc, char **argv)
QQmlJSImporter importer { importPaths, /* resource file mapper */ nullptr };
QQmlJSLogger logger(url, sourceCode, /* silent */ false);
+ setupLogger(logger);
QmltcVisitor visitor(&importer, &logger, implicitImportDirectory, qmltypesFiles);
// Type resolving is only static here due the inability to resolve parent
// properties dynamically (QTBUG-95530). Indirect type storage is used as
@@ -167,7 +174,7 @@ int main(int argc, char **argv)
QQmlJSTypeResolver::Static, &logger };
typeResolver.init(visitor);
- QmltcCompiler compiler(url, &typeResolver, &logger);
+ 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 7071071a78..d9d29dccd3 100644
--- a/tools/qmltc/qmltccodewriter.cpp
+++ b/tools/qmltc/qmltccodewriter.cpp
@@ -31,6 +31,8 @@
#include <QtCore/qfileinfo.h>
#include <QtCore/qstringbuilder.h>
+#include <utility>
+
QT_BEGIN_NAMESPACE
static QString urlToMacro(const QString &url)
@@ -39,6 +41,59 @@ static QString urlToMacro(const QString &url)
return u"Q_QMLTC_" + fi.baseName().toUpper();
}
+static QString getFunctionCategory(const QmltcMethodBase &method)
+{
+ QString category;
+ switch (method.access) {
+ case QQmlJSMetaMethod::Private:
+ category = u"private"_qs;
+ break;
+ case QQmlJSMetaMethod::Protected:
+ category = u"protected"_qs;
+ break;
+ case QQmlJSMetaMethod::Public:
+ category = u"public"_qs;
+ break;
+ }
+ return category;
+}
+
+static QString getFunctionCategory(const QmltcMethod &method)
+{
+ QString category = getFunctionCategory(static_cast<const QmltcMethodBase &>(method));
+ switch (method.type) {
+ case QQmlJSMetaMethod::Signal:
+ category = u"Q_SIGNALS"_qs;
+ break;
+ case QQmlJSMetaMethod::Slot:
+ category += u" Q_SLOTS"_qs;
+ break;
+ case QQmlJSMetaMethod::Method:
+ break;
+ }
+ return category;
+}
+
+static std::pair<QString, QString> functionSignatures(const QmltcMethodBase &method)
+{
+ const QString name = method.name;
+ const QList<QmltcVariable> &parameterList = method.parameterList;
+
+ QStringList headerParamList;
+ QStringList cppParamList;
+ for (const QmltcVariable &variable : parameterList) {
+ const QString commonPart = variable.cppType + u" " + variable.name;
+ cppParamList << commonPart;
+ headerParamList << commonPart;
+ if (!variable.defaultValue.isEmpty())
+ headerParamList.back() += u" = " + variable.defaultValue;
+ }
+
+ const QString headerSignature = name + u"(" + headerParamList.join(u", "_qs) + u")";
+ const QString cppSignature = name + u"(" + cppParamList.join(u", "_qs) + u")";
+ return { headerSignature, cppSignature };
+}
+
void QmltcCodeWriter::writeGlobalHeader(QmltcOutputWrapper &code, const QString &sourcePath,
const QString &hPath, const QString &cppPath,
const QSet<QString> &requiredCppIncludes)
@@ -120,12 +175,177 @@ static void writeToFile(const QString &path, const QByteArray &data)
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProgram &program)
{
writeGlobalHeader(code, program.url, program.hPath, program.cppPath, program.includes);
+ // TODO: keep the "NOT IMPLEMENTED" as long as we don't actually compile
+ // useful code
code.rawAppendToHeader(u"/* QMLTC: NOT IMPLEMENTED */");
code.rawAppendToCpp(u"/* QMLTC: NOT IMPLEMENTED */");
+
+ // forward declare all the types first
+ for (const QmltcType &type : qAsConst(program.compiledTypes))
+ code.rawAppendToHeader(u"class " + type.cppType + u";");
+ // write all the types and their content
+ for (const QmltcType &type : qAsConst(program.compiledTypes))
+ write(code, type);
writeGlobalFooter(code, program.url);
writeToFile(program.hPath, code.code().header.toUtf8());
writeToFile(program.cppPath, code.code().cpp.toUtf8());
}
+void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type)
+{
+ const auto constructClassString = [&]() {
+ QString str = u"class " + type.cppType;
+ if (!type.baseClasses.isEmpty())
+ str += u" : public " + type.baseClasses.join(u", public "_qs);
+ return str;
+ };
+
+ code.rawAppendToHeader(u""); // blank line
+ code.rawAppendToCpp(u""); // blank line
+
+ code.rawAppendToHeader(constructClassString());
+ code.rawAppendToHeader(u"{");
+ for (const QString &mocLine : qAsConst(type.mocCode))
+ code.rawAppendToHeader(mocLine, 1);
+
+ QmltcOutputWrapper::MemberNameScope typeScope(&code, type.cppType);
+ Q_UNUSED(typeScope);
+ {
+ QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code);
+ Q_UNUSED(headerIndent);
+
+ // special member functions
+ code.rawAppendToHeader(u"protected:", -1);
+ write(code, type.basicCtor);
+ write(code, type.init);
+ write(code, type.finalize);
+ // NB: externalCtor might not be public when the type is QML singleton
+ code.rawAppendToHeader(getFunctionCategory(type.fullCtor) + u":", -1);
+ write(code, type.fullCtor);
+
+ // enums
+ if (!type.enums.isEmpty()) {
+ code.rawAppendToHeader(u""); // blank line
+ code.rawAppendToHeader(u"public:", -1);
+ }
+ for (const auto &enumeration : qAsConst(type.enums))
+ write(code, enumeration);
+
+ // child types
+ if (!type.children.isEmpty())
+ code.rawAppendToHeader(u""); // blank line
+ 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))
+ functionsByCategory[getFunctionCategory(function)].append(&function);
+
+ if (!functionsByCategory.isEmpty())
+ code.rawAppendToHeader(u""); // blank line
+ for (auto it = functionsByCategory.cbegin(); it != functionsByCategory.cend(); ++it) {
+ code.rawAppendToHeader(it.key() + u":", -1);
+ for (const QmltcMethod *function : qAsConst(it.value()))
+ write(code, *function);
+ }
+ }
+
+ if (type.documentRootType) {
+ code.rawAppendToHeader(u""); // blank line
+ code.rawAppendToHeader(u"private:");
+ code.rawAppendToHeader(u"friend class " + *type.documentRootType + u";", 1);
+ }
+
+ if (type.typeCount) {
+ code.rawAppendToHeader(u""); // blank line
+ code.rawAppendToHeader(u"public:");
+ code.rawAppendToHeader(u"constexpr static uint qmltc_typeCount = "
+ + QString::number(*type.typeCount) + u";",
+ 1);
+ }
+
+ code.rawAppendToHeader(u"};");
+}
+
+void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcEnum &enumeration)
+{
+ code.rawAppendToHeader(u"enum " + enumeration.cppType + u" {");
+ for (qsizetype i = 0; i < enumeration.keys.size(); ++i) {
+ QString str;
+ if (enumeration.values.isEmpty()) {
+ str += enumeration.keys.at(i) + u",";
+ } else {
+ str += enumeration.keys.at(i) + u" = " + enumeration.values.at(i) + u",";
+ }
+ code.rawAppendToHeader(str, 1);
+ }
+ code.rawAppendToHeader(u"};");
+}
+
+void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcMethod &method)
+{
+ const auto [hSignature, cppSignature] = functionSignatures(method);
+ // Note: augment return type with preambles in declaration
+ code.rawAppendToHeader(method.returnType + u" " + hSignature + u";");
+
+ // do not generate method implementation if it is a signal
+ const auto methodType = method.type;
+ if (methodType != QQmlJSMetaMethod::Signal) {
+ code.rawAppendToCpp(u""); // blank line
+ code.rawAppendToCpp(method.returnType);
+ code.rawAppendSignatureToCpp(cppSignature);
+ code.rawAppendToCpp(u"{");
+ {
+ QmltcOutputWrapper::CppIndentationScope cppIndent(&code);
+ Q_UNUSED(cppIndent);
+ for (const QString &line : qAsConst(method.body))
+ code.rawAppendToCpp(line);
+ }
+ code.rawAppendToCpp(u"}");
+ }
+}
+
+void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcCtor &ctor)
+{
+ const auto [hSignature, cppSignature] = functionSignatures(ctor);
+ const QString returnTypeWithSpace = ctor.returnType.isEmpty() ? u""_qs : ctor.returnType + u" ";
+
+ code.rawAppendToHeader(returnTypeWithSpace + hSignature + u";");
+
+ code.rawAppendToCpp(u""); // blank line
+ if (!returnTypeWithSpace.isEmpty())
+ code.rawAppendToCpp(returnTypeWithSpace);
+ code.rawAppendSignatureToCpp(cppSignature);
+ if (!ctor.initializerList.isEmpty()) {
+ code.rawAppendToCpp(u":", 1);
+ // double \n to make separate initializer list lines stand out more
+ code.rawAppendToCpp(
+ ctor.initializerList.join(u",\n\n" + u" "_qs.repeated(code.cppIndent + 1)), 1);
+ }
+ code.rawAppendToCpp(u"{");
+ {
+ QmltcOutputWrapper::CppIndentationScope cppIndent(&code);
+ Q_UNUSED(cppIndent);
+ for (const QString &line : qAsConst(ctor.body))
+ code.rawAppendToCpp(line);
+ }
+ code.rawAppendToCpp(u"}");
+}
+
+void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcVariable &var)
+{
+ const QString optionalPart = var.defaultValue.isEmpty() ? u""_qs : u" = " + var.defaultValue;
+ code.rawAppendToHeader(var.cppType + u" " + var.name + optionalPart + u";");
+}
+
QT_END_NAMESPACE
diff --git a/tools/qmltc/qmltccodewriter.h b/tools/qmltc/qmltccodewriter.h
index 6f2dba422f..b34f198c88 100644
--- a/tools/qmltc/qmltccodewriter.h
+++ b/tools/qmltc/qmltccodewriter.h
@@ -43,6 +43,11 @@ struct QmltcCodeWriter
const QSet<QString> &requiredCppIncludes);
static void writeGlobalFooter(QmltcOutputWrapper &code, const QString &sourcePath);
static void write(QmltcOutputWrapper &code, const QmltcProgram &program);
+ static void write(QmltcOutputWrapper &code, const QmltcType &type);
+ static void write(QmltcOutputWrapper &code, const QmltcEnum &enumeration);
+ static void write(QmltcOutputWrapper &code, const QmltcMethod &method);
+ static void write(QmltcOutputWrapper &code, const QmltcCtor &ctor);
+ static void write(QmltcOutputWrapper &code, const QmltcVariable &var);
};
QT_END_NAMESPACE
diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp
index 1c6f2a6f99..c6d463c671 100644
--- a/tools/qmltc/qmltccompiler.cpp
+++ b/tools/qmltc/qmltccompiler.cpp
@@ -32,26 +32,113 @@
QT_BEGIN_NAMESPACE
-QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QQmlJSLogger *logger)
- : m_url(url), m_typeResolver(resolver), m_logger(logger)
+Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg);
+
+QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor,
+ QQmlJSLogger *logger)
+ : m_url(url), m_typeResolver(resolver), m_visitor(visitor), m_logger(logger)
{
- Q_UNUSED(m_url);
Q_UNUSED(m_typeResolver);
- Q_UNUSED(m_logger);
+ Q_ASSERT(!hasErrors());
}
void QmltcCompiler::compile(const QmltcCompilerInfo &info)
{
m_info = info;
+ Q_ASSERT(!m_info.outputCppFile.isEmpty());
+ Q_ASSERT(!m_info.outputHFile.isEmpty());
+ Q_ASSERT(!m_info.resourcePath.isEmpty());
+
+ const QList<QQmlJSScope::ConstPtr> types = m_visitor->qmlScopes();
+ QList<QmltcType> compiledTypes;
+ compiledTypes.reserve(types.size());
+
+ for (const QQmlJSScope::ConstPtr &type : types) {
+ compiledTypes.emplaceBack(); // creates empty type
+ compileType(compiledTypes.back(), type);
+ if (hasErrors())
+ return;
+ }
QmltcProgram program;
program.url = m_url;
program.cppPath = m_info.outputCppFile;
program.hPath = m_info.outputHFile;
+ program.compiledTypes = compiledTypes;
QmltcOutput out;
QmltcOutputWrapper code(out);
QmltcCodeWriter::write(code, program);
}
+void QmltcCompiler::compileType(QmltcType &current, const QQmlJSScope::ConstPtr &type)
+{
+ if (type->isSingleton()) {
+ recordError(type->sourceLocation(), u"Singleton types are not supported"_qs);
+ return;
+ }
+
+ current.cppType = type->internalName();
+ const QString baseClass = type->baseType()->internalName();
+
+ const bool documentRoot = (type == m_visitor->result());
+ const bool baseTypeIsCompiledQml = false; // TODO: support this in QmltcTypeResolver
+
+ current.baseClasses = { baseClass };
+ if (!documentRoot) {
+ current.documentRootType = m_visitor->result()->internalName();
+ } else {
+ current.typeCount = int(m_visitor->qmlScopes().size());
+ }
+
+ // add special member functions
+ current.basicCtor.access = QQmlJSMetaMethod::Protected;
+ current.init.access = QQmlJSMetaMethod::Protected;
+ current.finalize.access = QQmlJSMetaMethod::Protected;
+ current.fullCtor.access = QQmlJSMetaMethod::Public;
+
+ current.basicCtor.name = current.cppType;
+ current.fullCtor.name = current.cppType;
+ current.init.name = u"qmltc_init"_qs;
+ current.init.returnType = u"QQmlRefPointer<QQmlContextData>"_qs;
+ current.finalize.name = u"qmltc_finalize"_qs;
+ current.finalize.returnType = u"void"_qs;
+
+ QmltcVariable engine(u"QQmlEngine*"_qs, u"engine"_qs);
+ QmltcVariable parent(u"QObject*"_qs, u"parent"_qs, u"nullptr"_qs);
+ current.basicCtor.parameterList = { parent };
+ current.fullCtor.parameterList = { engine, parent };
+ QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_qs, u"parentContext"_qs);
+ QmltcVariable finalizeFlag(u"bool"_qs, u"canFinalize"_qs);
+ if (documentRoot) {
+ current.init.parameterList = { engine, ctxtdata, finalizeFlag };
+ current.finalize.parameterList = { engine, finalizeFlag };
+ } else {
+ current.init.parameterList = { engine, ctxtdata };
+ current.finalize.parameterList = { engine };
+ }
+
+ current.fullCtor.initializerList = { current.basicCtor.name + u"(" + parent.name + u")" };
+ if (baseTypeIsCompiledQml) {
+ // call parent's (QML type's) basic ctor from this. that one will take
+ // care about QObject::setParent()
+ current.basicCtor.initializerList = { baseClass + u"(" + parent.name + u")" };
+ } else {
+ // default call to ctor is enough, but QQml_setParent_noEvent() is
+ // needed (note: faster? version of QObject::setParent())
+ current.basicCtor.body << u"QQml_setParent_noEvent(this, " + parent.name + u");";
+ }
+
+ // compilation stub:
+ current.fullCtor.body << u"Q_UNUSED(engine);"_qs;
+ current.init.body << u"Q_UNUSED(engine);"_qs;
+ current.init.body << u"Q_UNUSED(parentContext);"_qs;
+ current.finalize.body << u"Q_UNUSED(engine);"_qs;
+ if (documentRoot) {
+ current.init.body << u"Q_UNUSED(canFinalize);"_qs;
+ current.finalize.body << u"Q_UNUSED(canFinalize);"_qs;
+ }
+ current.init.body << u"return nullptr;"_qs;
+}
+
QT_END_NAMESPACE
diff --git a/tools/qmltc/qmltccompiler.h b/tools/qmltc/qmltccompiler.h
index 2bdd4a9758..bdd56a7579 100644
--- a/tools/qmltc/qmltccompiler.h
+++ b/tools/qmltc/qmltccompiler.h
@@ -31,6 +31,7 @@
#include "qmltctyperesolver.h"
#include "qmltcvisitor.h"
+#include "qmltcoutputir.h"
#include <QtCore/qcommandlineparser.h>
#include <QtCore/qcoreapplication.h>
@@ -50,14 +51,33 @@ struct QmltcCompilerInfo
class QmltcCompiler
{
public:
- QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QQmlJSLogger *logger);
+ QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor,
+ QQmlJSLogger *logger);
void compile(const QmltcCompilerInfo &info);
private:
QString m_url; // QML input file url
QmltcTypeResolver *m_typeResolver = nullptr;
+ QmltcVisitor *m_visitor = nullptr;
QQmlJSLogger *m_logger = nullptr;
QmltcCompilerInfo m_info {}; // miscellaneous input/output information
+
+ void compileType(QmltcType &current, const QQmlJSScope::ConstPtr &type);
+
+ bool hasErrors() const { return m_logger->hasErrors(); } // TODO: count warnings as errors?
+ void recordError(const QQmlJS::SourceLocation &location, const QString &message,
+ QQmlJSLoggerCategory category = Log_Compiler)
+ {
+ // pretty much any compiler error is a critical error (we cannot
+ // generate code - compilation fails)
+ m_logger->logCritical(message, category, location);
+ }
+ void recordError(const QV4::CompiledData::Location &location, const QString &message,
+ QQmlJSLoggerCategory category = Log_Compiler)
+ {
+ recordError(QQmlJS::SourceLocation { 0, 0, location.line, location.column }, message,
+ category);
+ }
};
QT_END_NAMESPACE
diff --git a/tools/qmltc/qmltcoutputir.h b/tools/qmltc/qmltcoutputir.h
index f5fa450529..ced2c48f86 100644
--- a/tools/qmltc/qmltcoutputir.h
+++ b/tools/qmltc/qmltcoutputir.h
@@ -34,14 +34,104 @@
#include <QtCore/qstringlist.h>
#include <QtCore/qset.h>
+#include <private/qqmljsmetatypes_p.h>
+
+#include <optional>
+
QT_BEGIN_NAMESPACE
+// Below are the classes that represent compiled QML types in a string data
+// form. These classes are used to generate C++ code.
+
+// Represents C++ variable
+struct QmltcVariable
+{
+ QString cppType; // C++ type of a variable
+ QString name; // variable name
+ QString defaultValue; // optional initialization value
+
+ QmltcVariable() = default;
+ // special ctor for QList's emplace back
+ QmltcVariable(const QString &t, const QString &n, const QString &v = QString())
+ : cppType(t), name(n), defaultValue(v)
+ {
+ }
+};
+
+// Represents QML -> C++ compiled enumeration type
+struct QmltcEnum
+{
+ QString cppType; // C++ type of an enum
+ QStringList keys; // enumerator keys
+ QStringList values; // enumerator values
+
+ QmltcEnum() = default;
+ QmltcEnum(const QString &t, const QStringList &ks, const QStringList &vs)
+ : cppType(t), keys(ks), values(vs)
+ {
+ }
+};
+
+struct QmltcMethodBase
+{
+ QString returnType; // C++ return type
+ QString name; // C++ function name
+ QList<QmltcVariable> parameterList; // C++ function parameter list
+ QStringList body; // C++ function code
+ QQmlJSMetaMethod::Access access = QQmlJSMetaMethod::Public; // access specifier
+};
+
+// Represents QML -> C++ compiled function
+struct QmltcMethod : QmltcMethodBase
+{
+ QQmlJSMetaMethod::Type type = QQmlJSMetaMethod::Method; // Qt function type
+};
+
+// Represents C++ ctor of a type
+struct QmltcCtor : QmltcMethodBase
+{
+ QStringList initializerList; // C++ ctor's initializer list
+};
+
+// Represents QML -> C++ compiled type
+struct QmltcType
+{
+ QString cppType; // C++ type of the QML type
+ QStringList baseClasses; // C++ type names of base classes
+ QStringList mocCode; // Qt MOC code
+
+ // member types: enumerations and child types
+ QList<QmltcEnum> enums;
+ QList<QmltcType> children; // these are pretty much always empty
+
+ // special member functions:
+ QmltcCtor basicCtor = {}; // does basic contruction
+ QmltcCtor fullCtor = {}; // calls basicCtor, calls init
+ QmltcMethod init = {}; // starts object initialization (context setup), calls finalize
+ QmltcMethod finalize = {}; // finalizes object (bindings, special interface calls, etc.)
+
+ // member functions: methods, signals and slots
+ QList<QmltcMethod> functions;
+ // member variables: properties and just variables
+ QList<QmltcVariable> variables;
+
+ // we want to use certain private/protected functions by document root, so
+ // record it to make it a friend
+ std::optional<QString> documentRootType;
+
+ // QML document root specific:
+ std::optional<int> typeCount = 0; // the number of QML types defined in a document
+};
+
+// Represents whole QML program, compiled to C++
struct QmltcProgram
{
- QString url;
- QString cppPath;
- QString hPath;
+ QString url; // QML file url
+ QString cppPath; // C++ output .cpp path
+ QString hPath; // C++ output .h path
QSet<QString> includes; // non-default C++ include files
+
+ QList<QmltcType> compiledTypes; // all QML types that are compiled to C++
};
QT_END_NAMESPACE
diff --git a/tools/qmltc/qmltcoutputprimitives.h b/tools/qmltc/qmltcoutputprimitives.h
index a59eeb7ffe..37e1e05a02 100644
--- a/tools/qmltc/qmltcoutputprimitives.h
+++ b/tools/qmltc/qmltcoutputprimitives.h
@@ -29,6 +29,7 @@
#ifndef QMLTCOUTPUTPRIMITIVES_H
#define QMLTCOUTPUTPRIMITIVES_H
+#include <QtCore/qstack.h>
#include <QtCore/qstring.h>
#include <QtCore/qstringbuilder.h>
@@ -56,12 +57,45 @@ public:
QmltcOutputWrapper(QmltcOutput &code) : m_code(code) { }
const QmltcOutput &code() const { return m_code; }
+ QStack<QString> memberScopes; // member name scopes e.g. MyClass::MySubclass::
+ int headerIndent = 0; // header indentation level
+ int cppIndent = 0; // cpp indentation level
+
+ // manages current scope of the generated code, which is necessary for
+ // cpp file generation. Example:
+ // class MyClass { MyClass(); }; - in header
+ // MyClass::MyClass() {} - in cpp
+ // MemberNameScope makes sure "MyClass::" is recorded
+ struct MemberNameScope
+ {
+ QmltcOutputWrapper *m_code;
+ MemberNameScope(QmltcOutputWrapper *code, const QString &str) : m_code(code)
+ {
+ m_code->memberScopes.push(str);
+ }
+ ~MemberNameScope() { m_code->memberScopes.pop(); }
+ };
+
+ struct HeaderIndentationScope
+ {
+ QmltcOutputWrapper *m_code;
+ HeaderIndentationScope(QmltcOutputWrapper *code) : m_code(code) { ++m_code->headerIndent; }
+ ~HeaderIndentationScope() { --m_code->headerIndent; }
+ };
+
+ struct CppIndentationScope
+ {
+ QmltcOutputWrapper *m_code;
+ CppIndentationScope(QmltcOutputWrapper *code) : m_code(code) { ++m_code->cppIndent; }
+ ~CppIndentationScope() { --m_code->cppIndent; }
+ };
+
// appends string \a what with extra indentation \a extraIndent to current
// header string
template<typename String>
void rawAppendToHeader(const String &what, int extraIndent = 0)
{
- rawAppend(m_code.header, what, extraIndent);
+ rawAppend(m_code.header, what, headerIndent + extraIndent);
}
// appends string \a what with extra indentation \a extraIndent to current
@@ -69,7 +103,18 @@ public:
template<typename String>
void rawAppendToCpp(const String &what, int extraIndent = 0)
{
- rawAppend(m_code.cpp, what, extraIndent);
+ rawAppend(m_code.cpp, what, cppIndent + extraIndent);
+ }
+
+ // special case of rawAppendToCpp that makes sure that string "foo()"
+ // becomes "MyClass::foo()"
+ template<typename String>
+ void rawAppendSignatureToCpp(const String &what, int extraIndent = 0)
+ {
+ QString signatureScope;
+ for (const auto &scope : memberScopes)
+ signatureScope += scope + u"::";
+ rawAppendToCpp(signatureScope + what, extraIndent);
}
};
diff --git a/tools/qmltc/qmltcvisitor.cpp b/tools/qmltc/qmltcvisitor.cpp
new file mode 100644
index 0000000000..5396f5368f
--- /dev/null
+++ b/tools/qmltc/qmltcvisitor.cpp
@@ -0,0 +1,76 @@
+/****************************************************************************
+**
+** 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$
+**
+****************************************************************************/
+
+#include "qmltcvisitor.h"
+
+#include <QtCore/qfileinfo.h>
+
+#include <algorithm>
+
+QT_BEGIN_NAMESPACE
+
+static QString uniqueNameFromPieces(const QStringList &pieces, QHash<QString, int> &repetitions)
+{
+ QString possibleName = pieces.join(u'_');
+ const int count = repetitions[possibleName]++;
+ if (count > 0)
+ possibleName.append(u"_" + QString::number(count));
+ return possibleName;
+}
+
+QmltcVisitor::QmltcVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger,
+ const QString &implicitImportDirectory, const QStringList &qmltypesFiles)
+ : QQmlJSImportVisitor(importer, logger, implicitImportDirectory, qmltypesFiles)
+{
+ m_qmlTypeNames.append(QFileInfo(logger->fileName()).baseName()); // put document root
+}
+
+bool QmltcVisitor::visit(QQmlJS::AST::UiObjectDefinition *object)
+{
+ if (!QQmlJSImportVisitor::visit(object))
+ return false;
+
+ Q_ASSERT(m_currentScope->scopeType() == QQmlJSScope::QMLScope);
+ Q_ASSERT(m_currentScope->internalName().isEmpty());
+ Q_ASSERT(!m_currentScope->baseTypeName().isEmpty());
+
+ if (m_currentScope != m_exportedRootScope) // not document root
+ m_qmlTypeNames.append(m_currentScope->baseTypeName());
+
+ // give C++-relevant internal names to QMLScopes, we can use them later in compiler
+ m_currentScope->setInternalName(uniqueNameFromPieces(m_qmlTypeNames, m_qmlTypeNameCounts));
+
+ return true;
+}
+
+void QmltcVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *)
+{
+ m_qmlTypeNames.removeLast();
+}
+
+QT_END_NAMESPACE
diff --git a/tools/qmltc/qmltcvisitor.h b/tools/qmltc/qmltcvisitor.h
index b8cfd7511e..870e9863d1 100644
--- a/tools/qmltc/qmltcvisitor.h
+++ b/tools/qmltc/qmltcvisitor.h
@@ -31,6 +31,7 @@
#include <QtCore/qstring.h>
#include <QtCore/qstringlist.h>
+#include <QtCore/qlist.h>
#include <QtQml/private/qqmlirbuilder_p.h>
#include <private/qqmljsimportvisitor_p.h>
@@ -43,10 +44,17 @@ class QmltcVisitor : public QQmlJSImportVisitor
public:
QmltcVisitor(QQmlJSImporter *importer, QQmlJSLogger *logger,
const QString &implicitImportDirectory,
- const QStringList &qmltypesFiles = QStringList())
- : QQmlJSImportVisitor(importer, logger, implicitImportDirectory, qmltypesFiles)
- {
- }
+ const QStringList &qmltypesFiles = QStringList());
+
+ bool visit(QQmlJS::AST::UiObjectDefinition *) override;
+ void endVisit(QQmlJS::AST::UiObjectDefinition *) override;
+
+ // NB: overwrite result() method to return ConstPtr
+ QQmlJSScope::ConstPtr result() const { return QQmlJSImportVisitor::result(); }
+
+protected:
+ QStringList m_qmlTypeNames; // names of QML types arranged as a stack
+ QHash<QString, int> m_qmlTypeNameCounts;
};
QT_END_NAMESPACE