// Copyright (C) 2021 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qmltccodewriter.h" #include #include #include #include #include #include QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; static QString urlToMacro(const QString &url) { QFileInfo fi(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"_s; break; case QQmlJSMetaMethod::Protected: category = u"protected"_s; break; case QQmlJSMetaMethod::Public: category = u"public"_s; break; } return category; } static QString getFunctionCategory(const QmltcMethod &method) { QString category = getFunctionCategory(static_cast(method)); switch (method.type) { case QQmlJSMetaMethodType::Signal: category = u"Q_SIGNALS"_s; break; case QQmlJSMetaMethodType::Slot: category += u" Q_SLOTS"_s; break; case QQmlJSMetaMethodType::Method: case QQmlJSMetaMethodType::StaticMethod: break; } return category; } static QString appendSpace(const QString &s) { if (s.isEmpty()) return s; return s + u" "; } static QString prependSpace(const QString &s) { if (s.isEmpty()) return s; return u" " + s; } static std::pair functionSignatures(const QmltcMethodBase &method) { const QString name = method.name; const QList ¶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", "_s) + u")" + prependSpace(method.modifiers.join(u" ")); const QString cppSignature = name + u"(" + cppParamList.join(u", "_s) + u")" + prependSpace(method.modifiers.join(u" ")); return { headerSignature, cppSignature }; } static QString functionReturnType(const QmltcMethod &m) { return appendSpace(m.declarationPrefixes.join(u" "_s)) + m.returnType; } void QmltcCodeWriter::writeGlobalHeader(QmltcOutputWrapper &code, const QString &sourcePath, const QString &hPath, const QString &cppPath, const QString &outNamespace, const QSet &requiredCppIncludes) { Q_UNUSED(cppPath); const QString preamble = u"// This code is auto-generated by the qmltc tool from the file '" + sourcePath + u"'\n// WARNING! All changes made in this file will be lost!\n"; code.rawAppendToHeader(preamble); code.rawAppendToCpp(preamble); code.rawAppendToHeader( u"// NOTE: This generated API is to be considered implementation detail."); code.rawAppendToHeader( u"// It may change from version to version and should not be relied upon."); const QString headerMacro = urlToMacro(sourcePath); code.rawAppendToHeader(u"#ifndef %1_H"_s.arg(headerMacro)); code.rawAppendToHeader(u"#define %1_H"_s.arg(headerMacro)); code.rawAppendToHeader(u"#include "); code.rawAppendToHeader(u"#include "); code.rawAppendToHeader(u"#include "); code.rawAppendToHeader(u"#include "); code.rawAppendToHeader(u"#include "); code.rawAppendToHeader(u"#include "); // used in engine execution code.rawAppendToHeader(u"#include "); // used for attached properties code.rawAppendToHeader(u"#include "); // executeRuntimeFunction(), etc. code.rawAppendToHeader(u"#include "); // QmltcSupportLib code.rawAppendToHeader(u"#include "); // QQmlListProperty // include custom C++ includes required by used types code.rawAppendToHeader(u"// BEGIN(custom_cpp_includes)"); for (const auto &requiredInclude : requiredCppIncludes) code.rawAppendToHeader(u"#include \"" + requiredInclude + u"\""); code.rawAppendToHeader(u"// END(custom_cpp_includes)"); code.rawAppendToCpp(u"#include \"" + hPath + u"\""); // include own .h file code.rawAppendToCpp(u"// qmltc support library:"); code.rawAppendToCpp(u"#include "); // QmltcSupportLib code.rawAppendToCpp(u"#include "); // QmltcSupportLib code.rawAppendToHeader(u"#include "); // QmltcSupportLib code.rawAppendToCpp(u"#include "); // createComponent() code.rawAppendToCpp(u"#include "); // QQmlComponentPrivate::get() code.rawAppendToCpp(u""); code.rawAppendToCpp(u"#include "); // NB: for private properties code.rawAppendToCpp(u"#include "); // for finalize callbacks code.rawAppendToCpp(u"#include "); // QQmlPrivate::qmlExtendedObject() code.rawAppendToCpp(u""); // blank line code.rawAppendToCpp(u"QT_USE_NAMESPACE // avoid issues with QT_NAMESPACE"); code.rawAppendToHeader(u""); // blank line const QStringList namespaces = outNamespace.split(u"::"_s); for (const QString ¤tNamespace : namespaces) { code.rawAppendToHeader(u"namespace %1 {"_s.arg(currentNamespace)); code.rawAppendToCpp(u"namespace %1 {"_s.arg(currentNamespace)); } } void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcPropertyInitializer &propertyInitializer, const QmltcType &wrappedType) { code.rawAppendToHeader(u"class " + propertyInitializer.name + u" {"); { { [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code); code.rawAppendToHeader(u"friend class " + wrappedType.cppType + u";"); } code.rawAppendToHeader(u"public:"_s); [[maybe_unused]] QmltcOutputWrapper::MemberNameScope typeScope(&code, propertyInitializer.name); { [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code); write(code, propertyInitializer.constructor); code.rawAppendToHeader(u""); // blank line for (const auto &propertySetter : propertyInitializer.propertySetters) { write(code, propertySetter); } } code.rawAppendToHeader(u""); // blank line code.rawAppendToHeader(u"private:"_s); { [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code); write(code, propertyInitializer.component); write(code, propertyInitializer.initializedCache); } } code.rawAppendToHeader(u"};"_s); code.rawAppendToHeader(u""); // blank line } void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcRequiredPropertiesBundle &requiredPropertiesBundle) { code.rawAppendToHeader(u"struct " + requiredPropertiesBundle.name + u" {"); { [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code); for (const auto &member : requiredPropertiesBundle.members) { write(code, member); } } code.rawAppendToHeader(u"};"_s); code.rawAppendToHeader(u""); // blank line } void QmltcCodeWriter::writeGlobalFooter(QmltcOutputWrapper &code, const QString &sourcePath, const QString &outNamespace) { const QStringList namespaces = outNamespace.split(u"::"_s); for (auto it = namespaces.crbegin(), end = namespaces.crend(); it != end; it++) { code.rawAppendToCpp(u"} // namespace %1"_s.arg(*it)); code.rawAppendToHeader(u"} // namespace %1"_s.arg(*it)); } code.rawAppendToHeader(u""); // blank line code.rawAppendToHeader(u"#endif // %1_H"_s.arg(urlToMacro(sourcePath))); code.rawAppendToHeader(u""); // blank line } static void writeToFile(const QString &path, const QByteArray &data) { // When not using dependency files, changing a single qml invalidates all // qml files and would force the recompilation of everything. To avoid that, // we check if the data is equal to the existing file, if yes, don't touch // it so the build system will not recompile unnecessary things. // // If the build system use dependency file, we should anyway touch the file // so qmltc is not re-run QFileInfo fi(path); if (fi.exists() && fi.size() == data.size()) { QFile oldFile(path); if (oldFile.open(QIODevice::ReadOnly)) { if (oldFile.readAll() == data) return; } } QFile file(path); if (!file.open(QIODevice::WriteOnly)) qFatal("Could not open file %s", qPrintable(path)); file.write(data); } void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProgram &program) { writeGlobalHeader(code, program.url, program.hPath, program.cppPath, program.outNamespace, program.includes); // url method comes first writeUrl(code, program.urlMethod); // forward declare all the types first for (const QmltcType &type : std::as_const(program.compiledTypes)) code.rawAppendToHeader(u"class " + type.cppType + u";"); // write all the types and their content for (const QmltcType &type : std::as_const(program.compiledTypes)) write(code, type, program.exportMacro); // add typeCount definitions. after all types have been written down (so // they are now complete types as per C++). practically, this only concerns // document root type for (const QmltcType &type : std::as_const(program.compiledTypes)) { if (!type.typeCount) continue; code.rawAppendToHeader(u""); // blank line code.rawAppendToHeader(u"constexpr %1 %2::%3()"_s.arg(type.typeCount->returnType, type.cppType, type.typeCount->name)); code.rawAppendToHeader(u"{"); for (const QString &line : std::as_const(type.typeCount->body)) code.rawAppendToHeader(line, 1); code.rawAppendToHeader(u"}"); } writeGlobalFooter(code, program.url, program.outNamespace); writeToFile(program.hPath, code.code().header.toUtf8()); writeToFile(program.cppPath, code.code().cpp.toUtf8()); } template static void dumpFunctions(QmltcOutputWrapper &code, const QList &functions, Predicate pred) { // functions are _ordered_ by access and kind. ordering is important to // provide consistent output QMap> orderedFunctions; for (const auto &function : functions) { if (pred(function)) orderedFunctions[getFunctionCategory(function)].append(std::addressof(function)); } for (auto it = orderedFunctions.cbegin(); it != orderedFunctions.cend(); ++it) { code.rawAppendToHeader(it.key() + u":", -1); for (const QmltcMethod *function : std::as_const(it.value())) QmltcCodeWriter::write(code, *function); } } void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type, const QString &exportMacro) { const auto constructClassString = [&]() { QString str = u"class "_s; if (!exportMacro.isEmpty()) str.append(exportMacro).append(u" "_s); str.append(type.cppType); QStringList nonEmptyBaseClasses; nonEmptyBaseClasses.reserve(type.baseClasses.size()); std::copy_if(type.baseClasses.cbegin(), type.baseClasses.cend(), std::back_inserter(nonEmptyBaseClasses), [](const QString &entry) { return !entry.isEmpty(); }); if (!nonEmptyBaseClasses.isEmpty()) str += u" : public " + nonEmptyBaseClasses.join(u", public "_s); return str; }; code.rawAppendToHeader(u""); // blank line code.rawAppendToCpp(u""); // blank line code.rawAppendToHeader(constructClassString()); code.rawAppendToHeader(u"{"); for (const QString &mocLine : std::as_const(type.mocCode)) code.rawAppendToHeader(mocLine, 1); QmltcOutputWrapper::MemberNameScope typeScope(&code, type.cppType); Q_UNUSED(typeScope); { QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code); Q_UNUSED(headerIndent); // first, write user-visible code, then everything else. someone might // want to look at the generated code, so let's make an effort when // writing it down code.rawAppendToHeader(u"/* ----------------- */"); code.rawAppendToHeader(u"/* External C++ API */"); code.rawAppendToHeader(u"public:", -1); if (!type.propertyInitializer.name.isEmpty()) write(code, type.propertyInitializer, type); if (type.requiredPropertiesBundle) write(code, *type.requiredPropertiesBundle); // NB: when non-document root, the externalCtor won't be public - but we // really don't care about the output format of such types if (!type.ignoreInit && type.externalCtor.access == QQmlJSMetaMethod::Public) { // TODO: ignoreInit must be eliminated QmltcCodeWriter::write(code, type.externalCtor); if (type.staticCreate) QmltcCodeWriter::write(code, *type.staticCreate); } // dtor if (type.dtor) QmltcCodeWriter::write(code, *type.dtor); // enums for (const auto &enumeration : std::as_const(type.enums)) QmltcCodeWriter::write(code, enumeration); // visible functions const auto isUserVisibleFunction = [](const QmltcMethod &function) { return function.userVisible; }; dumpFunctions(code, type.functions, isUserVisibleFunction); code.rawAppendToHeader(u"/* ----------------- */"); code.rawAppendToHeader(u""); // blank line code.rawAppendToHeader(u"/* Internal functionality (do NOT use it!) */"); // below are the hidden parts of the type // (rest of the) ctors if (type.ignoreInit) { // TODO: this branch should be eliminated Q_ASSERT(type.baselineCtor.access == QQmlJSMetaMethod::Public); code.rawAppendToHeader(u"public:", -1); QmltcCodeWriter::write(code, type.baselineCtor); } else { code.rawAppendToHeader(u"protected:", -1); if (type.externalCtor.access != QQmlJSMetaMethod::Public) { Q_ASSERT(type.externalCtor.access == QQmlJSMetaMethod::Protected); QmltcCodeWriter::write(code, type.externalCtor); } QmltcCodeWriter::write(code, type.baselineCtor); QmltcCodeWriter::write(code, type.init); QmltcCodeWriter::write(code, type.endInit); QmltcCodeWriter::write(code, type.setComplexBindings); QmltcCodeWriter::write(code, type.beginClass); QmltcCodeWriter::write(code, type.completeComponent); QmltcCodeWriter::write(code, type.finalizeComponent); QmltcCodeWriter::write(code, type.handleOnCompleted); } // children for (const auto &child : std::as_const(type.children)) QmltcCodeWriter::write(code, child, exportMacro); // (non-visible) functions dumpFunctions(code, type.functions, std::not_fn(isUserVisibleFunction)); // variables and properties if (!type.variables.isEmpty() || !type.properties.isEmpty()) { code.rawAppendToHeader(u""); // blank line code.rawAppendToHeader(u"protected:", -1); } for (const auto &property : std::as_const(type.properties)) write(code, property); for (const auto &variable : std::as_const(type.variables)) write(code, variable); } code.rawAppendToHeader(u"private:", -1); for (const QString &otherLine : std::as_const(type.otherCode)) code.rawAppendToHeader(otherLine, 1); if (type.typeCount) { // add typeCount declaration, definition is added later code.rawAppendToHeader(u""); // blank line code.rawAppendToHeader(u"protected:"); code.rawAppendToHeader(u"constexpr static %1 %2();"_s.arg(type.typeCount->returnType, type.typeCount->name), 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"};"); code.rawAppendToHeader(enumeration.ownMocLine); } 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.type == QQmlJSMetaMethodType::StaticMethod ? u"static " + functionReturnType(method) : functionReturnType(method)) + u" " + hSignature + u";"); // do not generate method implementation if it is a signal const auto methodType = method.type; if (methodType != QQmlJSMetaMethodType::Signal) { code.rawAppendToCpp(u""_s); // blank line if (method.comments.size() > 0) { code.rawAppendToCpp(u"/*! \\internal"_s); for (const auto &comment : method.comments) code.rawAppendToCpp(comment, 1); code.rawAppendToCpp(u"*/"_s); } code.rawAppendToCpp(method.returnType); code.rawAppendSignatureToCpp(cppSignature); code.rawAppendToCpp(u"{"); { QmltcOutputWrapper::CppIndentationScope cppIndent(&code); Q_UNUSED(cppIndent); for (const QString &line : std::as_const(method.body)) code.rawAppendToCpp(line); } code.rawAppendToCpp(u"}"); } } template static void writeSpecialMethod(QmltcOutputWrapper &code, const QmltcMethodBase &specialMethod, WriteInitialization writeInit) { const auto [hSignature, cppSignature] = functionSignatures(specialMethod); code.rawAppendToHeader(hSignature + u";"); code.rawAppendToCpp(u""); // blank line code.rawAppendSignatureToCpp(cppSignature); writeInit(specialMethod); code.rawAppendToCpp(u"{"); { QmltcOutputWrapper::CppIndentationScope cppIndent(&code); Q_UNUSED(cppIndent); for (const QString &line : std::as_const(specialMethod.body)) code.rawAppendToCpp(line); } code.rawAppendToCpp(u"}"); } void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcCtor &ctor) { const auto writeInitializerList = [&](const QmltcMethodBase &ctorBase) { auto ctor = static_cast(ctorBase); 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" "_s.repeated(code.cppIndent + 1)), 1); } }; writeSpecialMethod(code, ctor, writeInitializerList); } void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcDtor &dtor) { const auto noop = [](const QmltcMethodBase &) {}; writeSpecialMethod(code, dtor, noop); } void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcVariable &var) { const QString optionalPart = var.defaultValue.isEmpty() ? u""_s : u" = " + var.defaultValue; 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 (or at all?) code.rawAppendToHeader(u"Q_OBJECT_BINDABLE_PROPERTY(%1, %2, %3, &%1::%4)"_s.arg( prop.containingClass, prop.cppType, prop.name, prop.signalName)); } void QmltcCodeWriter::writeUrl(QmltcOutputWrapper &code, const QmltcMethod &urlMethod) { // unlike ordinary methods, url function only exists in .cpp Q_ASSERT(!urlMethod.returnType.isEmpty()); const auto [hSignature, _] = functionSignatures(urlMethod); Q_UNUSED(_); // Note: augment return type with preambles in declaration code.rawAppendToCpp(functionReturnType(urlMethod) + u" " + hSignature); code.rawAppendToCpp(u"{"); { QmltcOutputWrapper::CppIndentationScope cppIndent(&code); Q_UNUSED(cppIndent); for (const QString &line : std::as_const(urlMethod.body)) code.rawAppendToCpp(line); } code.rawAppendToCpp(u"}"); } QT_END_NAMESPACE