diff options
author | Andrei Golubev <andrei.golubev@qt.io> | 2021-09-21 10:52:24 +0200 |
---|---|---|
committer | Andrei Golubev <andrei.golubev@qt.io> | 2021-10-22 18:37:20 +0200 |
commit | ee469d528a350f1fbcebc12a3728f7ed6f4a1302 (patch) | |
tree | d76e2f9667ef79812639ebc7892cb2de0ebe82b5 /tools/qmltc | |
parent | 8dab4a81a0bf6cf1765ecf15f6f8c5b9ac2f5100 (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.txt | 2 | ||||
-rw-r--r-- | tools/qmltc/main.cpp | 9 | ||||
-rw-r--r-- | tools/qmltc/qmltccodewriter.cpp | 220 | ||||
-rw-r--r-- | tools/qmltc/qmltccodewriter.h | 5 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.cpp | 95 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.h | 22 | ||||
-rw-r--r-- | tools/qmltc/qmltcoutputir.h | 96 | ||||
-rw-r--r-- | tools/qmltc/qmltcoutputprimitives.h | 49 | ||||
-rw-r--r-- | tools/qmltc/qmltcvisitor.cpp | 76 | ||||
-rw-r--r-- | tools/qmltc/qmltcvisitor.h | 16 |
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> ¶meterList = 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 ¤t, 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 ¤t, 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 |