diff options
Diffstat (limited to 'tools')
31 files changed, 572 insertions, 1354 deletions
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 1e8ec2d46f..3392714335 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -61,7 +61,7 @@ endif() if(QT_FEATURE_thread AND TARGET Qt::QuickTest AND NOT ANDROID AND NOT WASM AND NOT rtems) add_subdirectory(qmltestrunner) endif() -if(QT_FEATURE_private_tests AND QT_FEATURE_thread AND NOT ANDROID AND NOT WASM AND NOT rtems) +if(QT_FEATURE_private_tests AND QT_FEATURE_thread AND NOT ANDROID AND NOT WASM AND NOT IOS AND NOT rtems) add_subdirectory(qmljs) endif() if (QT_FEATURE_private_tests AND NOT CMAKE_CROSSCOMPILING) diff --git a/tools/qml/main.cpp b/tools/qml/main.cpp index 076ca28b22..42a1745fd8 100644 --- a/tools/qml/main.cpp +++ b/tools/qml/main.cpp @@ -61,6 +61,7 @@ #include <qqmlfileselector.h> #include <private/qtqmlglobal_p.h> +#include <private/qqmlimport_p.h> #if QT_CONFIG(qml_animation) #include <private/qabstractanimation_p.h> #endif @@ -113,23 +114,23 @@ static void loadConf(const QString &override, bool quiet) // Terminates app on f QFileInfo fi; fi.setFile(QStandardPaths::locate(QStandardPaths::AppDataLocation, defaultFileName)); if (fi.exists()) { - settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath()); + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); } else { // ### If different built-in configs are needed per-platform, just apply QFileSelector to the qrc conf.qml path fi.setFile(confResourcePath + defaultFileName); - settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath()); + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); builtIn = true; } } else { QFileInfo fi; fi.setFile(confResourcePath + override + QLatin1String(".qml")); if (fi.exists()) { - settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath()); + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); builtIn = true; } else { fi.setFile(QDir(QStandardPaths::locate(QStandardPaths::AppConfigLocation, override, QStandardPaths::LocateDirectory)), customConfFileName); if (fi.exists()) - settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath()); + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); else fi.setFile(override); if (!fi.exists()) { @@ -137,7 +138,7 @@ static void loadConf(const QString &override, bool quiet) // Terminates app on f qPrintable(QDir::toNativeSeparators(fi.absoluteFilePath()))); exit(1); } - settingsUrl = QUrl::fromLocalFile(fi.absoluteFilePath()); + settingsUrl = QQmlImports::urlFromLocalFileOrQrcOrUrl(fi.absoluteFilePath()); } } diff --git a/tools/qmlcachegen/qmlcachegen.cpp b/tools/qmlcachegen/qmlcachegen.cpp index d6869ab05b..7a715d5d13 100644 --- a/tools/qmlcachegen/qmlcachegen.cpp +++ b/tools/qmlcachegen/qmlcachegen.cpp @@ -270,19 +270,15 @@ int main(int argc, char **argv) QQmlJSLogger logger; // Always trigger the qFatal() on "pragma Strict" violations. - logger.setCategoryError(Log_Compiler, true); + logger.setCategoryLevel(Log_Compiler, QtCriticalMsg); + logger.setCategoryIgnored(Log_Compiler, false); // By default, we're completely silent, // as the lcAotCompiler category default is QtFatalMsg - if (lcAotCompiler().isDebugEnabled()) - logger.setCategoryLevel(Log_Compiler, QtDebugMsg); - else if (lcAotCompiler().isInfoEnabled()) - logger.setCategoryLevel(Log_Compiler, QtInfoMsg); - else if (lcAotCompiler().isWarningEnabled()) - logger.setCategoryLevel(Log_Compiler, QtWarningMsg); - else if (lcAotCompiler().isCriticalEnabled()) - logger.setCategoryLevel(Log_Compiler, QtCriticalMsg); - else + const bool loggingEnabled = lcAotCompiler().isDebugEnabled() + || lcAotCompiler().isInfoEnabled() || lcAotCompiler().isWarningEnabled() + || lcAotCompiler().isCriticalEnabled(); + if (!loggingEnabled) logger.setSilent(true); QQmlJSAotCompiler cppCodeGen( @@ -297,9 +293,9 @@ int main(int argc, char **argv) QList<QQmlJS::DiagnosticMessage> warnings = importer.takeGlobalWarnings(); if (!warnings.isEmpty()) { - logger.logWarning(QStringLiteral("Type warnings occurred while compiling file:"), - Log_Import); - logger.processMessages(warnings, QtWarningMsg, Log_Import); + logger.log(QStringLiteral("Type warnings occurred while compiling file:"), + Log_Import, QQmlJS::SourceLocation()); + logger.processMessages(warnings, Log_Import); } } } else if (inputFile.endsWith(QLatin1String(".js")) || inputFile.endsWith(QLatin1String(".mjs"))) { diff --git a/tools/qmlimportscanner/main.cpp b/tools/qmlimportscanner/main.cpp index 605ea03400..24490e1198 100644 --- a/tools/qmlimportscanner/main.cpp +++ b/tools/qmlimportscanner/main.cpp @@ -43,6 +43,7 @@ #include <QtCore/QMetaObject> #include <QtCore/QMetaProperty> #include <QtCore/QVariant> +#include <QtCore/QVariantMap> #include <QtCore/QJsonObject> #include <QtCore/QJsonArray> #include <QtCore/QJsonDocument> diff --git a/tools/qmltc/CMakeLists.txt b/tools/qmltc/CMakeLists.txt index cf35319a3b..7e7941539e 100644 --- a/tools/qmltc/CMakeLists.txt +++ b/tools/qmltc/CMakeLists.txt @@ -8,21 +8,16 @@ qt_internal_add_tool(${target_name} qmltcoutputprimitives.h qmltccodewriter.h qmltccodewriter.cpp qmltcoutputir.h - qmltctyperesolver.h + qmltctyperesolver.h qmltctyperesolver.cpp qmltcvisitor.h qmltcvisitor.cpp qmltccompiler.h qmltccompiler.cpp qmltccompilerpieces.h qmltcpropertyutils.h - prototype/generatedcodeprimitives.h prototype/qml2cppcontext.h - prototype/visitor.cpp prototype/visitor.h prototype/qml2cppdefaultpasses.cpp prototype/qml2cppdefaultpasses.h prototype/codegenerator.cpp prototype/codegenerator.h prototype/codegeneratorutil.cpp prototype/codegeneratorutil.h - prototype/codegeneratorwriter.cpp prototype/codegeneratorwriter.h - prototype/qmlcompiler.h - prototype/typeresolver.cpp prototype/typeresolver.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 c2002235cf..49e218f4fe 100644 --- a/tools/qmltc/main.cpp +++ b/tools/qmltc/main.cpp @@ -28,8 +28,10 @@ #include "qmltccommandlineutils.h" #include "prototype/codegenerator.h" -#include "prototype/visitor.h" -#include "prototype/typeresolver.h" +#include "qmltcvisitor.h" +#include "qmltctyperesolver.h" + +#include "qmltccompiler.h" #include <QtQml/private/qqmlirbuilder_p.h> #include <private/qqmljscompiler_p.h> @@ -46,8 +48,18 @@ void setupLogger(QQmlJSLogger &logger) // prepare logger to work with compiler { - // TODO: support object bindings and change to setCategoryLevel(QtInfoMsg) - logger.setCategoryError(Log_Compiler, true); + const QSet<QQmlJSLoggerCategory> exceptions { + Log_ControlsSanity, // this category is just weird + Log_UnusedImport, // not critical + }; + + for (int i = 0; i <= static_cast<int>(QQmlJSLoggerCategory_Last); ++i) { + const auto c = static_cast<QQmlJSLoggerCategory>(i); + if (exceptions.contains(c)) + continue; + logger.setCategoryLevel(c, QtCriticalMsg); + logger.setCategoryIgnored(c, false); + } } int main(int argc, char **argv) @@ -175,11 +187,11 @@ int main(int argc, char **argv) return EXIT_FAILURE; } - Options options; - options.outputCppFile = parser.value(outputCppOption); - options.outputHFile = parser.value(outputHOption); - options.resourcePath = paths.first(); - options.outNamespace = parser.value(namespaceOption); + QmltcCompilerInfo info; + info.outputCppFile = parser.value(outputCppOption); + info.outputHFile = parser.value(outputHOption); + info.resourcePath = paths.first(); + info.outputNamespace = parser.value(namespaceOption); QQmlJSImporter importer { importPaths, &mapper }; QQmlJSLogger logger; @@ -187,29 +199,27 @@ int main(int argc, char **argv) logger.setCode(sourceCode); setupLogger(logger); - Qmltc::Visitor visitor(&importer, &logger, - QQmlJSImportVisitor::implicitImportDirectory(url, &mapper), qmldirFiles); - Qmltc::TypeResolver typeResolver { &importer }; + QmltcVisitor visitor(&importer, &logger, + QQmlJSImportVisitor::implicitImportDirectory(url, &mapper), qmldirFiles); + QmltcTypeResolver typeResolver { &importer }; typeResolver.init(visitor, document.program); - if (logger.hasWarnings() || logger.hasErrors()) + if (logger.hasErrors()) return EXIT_FAILURE; - CodeGenerator generator(url, &logger, &document, &typeResolver); - generator.generate(options); - -# if 0 // TODO: Currently disabled due to QTBUG-100103, remove this #if guard once the issue has - // been addressed QList<QQmlJS::DiagnosticMessage> warnings = importer.takeGlobalWarnings(); - if (!warnings.isEmpty()) { - logger.logWarning(QStringLiteral("Type warnings occurred while compiling file:"), - Log_Import); - logger.processMessages(warnings, QtWarningMsg, Log_Import); + logger.log(QStringLiteral("Type warnings occurred while compiling file:"), Log_Import, + QQmlJS::SourceLocation()); + logger.processMessages(warnings, Log_Import); + // Log_Import is critical for the compiler + return EXIT_FAILURE; } -# endif - if (logger.hasWarnings() || logger.hasErrors()) + CodeGenerator generator(url, &logger, &document, &typeResolver, &info); + generator.generate(); + + if (logger.hasErrors()) return EXIT_FAILURE; return EXIT_SUCCESS; diff --git a/tools/qmltc/prototype/codegenerator.cpp b/tools/qmltc/prototype/codegenerator.cpp index ff71db989b..cf1bb719ee 100644 --- a/tools/qmltc/prototype/codegenerator.cpp +++ b/tools/qmltc/prototype/codegenerator.cpp @@ -30,7 +30,9 @@ #include "prototype/qml2cppdefaultpasses.h" #include "prototype/qml2cpppropertyutils.h" #include "prototype/codegeneratorutil.h" -#include "prototype/codegeneratorwriter.h" +#include "qmltccodewriter.h" + +#include "qmltccompiler.h" #include <QtCore/qfileinfo.h> #include <QtCore/qhash.h> @@ -45,12 +47,7 @@ #include <utility> #include <numeric> -static constexpr char newLineLatin1[] = -#ifdef Q_OS_WIN32 - "\r\n"; -#else - "\n"; -#endif +QT_BEGIN_NAMESPACE Q_LOGGING_CATEGORY(lcCodeGenerator, "qml.qmltc.compiler", QtWarningMsg); @@ -91,12 +88,12 @@ static QString figureReturnType(const QQmlJSMetaMethod &m) return type; } -static QList<QQmlJSAotVariable> +static QList<QmltcVariable> compileMethodParameters(const QStringList &names, const QList<QSharedPointer<const QQmlJSScope>> &types, bool allowUnnamed = false) { - QList<QQmlJSAotVariable> paramList; + QList<QmltcVariable> paramList; const auto size = names.size(); paramList.reserve(size); for (qsizetype i = 0; i < size; ++i) { @@ -105,8 +102,7 @@ compileMethodParameters(const QStringList &names, Q_ASSERT(allowUnnamed || !name.isEmpty()); // assume verified if (name.isEmpty() && allowUnnamed) name = u"unnamed_" + QString::number(i); - paramList.emplaceBack( - QQmlJSAotVariable { types[i]->augmentedInternalName(), name, QString() }); + paramList.emplaceBack(QmltcVariable { types[i]->augmentedInternalName(), name, QString() }); } return paramList; } @@ -216,13 +212,12 @@ toOrderedSequence(typename QmlIR::PoolList<QmlIR::Binding>::Iterator first, Q_LOGGING_CATEGORY(lcCodeGen, "qml.compiler.CodeGenerator", QtWarningMsg); CodeGenerator::CodeGenerator(const QString &url, QQmlJSLogger *logger, QmlIR::Document *doc, - const Qmltc::TypeResolver *localResolver) - : m_url(url), - m_logger(logger), - m_doc(doc), - m_localTypeResolver(localResolver), - m_qmlSource(doc->code.split(QLatin1String(newLineLatin1))) + const QmltcTypeResolver *localResolver, const QmltcCompilerInfo *info) + : m_url(url), m_logger(logger), m_doc(doc), m_localTypeResolver(localResolver), m_info(info) { + Q_ASSERT(m_info); + Q_ASSERT(!m_info->outputHFile.isEmpty()); + Q_ASSERT(!m_info->outputCppFile.isEmpty()); } void CodeGenerator::constructObjects(QSet<QString> &requiredCppIncludes) @@ -291,21 +286,18 @@ void CodeGenerator::constructObjects(QSet<QString> &requiredCppIncludes) m_ignoredTypes = collectIgnoredTypes(context, objects); }; executor.addPass(setIgnoredTypes); + executor.addPass(&setDeferredBindings); // run all passes: executor.run(m_logger); } -void CodeGenerator::generate(const Options &options) +void CodeGenerator::generate() { - m_options = options; - GeneratedCode code; const QString rootClassName = QFileInfo(m_url).baseName(); Q_ASSERT(!rootClassName.isEmpty()); - Q_ASSERT(!options.outputHFile.isEmpty()); - Q_ASSERT(!options.outputCppFile.isEmpty()); - const QString hPath = options.outputHFile; - const QString cppPath = options.outputCppFile; + const QString hPath = m_info->outputHFile; + const QString cppPath = m_info->outputCppFile; m_isAnonymous = rootClassName.at(0).isLower(); const QString url = QFileInfo(m_url).fileName(); @@ -315,7 +307,7 @@ void CodeGenerator::generate(const Options &options) QSet<QString> requiredCppIncludes; constructObjects(requiredCppIncludes); // this populates all the codegen objects // no point in compiling anything if there are errors - if (m_logger->hasErrors() || m_logger->hasWarnings()) + if (m_logger->hasErrors()) return; if (m_objects.isEmpty()) { @@ -329,16 +321,16 @@ void CodeGenerator::generate(const Options &options) }; const auto &root = m_objects.at(0).type; - QList<QQmlJSAotObject> compiledObjects; + QList<QmltcType> compiledObjects; if (isComponent(root)) { compiledObjects.reserve(1); compiledObjects.emplaceBack(); // create new object - const auto compile = [this](QQmlJSAotObject ¤t, const CodeGenObject &object) { + const auto compile = [this](QmltcType ¤t, const CodeGenObject &object) { this->compileQQmlComponentElements(current, object); }; compileObject(compiledObjects.back(), m_objects.at(0), compile); } else { - const auto compile = [this](QQmlJSAotObject ¤t, const CodeGenObject &object) { + const auto compile = [this](QmltcType ¤t, const CodeGenObject &object) { this->compileObjectElements(current, object); }; @@ -362,18 +354,21 @@ void CodeGenerator::generate(const Options &options) } } // no point in generating anything if there are errors - if (m_logger->hasErrors() || m_logger->hasWarnings()) + if (m_logger->hasErrors()) return; - QQmlJSProgram program { compiledObjects, m_urlMethod, url, hPath, cppPath, - options.outNamespace, requiredCppIncludes }; + QmltcProgram program { + url, cppPath, hPath, m_info->outputNamespace, requiredCppIncludes, + m_urlMethod, compiledObjects + }; // write everything - GeneratedCodeUtils codeUtils(code); - CodeGeneratorWriter::write(codeUtils, program); + QmltcOutput code; + QmltcOutputWrapper codeUtils(code); + QmltcCodeWriter::write(codeUtils, program); writeToFile(hPath, code.header.toUtf8()); - writeToFile(cppPath, code.implementation.toUtf8()); + writeToFile(cppPath, code.cpp.toUtf8()); } QString buildCallSpecialMethodValue(bool documentRoot, const QString &outerFlagName, @@ -388,9 +383,14 @@ QString buildCallSpecialMethodValue(bool documentRoot, const QString &outerFlagN } void CodeGenerator::compileObject( - QQmlJSAotObject &compiled, const CodeGenObject &object, - std::function<void(QQmlJSAotObject &, const CodeGenObject &)> compileElements) + QmltcType &compiled, const CodeGenObject &object, + std::function<void(QmltcType &, const CodeGenObject &)> compileElements) { + if (object.type->isSingleton()) { + recordError(object.type->sourceLocation(), u"Singleton types are not supported"_qs); + return; + } + compiled.cppType = object.type->internalName(); const QString baseClass = object.type->baseType()->internalName(); @@ -428,14 +428,14 @@ void CodeGenerator::compileObject( compiled.handleOnCompleted.name = u"QML_handleOnCompleted"_qs; compiled.handleOnCompleted.returnType = u"void"_qs; - QQmlJSAotVariable engine(u"QQmlEngine *"_qs, u"engine"_qs, QString()); - QQmlJSAotVariable parent(u"QObject *"_qs, u"parent"_qs, u"nullptr"_qs); + QmltcVariable engine(u"QQmlEngine *"_qs, u"engine"_qs, QString()); + QmltcVariable parent(u"QObject *"_qs, u"parent"_qs, u"nullptr"_qs); compiled.baselineCtor.parameterList = { parent }; compiled.externalCtor.parameterList = { engine, parent }; - QQmlJSAotVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData> &"_qs, u"parentContext"_qs, - QString()); - QQmlJSAotVariable finalizeFlag(u"bool"_qs, u"canFinalize"_qs, QString()); - QQmlJSAotVariable callSpecialMethodFlag(u"bool"_qs, u"callSpecialMethodNow"_qs, QString()); + QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData> &"_qs, u"parentContext"_qs, + QString()); + QmltcVariable finalizeFlag(u"bool"_qs, u"canFinalize"_qs, QString()); + QmltcVariable callSpecialMethodFlag(u"bool"_qs, u"callSpecialMethodNow"_qs, QString()); if (documentRoot) { compiled.init.parameterList = { engine, ctxtdata, finalizeFlag, callSpecialMethodFlag }; compiled.endInit.parameterList = { engine, finalizeFlag }; @@ -645,6 +645,17 @@ void CodeGenerator::compileObject( + u"(engine, /* finalize */ false);"; compiled.endInit.body << u"}"_qs; } + + if (object.irObject->flags & QV4::CompiledData::Object::HasDeferredBindings) { + compiled.endInit.body << u"{ // defer bindings"_qs; + compiled.endInit.body << u"auto ddata = QQmlData::get(this);"_qs; + compiled.endInit.body << u"auto thisContext = ddata->outerContext;"_qs; + compiled.endInit.body << u"Q_ASSERT(thisContext);"_qs; + compiled.endInit.body << u"ddata->deferData(" + QString::number(objectIndex) + u", " + + CodeGeneratorUtility::compilationUnitVariable.name + u", thisContext);"; + compiled.endInit.body << u"}"_qs; + } + // TODO: decide whether begin/end property update group is needed // compiled.endInit.body << u"Qt::beginPropertyUpdateGroup(); // defer binding evaluation"_qs; @@ -682,20 +693,8 @@ void CodeGenerator::compileObject( // compiled.endInit.body << u"Qt::endPropertyUpdateGroup();"_qs; } -void CodeGenerator::compileObjectElements(QQmlJSAotObject &compiled, const CodeGenObject &object) +void CodeGenerator::compileObjectElements(QmltcType &compiled, const CodeGenObject &object) { - if (object.type->isSingleton()) { - if (m_isAnonymous) { - recordError(object.type->sourceLocation(), - QStringLiteral(u"This singleton type won't be accessible from the outside. " - "Consider changing the file name so that it starts with a " - "capital letter.")); - return; - } - compiled.mocCode << u"QML_SINGLETON"_qs; - compiled.externalCtor.access = QQmlJSMetaMethod::Private; - } - // compile enums const auto enums = object.type->ownEnumerations(); compiled.enums.reserve(enums.size()); @@ -787,15 +786,9 @@ void CodeGenerator::compileObjectElements(QQmlJSAotObject &compiled, const CodeG compileBinding(compiled, **it, object, { object.type, u"this"_qs, u""_qs, false }); } -void CodeGenerator::compileQQmlComponentElements(QQmlJSAotObject &compiled, - const CodeGenObject &object) +void CodeGenerator::compileQQmlComponentElements(QmltcType &compiled, const CodeGenObject &object) { - if (object.type->isSingleton()) { - // it is unclear what to do with singletons in general, so just reject - recordError(object.type->sourceLocation(), - QStringLiteral(u"Singleton Component-based types are not supported")); - return; - } + Q_UNUSED(object); // since we create a document root as QQmlComponent, we only need to fake // QQmlComponent construction in init: @@ -827,7 +820,7 @@ void CodeGenerator::compileQQmlComponentElements(QQmlJSAotObject &compiled, compiled.init.body << u"}"_qs; } -void CodeGenerator::compileEnum(QQmlJSAotObject ¤t, const QQmlJSMetaEnum &e) +void CodeGenerator::compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e) { const auto intValues = e.values(); QStringList values; @@ -840,7 +833,7 @@ void CodeGenerator::compileEnum(QQmlJSAotObject ¤t, const QQmlJSMetaEnum & u"Q_ENUM(%1)"_qs.arg(e.name())); } -void CodeGenerator::compileProperty(QQmlJSAotObject ¤t, const QQmlJSMetaProperty &p, +void CodeGenerator::compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p, const QQmlJSScope::ConstPtr &owner) { Q_ASSERT(!p.isAlias()); // will be handled separately @@ -872,10 +865,10 @@ void CodeGenerator::compileProperty(QQmlJSAotObject ¤t, const QQmlJSMetaPr // If p.isList(), it's a QQmlListProperty. Then you can write the underlying list through // the QQmlListProperty object retrieved with the getter. Setting it would make no sense. if (p.isWritable() && !p.isList()) { - QQmlJSAotMethod setter {}; + QmltcMethod setter {}; setter.returnType = u"void"_qs; setter.name = compilationData.write; - // QQmlJSAotVariable + // QmltcVariable setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(underlyingType), name + u"_", u""_qs); setter.body << variableName + u".setValue(" + name + u"_);"; @@ -885,7 +878,7 @@ void CodeGenerator::compileProperty(QQmlJSAotObject ¤t, const QQmlJSMetaPr mocPieces << u"WRITE"_qs << setter.name; } - QQmlJSAotMethod getter {}; + QmltcMethod getter {}; getter.returnType = underlyingType; getter.name = compilationData.read; getter.body << u"return " + variableName + u".value();"; @@ -895,7 +888,7 @@ void CodeGenerator::compileProperty(QQmlJSAotObject ¤t, const QQmlJSMetaPr // 2. add bindable if (!p.isList()) { - QQmlJSAotMethod bindable {}; + QmltcMethod bindable {}; bindable.returnType = u"QBindable<" + underlyingType + u">"; bindable.name = compilationData.bindable; bindable.body << u"return QBindable<" + underlyingType + u">(std::addressof(" + variableName @@ -923,7 +916,7 @@ void CodeGenerator::compileProperty(QQmlJSAotObject ¤t, const QQmlJSMetaPr compilationData.notify); } -void CodeGenerator::compileAlias(QQmlJSAotObject ¤t, const QQmlJSMetaProperty &alias, +void CodeGenerator::compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &alias, const QQmlJSScope::ConstPtr &owner) { const QString aliasName = alias.propertyName(); @@ -1047,7 +1040,7 @@ void CodeGenerator::compileAlias(QQmlJSAotObject ¤t, const QQmlJSMetaPrope Qml2CppPropertyData compilationData(aliasName); // 1. add setter and getter if (!info.readLine.isEmpty()) { - QQmlJSAotMethod getter {}; + QmltcMethod getter {}; getter.returnType = info.underlyingType; getter.name = compilationData.read; getter.body += prologue; @@ -1059,13 +1052,13 @@ void CodeGenerator::compileAlias(QQmlJSAotObject ¤t, const QQmlJSMetaPrope } // else always an error? if (!info.writeLine.isEmpty()) { - QQmlJSAotMethod setter {}; + QmltcMethod setter {}; setter.returnType = u"void"_qs; setter.name = compilationData.write; QList<QQmlJSMetaMethod> methods = type->methods(resultingProperty.write()); if (methods.isEmpty()) { - // QQmlJSAotVariable + // QmltcVariable setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(info.underlyingType), aliasName + u"_", u""_qs); } else { @@ -1079,7 +1072,7 @@ void CodeGenerator::compileAlias(QQmlJSAotObject ¤t, const QQmlJSMetaPrope parameterNames.reserve(setter.parameterList.size()); std::transform(setter.parameterList.cbegin(), setter.parameterList.cend(), std::back_inserter(parameterNames), - [](const QQmlJSAotVariable &x) { return x.name; }); + [](const QmltcVariable &x) { return x.name; }); QString commaSeparatedParameterNames = parameterNames.join(u", "_qs); setter.body << info.writeLine.arg(commaSeparatedParameterNames) + u";"; } else { @@ -1093,7 +1086,7 @@ void CodeGenerator::compileAlias(QQmlJSAotObject ¤t, const QQmlJSMetaPrope // 2. add bindable if (!info.bindableLine.isEmpty()) { - QQmlJSAotMethod bindable {}; + QmltcMethod bindable {}; bindable.returnType = u"QBindable<" + info.underlyingType + u">"; bindable.name = compilationData.bindable; bindable.body += prologue; @@ -1129,7 +1122,7 @@ void CodeGenerator::compileAlias(QQmlJSAotObject ¤t, const QQmlJSMetaPrope } } -void CodeGenerator::compileMethod(QQmlJSAotObject ¤t, const QQmlJSMetaMethod &m, +void CodeGenerator::compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m, const QmlIR::Function *f, const CodeGenObject &object) { Q_UNUSED(object); @@ -1138,7 +1131,7 @@ void CodeGenerator::compileMethod(QQmlJSAotObject ¤t, const QQmlJSMetaMeth const auto paramNames = m.parameterNames(); const auto paramTypes = m.parameterTypes(); Q_ASSERT(paramNames.size() == paramTypes.size()); - const QList<QQmlJSAotVariable> paramList = compileMethodParameters(paramNames, paramTypes); + const QList<QmltcVariable> paramList = compileMethodParameters(paramNames, paramTypes); const auto methodType = QQmlJSMetaMethod::Type(m.methodType()); @@ -1153,7 +1146,7 @@ void CodeGenerator::compileMethod(QQmlJSAotObject ¤t, const QQmlJSMetaMeth returnType, paramList); } - QQmlJSAotMethod compiled {}; + QmltcMethod compiled {}; compiled.returnType = returnType; compiled.name = m.methodName(); compiled.parameterList = std::move(paramList); @@ -1161,7 +1154,7 @@ void CodeGenerator::compileMethod(QQmlJSAotObject ¤t, const QQmlJSMetaMeth compiled.type = methodType; compiled.access = m.access(); if (methodType != QQmlJSMetaMethod::Signal) { - compiled.declPreambles << u"Q_INVOKABLE"_qs; // TODO: do we need this for signals as well? + compiled.declarationPrefixes << u"Q_INVOKABLE"_qs; compiled.userVisible = m.access() == QQmlJSMetaMethod::Public; } else { compiled.userVisible = !m.isImplicitQmlPropertyChangeSignal(); @@ -1176,10 +1169,33 @@ static QString getPropertyOrAliasNameFromIr(const QmlIR::Document *doc, Iterator return doc->stringAt(first->nameIndex); } -void CodeGenerator::compileBinding(QQmlJSAotObject ¤t, const QmlIR::Binding &binding, +void CodeGenerator::compileBinding(QmltcType ¤t, const QmlIR::Binding &binding, const CodeGenObject &object, const CodeGenerator::AccessorData &accessor) { + // Note: unlike QQmlObjectCreator, we don't have to do a complicated + // deferral logic for bindings: if a binding is deferred, it is not compiled + // (potentially, with all the bindings inside of it), period. + if (binding.flags & QV4::CompiledData::Binding::IsDeferredBinding) { + if (binding.type == QmlIR::Binding::Type_GroupProperty) { + // TODO: we should warn about this in QmlCompiler library + qCWarning(lcCodeGenerator) + << QStringLiteral("Binding at line %1 column %2 is not deferred as it is a " + "binding on a group property.") + .arg(QString::number(binding.location.line), + QString::number(binding.location.column)); + // we do not support PropertyChanges and other types with similar + // behavior yet, so this binding is compiled + } else { + qCDebug(lcCodeGenerator) + << QStringLiteral( + "Binding at line %1 column %2 is deferred and thus not compiled") + .arg(QString::number(binding.location.line), + QString::number(binding.location.column)); + return; + } + } + // TODO: cache property name somehow, so we don't need to look it up again QString propertyName = m_doc->stringAt(binding.propertyNameIndex); if (propertyName.isEmpty()) { @@ -1401,6 +1417,9 @@ void CodeGenerator::compileBinding(QQmlJSAotObject ¤t, const QmlIR::Bindin std::for_each(irObject->bindingsBegin(), irObject->bindingsEnd(), compileComponent); } else { const QString attachingTypeName = propertyName; // acts as an identifier + auto attachingType = m_localTypeResolver->typeForName(attachingTypeName); + Q_ASSERT(attachingType); // an error somewhere else + QString attachedTypeName = type->attachedTypeName(); // TODO: check if == internalName? if (attachedTypeName.isEmpty()) // TODO: shouldn't happen ideally attachedTypeName = type->baseTypeName(); @@ -1413,7 +1432,7 @@ void CodeGenerator::compileBinding(QQmlJSAotObject ¤t, const QmlIR::Bindin u"nullptr"_qs); // Note: getting attached property is fairly expensive const QString getAttachedPropertyLine = u"qobject_cast<" + attachedTypeName - + u" *>(qmlAttachedPropertiesObject<" + attachedTypeName + + u" *>(qmlAttachedPropertiesObject<" + attachingType->internalName() + u">(this, /* create = */ true))"; current.endInit.body << attachedMemberName + u" = " + getAttachedPropertyLine + u";"; @@ -1422,9 +1441,10 @@ void CodeGenerator::compileBinding(QQmlJSAotObject ¤t, const QmlIR::Bindin // compile bindings of the attached property auto sortedBindings = toOrderedSequence( irObject->bindingsBegin(), irObject->bindingsEnd(), irObject->bindingCount()); - for (auto it : qAsConst(sortedBindings)) + for (auto it : qAsConst(sortedBindings)) { compileBinding(current, *it, attachedObject, { object.type, attachedMemberName, propertyName, false }); + } } break; } @@ -1559,26 +1579,26 @@ QString CodeGenerator::makeGensym(const QString &base) } // returns compiled script binding for "property changed" handler in a form of object type -static QQmlJSAotObject compileScriptBindingPropertyChangeHandler( +static QmltcType compileScriptBindingPropertyChangeHandler( const QmlIR::Document *doc, const QmlIR::Binding &binding, const QmlIR::Object *irObject, - const QQmlJSAotMethod &urlMethod, const QString &functorCppType, - const QString &objectCppType, const QList<QQmlJSAotVariable> &slotParameters) + const QmltcMethod &urlMethod, const QString &functorCppType, const QString &objectCppType, + const QList<QmltcVariable> &slotParameters) { - QQmlJSAotObject bindingFunctor {}; + QmltcType bindingFunctor {}; bindingFunctor.cppType = functorCppType; bindingFunctor.ignoreInit = true; // default member variable and ctor: const QString pointerToObject = objectCppType + u" *"; bindingFunctor.variables.emplaceBack( - QQmlJSAotVariable { pointerToObject, u"m_self"_qs, u"nullptr"_qs }); + QmltcVariable { pointerToObject, u"m_self"_qs, u"nullptr"_qs }); bindingFunctor.baselineCtor.name = functorCppType; bindingFunctor.baselineCtor.parameterList.emplaceBack( - QQmlJSAotVariable { pointerToObject, u"self"_qs, QString() }); + QmltcVariable { pointerToObject, u"self"_qs, QString() }); bindingFunctor.baselineCtor.initializerList.emplaceBack(u"m_self(self)"_qs); // call operator: - QQmlJSAotMethod callOperator {}; + QmltcMethod callOperator {}; callOperator.returnType = u"void"_qs; callOperator.name = u"operator()"_qs; callOperator.parameterList = slotParameters; @@ -1608,7 +1628,7 @@ propertyForChangeHandler(const QQmlJSScope::ConstPtr &scope, QString name) return {}; } -void CodeGenerator::compileScriptBinding(QQmlJSAotObject ¤t, const QmlIR::Binding &binding, +void CodeGenerator::compileScriptBinding(QmltcType ¤t, const QmlIR::Binding &binding, const QString &bindingSymbolName, const CodeGenObject &object, const QString &propertyName, const QQmlJSScope::ConstPtr &propertyType, @@ -1647,7 +1667,7 @@ void CodeGenerator::compileScriptBinding(QQmlJSAotObject ¤t, const QmlIR:: }; // these only make sense when binding is on signal handler - QList<QQmlJSAotVariable> slotParameters; + QList<QmltcVariable> slotParameters; QString signalName; QString signalReturnType; @@ -1734,15 +1754,8 @@ void CodeGenerator::compileScriptBinding(QQmlJSAotObject ¤t, const QmlIR:: Q_ASSERT(!objectClassName_slot.isEmpty()); const QString slotName = makeGensym(signalName + u"_slot"); - if (objectType->isSingleton()) { // TODO: support - recordError(binding.location, - u"Binding on singleton type '" + objectClassName_signal - + u"' is not supported"); - return; - } - // SignalHander specific: - QQmlJSAotMethod slotMethod {}; + QmltcMethod slotMethod {}; slotMethod.returnType = signalReturnType; slotMethod.name = slotName; slotMethod.parameterList = slotParameters; @@ -1819,7 +1832,7 @@ void CodeGenerator::compileScriptBinding(QQmlJSAotObject ¤t, const QmlIR:: + accessor.name + u"))));"; current.variables.emplaceBack( - QQmlJSAotVariable { typeOfQmlBinding, bindingSymbolName, QString() }); + QmltcVariable { typeOfQmlBinding, bindingSymbolName, QString() }); // current.ctor.initializerList << bindingSymbolName + u"()"; break; } @@ -1827,7 +1840,7 @@ void CodeGenerator::compileScriptBinding(QQmlJSAotObject ¤t, const QmlIR:: } // TODO: should use "compileScriptBinding" instead of custom code -void CodeGenerator::compileScriptBindingOfComponent(QQmlJSAotObject ¤t, +void CodeGenerator::compileScriptBindingOfComponent(QmltcType ¤t, const QmlIR::Object *irObject, const QQmlJSScope::ConstPtr objectType, const QmlIR::Binding &binding, @@ -1867,7 +1880,7 @@ void CodeGenerator::compileScriptBindingOfComponent(QQmlJSAotObject ¤t, const QString slotName = makeGensym(signalName + u"_slot"); // SignalHander specific: - QQmlJSAotMethod slotMethod {}; + QmltcMethod slotMethod {}; slotMethod.returnType = signalReturnType; slotMethod.name = slotName; @@ -1885,7 +1898,8 @@ void CodeGenerator::compileScriptBindingOfComponent(QQmlJSAotObject ¤t, current.handleOnCompleted.body << slotName + u"();"; } else if (signalName == u"destruction"_qs) { if (!current.dtor) { - current.dtor = QQmlJSAotSpecialMethod {}; + // TODO: double-check that this stuff is actually correct now: + current.dtor = QmltcDtor {}; current.dtor->name = u"~" + current.cppType; } current.dtor->firstLines << slotName + u"();"; @@ -1898,18 +1912,20 @@ void CodeGenerator::compileUrlMethod() m_urlMethod.returnType = u"const QUrl &"_qs; m_urlMethod.name = u"q_qmltc_docUrl"_qs; m_urlMethod.body << u"static QUrl docUrl = %1;"_qs.arg( - CodeGeneratorUtility::toResourcePath(m_options.resourcePath)); + CodeGeneratorUtility::toResourcePath(m_info->resourcePath)); m_urlMethod.body << u"return docUrl;"_qs; - m_urlMethod.declPreambles << u"static"_qs; + m_urlMethod.declarationPrefixes << u"static"_qs; m_urlMethod.modifiers << u"noexcept"_qs; } void CodeGenerator::recordError(const QQmlJS::SourceLocation &location, const QString &message) { - m_logger->logCritical(message, Log_Compiler, location); + m_logger->log(message, Log_Compiler, location); } void CodeGenerator::recordError(const QV4::CompiledData::Location &location, const QString &message) { recordError(QQmlJS::SourceLocation { 0, 0, location.line, location.column }, message); } + +QT_END_NAMESPACE diff --git a/tools/qmltc/prototype/codegenerator.h b/tools/qmltc/prototype/codegenerator.h index 63549ae76e..a18699d23c 100644 --- a/tools/qmltc/prototype/codegenerator.h +++ b/tools/qmltc/prototype/codegenerator.h @@ -29,9 +29,8 @@ #ifndef CODEGENERATOR_H #define CODEGENERATOR_H -#include "prototype/typeresolver.h" -#include "prototype/qmlcompiler.h" -#include "prototype/generatedcodeprimitives.h" +#include "qmltctyperesolver.h" +#include "qmltcoutputir.h" #include "prototype/qml2cppcontext.h" #include <QtCore/qlist.h> @@ -40,19 +39,21 @@ #include <QtQml/private/qqmlirbuilder_p.h> #include <private/qqmljscompiler_p.h> -#include <private/qqmljstyperesolver_p.h> #include <variant> #include <utility> +QT_BEGIN_NAMESPACE + +struct QmltcCompilerInfo; class CodeGenerator { public: CodeGenerator(const QString &url, QQmlJSLogger *logger, QmlIR::Document *doc, - const Qmltc::TypeResolver *localResolver); + const QmltcTypeResolver *localResolver, const QmltcCompilerInfo *info); // main function: given compilation options, generates C++ code (implicitly) - void generate(const Options &options); + void generate(); // TODO: this should really be just QQmlJSScope::ConstPtr (and maybe C++ // class name), but bindings are currently not represented in QQmlJSScope, @@ -63,10 +64,9 @@ private: QString m_url; // document url QQmlJSLogger *m_logger = nullptr; QmlIR::Document *m_doc = nullptr; - const Qmltc::TypeResolver *m_localTypeResolver = nullptr; - QStringList m_qmlSource; // QML source code split to lines + const QmltcTypeResolver *m_localTypeResolver = nullptr; - Options m_options = {}; // compilation options + const QmltcCompilerInfo *m_info = nullptr; // convenient object abstraction, laid out as QmlIR::Document.objects QList<CodeGenObject> m_objects; @@ -79,13 +79,13 @@ private: // types ignored by the code generator QSet<QQmlJSScope::ConstPtr> m_ignoredTypes; - QQmlJSAotMethod m_urlMethod; + QmltcMethod m_urlMethod; // helper struct used for unique string generation struct UniqueStringId { QString combined; - UniqueStringId(const QQmlJSAotObject &compiled, const QString &value) + UniqueStringId(const QmltcType &compiled, const QString &value) : combined(compiled.cppType + u"_" + value) { Q_ASSERT(!compiled.cppType.isEmpty()); @@ -140,19 +140,18 @@ private: bool m_isAnonymous = false; // crutch to distinguish QML_ELEMENT from QML_ANONYMOUS // code compilation functions that produce "compiled" entities - void - compileObject(QQmlJSAotObject ¤t, const CodeGenObject &object, - std::function<void(QQmlJSAotObject &, const CodeGenObject &)> compileElements); - void compileObjectElements(QQmlJSAotObject ¤t, const CodeGenObject &object); - void compileQQmlComponentElements(QQmlJSAotObject ¤t, const CodeGenObject &object); - - void compileEnum(QQmlJSAotObject ¤t, const QQmlJSMetaEnum &e); - void compileProperty(QQmlJSAotObject ¤t, const QQmlJSMetaProperty &p, + void compileObject(QmltcType ¤t, const CodeGenObject &object, + std::function<void(QmltcType &, const CodeGenObject &)> compileElements); + void compileObjectElements(QmltcType ¤t, const CodeGenObject &object); + void compileQQmlComponentElements(QmltcType ¤t, const CodeGenObject &object); + + void compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e); + void compileProperty(QmltcType ¤t, const QQmlJSMetaProperty &p, const QQmlJSScope::ConstPtr &owner); - void compileAlias(QQmlJSAotObject ¤t, const QQmlJSMetaProperty &alias, + void compileAlias(QmltcType ¤t, const QQmlJSMetaProperty &alias, const QQmlJSScope::ConstPtr &owner); - void compileMethod(QQmlJSAotObject ¤t, const QQmlJSMetaMethod &m, - const QmlIR::Function *f, const CodeGenObject &object); + void compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m, const QmlIR::Function *f, + const CodeGenObject &object); void compileUrlMethod(); // special case // helper structure that holds the information necessary for most bindings, @@ -166,17 +165,17 @@ private: QString propertyName; // usually empty bool isValueType = false; // usually false }; - void compileBinding(QQmlJSAotObject ¤t, const QmlIR::Binding &binding, + void compileBinding(QmltcType ¤t, const QmlIR::Binding &binding, const CodeGenObject &object, const AccessorData &accessor); // special case (for simplicity) - void compileScriptBinding(QQmlJSAotObject ¤t, const QmlIR::Binding &binding, + void compileScriptBinding(QmltcType ¤t, const QmlIR::Binding &binding, const QString &bindingSymbolName, const CodeGenObject &object, const QString &propertyName, const QQmlJSScope::ConstPtr &propertyType, const AccessorData &accessor); // TODO: remove this special case - void compileScriptBindingOfComponent(QQmlJSAotObject ¤t, const QmlIR::Object *object, + void compileScriptBindingOfComponent(QmltcType ¤t, const QmlIR::Object *object, const QQmlJSScope::ConstPtr objectType, const QmlIR::Binding &binding, const QString &propertyName); @@ -186,4 +185,6 @@ private: void recordError(const QV4::CompiledData::Location &location, const QString &message); }; +QT_END_NAMESPACE + #endif // CODEGENERATOR_H diff --git a/tools/qmltc/prototype/codegeneratorutil.cpp b/tools/qmltc/prototype/codegeneratorutil.cpp index 3d7300e0d8..878a1f4b07 100644 --- a/tools/qmltc/prototype/codegeneratorutil.cpp +++ b/tools/qmltc/prototype/codegeneratorutil.cpp @@ -33,13 +33,14 @@ #include <tuple> +QT_BEGIN_NAMESPACE + // NB: this variable would behave correctly as long as QML init and QML finalize // are non-virtual functions -const QQmlJSAotVariable CodeGeneratorUtility::childrenOffsetVariable = { u"qsizetype"_qs, - u"QML_choffset"_qs, - QString() }; +const QmltcVariable CodeGeneratorUtility::childrenOffsetVariable { u"qsizetype"_qs, + u"QML_choffset"_qs, QString() }; -const QQmlJSAotVariable CodeGeneratorUtility::compilationUnitVariable = { +const QmltcVariable CodeGeneratorUtility::compilationUnitVariable { u"QV4::ExecutableCompilationUnit *"_qs, u"QML_cu"_qs, QString() }; @@ -141,7 +142,7 @@ QStringList CodeGeneratorUtility::generate_assignToSpecialAlias( QStringList CodeGeneratorUtility::generate_callExecuteRuntimeFunction( const QString &url, qsizetype index, const QString &accessor, const QString &returnType, - const QList<QQmlJSAotVariable> ¶meters) + const QList<QmltcVariable> ¶meters) { QStringList code; code.reserve(12); // should always be enough @@ -163,7 +164,7 @@ QStringList CodeGeneratorUtility::generate_callExecuteRuntimeFunction( types << u"QMetaType::fromType<std::decay_t<" + returnType + u">>()"; } - for (const QQmlJSAotVariable &p : parameters) { + for (const QmltcVariable &p : parameters) { args << u"const_cast<void *>(reinterpret_cast<const void *>(std::addressof(" + p.name + u")))"; types << u"QMetaType::fromType<std::decay_t<" + p.cppType + u">>()"; @@ -212,12 +213,12 @@ QStringList CodeGeneratorUtility::generate_createBindingOnProperty( return code; } -QString CodeGeneratorUtility::generate_qOverload(const QList<QQmlJSAotVariable> ¶ms, +QString CodeGeneratorUtility::generate_qOverload(const QList<QmltcVariable> ¶ms, const QString &overloaded) { QStringList types; types.reserve(params.size()); - for (const QQmlJSAotVariable &p : params) + for (const QmltcVariable &p : params) types.emplaceBack(p.cppType); return u"qOverload<" + types.join(u", "_qs) + u">(" + overloaded + u")"; } @@ -245,3 +246,5 @@ QString CodeGeneratorUtility::generate_setIdValue(const QString &context, qsizet return context + u"->setIdValue(" + QString::number(index) + idComment + u", " + accessor + u")"; } + +QT_END_NAMESPACE diff --git a/tools/qmltc/prototype/codegeneratorutil.h b/tools/qmltc/prototype/codegeneratorutil.h index d8b8defac8..4f5518aeed 100644 --- a/tools/qmltc/prototype/codegeneratorutil.h +++ b/tools/qmltc/prototype/codegeneratorutil.h @@ -29,7 +29,7 @@ #ifndef CODEGENERATORUTIL_H #define CODEGENERATORUTIL_H -#include "prototype/qmlcompiler.h" +#include "qmltcoutputir.h" #include <private/qqmljsscope_p.h> #include <private/qqmljsmetatypes_p.h> @@ -39,6 +39,8 @@ #include <utility> +QT_BEGIN_NAMESPACE + struct CodeGeneratorUtility { // magic variable, necessary for correct handling of object bindings: since @@ -51,10 +53,10 @@ struct CodeGeneratorUtility // reference any object in the document by id, which automatically means // that all ids have to be set up before we get to finalization (and the // only place for it is init) - static const QQmlJSAotVariable childrenOffsetVariable; + static const QmltcVariable childrenOffsetVariable; // represents QV4::ExecutableCompilationUnit - static const QQmlJSAotVariable compilationUnitVariable; + static const QmltcVariable compilationUnitVariable; // helper functions: static QString toResourcePath(const QString &s) @@ -82,13 +84,13 @@ struct CodeGeneratorUtility static QStringList generate_callExecuteRuntimeFunction(const QString &url, qsizetype index, const QString &accessor, const QString &returnType, - const QList<QQmlJSAotVariable> ¶meters = {}); + const QList<QmltcVariable> ¶meters = {}); static QStringList generate_createBindingOnProperty(const QString &unitVarName, const QString &scope, qsizetype functionIndex, const QString &target, int propertyIndex, const QQmlJSMetaProperty &p, int valueTypeIndex, const QString &subTarget); - static QString generate_qOverload(const QList<QQmlJSAotVariable> ¶meters, + static QString generate_qOverload(const QList<QmltcVariable> ¶meters, const QString &overloaded); static QString generate_addressof(const QString &addressed); static QString generate_getPrivateClass(const QString &accessor, const QQmlJSMetaProperty &p); @@ -96,4 +98,6 @@ struct CodeGeneratorUtility const QString &accessor, const QString &idString); }; +QT_END_NAMESPACE + #endif // CODEGENERATORUTIL_H diff --git a/tools/qmltc/prototype/codegeneratorwriter.cpp b/tools/qmltc/prototype/codegeneratorwriter.cpp deleted file mode 100644 index d43a94084e..0000000000 --- a/tools/qmltc/prototype/codegeneratorwriter.cpp +++ /dev/null @@ -1,476 +0,0 @@ -/**************************************************************************** -** -** 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 "codegeneratorwriter.h" - -#include <private/qqmljsmetatypes_p.h> - -#include <QtCore/qfileinfo.h> - -#include <utility> -#include <functional> - -static constexpr char16_t newLine[] = -#ifdef Q_OS_WIN32 - u"\r\n"; -#else - u"\n"; -#endif -static constexpr char newLineLatin1[] = -#ifdef Q_OS_WIN32 - "\r\n"; -#else - "\n"; -#endif - -static QString urlToMacro(const QString &url) -{ - QFileInfo fi(url); - return u"Q_QMLTC_" + fi.baseName().toUpper(); -} - -static QString getFunctionCategory(const QQmlJSAotMethodBase &compiled) -{ - QString category; - switch (compiled.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 QQmlJSAotMethod &compiled) -{ - QString category = getFunctionCategory(static_cast<const QQmlJSAotMethodBase &>(compiled)); - switch (compiled.type) { - case QQmlJSMetaMethod::Signal: - category = u"signals"_qs; - break; - case QQmlJSMetaMethod::Slot: - category += u" slots"_qs; - break; - case QQmlJSMetaMethod::Method: - break; - } - return category; -} - -void CodeGeneratorWriter::writeGlobalHeader(GeneratedCodeUtils &code, const QString &sourceName, - const QString &hPath, const QString &cppPath, - const QString &outNamespace, - const QSet<QString> &requiredCppIncludes) -{ - Q_UNUSED(newLineLatin1); - - Q_UNUSED(cppPath); - const QString preamble = - u"// This code is auto-generated by the qmlcompiler tool from the file '" + sourceName - + u"'" + newLine + u"// WARNING! All changes made in this file will be lost!" + newLine; - code.appendToHeader(preamble); - code.appendToImpl(preamble); - code.appendToHeader(u"// NOTE: This generated API is to be considered implementation detail."); - code.appendToHeader( - u"// It may change from version to version and should not be relied upon."); - - const QString headerMacro = urlToMacro(sourceName); - code.appendToHeader(u"#ifndef %1_H"_qs.arg(headerMacro)); - code.appendToHeader(u"#define %1_H"_qs.arg(headerMacro)); - - code.appendToHeader(u"#include <QtCore/qproperty.h>"); - code.appendToHeader(u"#include <QtCore/qobject.h>"); - code.appendToHeader(u"#include <QtCore/qcoreapplication.h>"); - code.appendToHeader(u"#include <QtQml/qqmlengine.h>"); - code.appendToHeader(u"#include <QtCore/qurl.h>"); // used in engine execution - code.appendToHeader(u"#include <QtQml/qqml.h>"); // used for attached properties - - code.appendToHeader(u"#include <private/qqmlengine_p.h>"); // NB: private header - - code.appendToHeader(u"#include <QQmlListProperty>"); // required by list properties - - // include custom C++ includes required by used types - code.appendToHeader(u"// BEGIN(custom_cpp_includes)"); - for (const auto &requiredInclude : requiredCppIncludes) { - code.appendToHeader(u"#include \"" + requiredInclude + u"\""); - } - code.appendToHeader(u"// END(custom_cpp_includes)"); - - code.appendToImpl(u"#include \"" + hPath + u"\""); // include own .h file - code.appendToImpl(u"#include <private/qqmlcppbinding_p.h>"); // QmltcSupportLib - code.appendToImpl(u"#include <private/qqmlcpponassignment_p.h>"); // QmltcSupportLib - - code.appendToImpl(u"#include <private/qqmlobjectcreator_p.h>"); // createComponent() - code.appendToImpl(u"#include <private/qqmlcomponent_p.h>"); // QQmlComponentPrivate::get() - - code.appendToImpl(u""); - code.appendToImpl(u"#include <private/qobject_p.h>"); // NB: for private properties - code.appendToImpl(u"#include <private/qqmlobjectcreator_p.h>"); // for finalize callbacks - - code.appendToImpl(u""); // blank line - if (!outNamespace.isEmpty()) { - code.appendToHeader(u""); // blank line - code.appendToHeader(u"namespace %1 {"_qs.arg(outNamespace)); - code.appendToImpl(u""); // blank line - code.appendToImpl(u"namespace %1 {"_qs.arg(outNamespace)); - } -} - -void CodeGeneratorWriter::writeGlobalFooter(GeneratedCodeUtils &code, const QString &sourceName, - const QString &hPath, const QString &cppPath, - const QString &outNamespace) -{ - Q_UNUSED(code); - Q_UNUSED(hPath); - Q_UNUSED(cppPath); - - if (!outNamespace.isEmpty()) { - code.appendToImpl(u"} // namespace %1"_qs.arg(outNamespace)); - code.appendToImpl(u""); // blank line - code.appendToHeader(u"} // namespace %1"_qs.arg(outNamespace)); - code.appendToHeader(u""); // blank line - } - - code.appendToHeader(u"#endif // %1_H"_qs.arg(urlToMacro(sourceName))); - code.appendToHeader(u""); // blank line -} - -static QString classString(const QQmlJSAotObject &compiled) -{ - QString str = u"class " + compiled.cppType; - QStringList nonEmptyBaseClasses; - nonEmptyBaseClasses.reserve(compiled.baseClasses.size()); - std::copy_if(compiled.baseClasses.cbegin(), compiled.baseClasses.cend(), - std::back_inserter(nonEmptyBaseClasses), - [](const QString &entry) { return !entry.isEmpty(); }); - if (!nonEmptyBaseClasses.isEmpty()) - str += u" : public " + nonEmptyBaseClasses.join(u", public "_qs); - return str; -} - -template<typename Predicate> -static void dumpFunctions(GeneratedCodeUtils &code, const QList<QQmlJSAotMethod> &functions, - Predicate pred) -{ - // functions are _ordered_ by access and kind. ordering is important to - // provide consistent output - QMap<QString, QList<const QQmlJSAotMethod *>> 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.appendToHeader(it.key() + u":", -1); - for (const QQmlJSAotMethod *function : qAsConst(it.value())) - CodeGeneratorWriter::write(code, *function); - } -} - -void CodeGeneratorWriter::write(GeneratedCodeUtils &code, const QQmlJSAotObject &compiled) -{ - code.appendToHeader(u""); // just new line - code.appendToImpl(u""); // just new line - - // generate class preamble - code.appendToHeader(classString(compiled)); - code.appendToHeader(u"{"); - for (const QString &mocLine : qAsConst(compiled.mocCode)) - code.appendToHeader(mocLine, 1); - - for (const QString &otherLine : qAsConst(compiled.otherCode)) - code.appendToHeader(otherLine, 1); - - GeneratedCodeUtils::MemberNamespaceScope thisObjectScope(code, compiled.cppType); - Q_UNUSED(thisObjectScope); - { - GeneratedCodeUtils::HeaderIndentationScope headerIndentScope(code); - Q_UNUSED(headerIndentScope); - - // 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.appendToHeader(u"// -----------------"); - code.appendToHeader(u"// External C++ API:"); - code.appendToHeader(u"public:", -1); - - // 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 (!compiled.ignoreInit && compiled.externalCtor.access == QQmlJSMetaMethod::Public) { - // TODO: ignoreInit must be eliminated - - CodeGeneratorWriter::write(code, compiled.externalCtor); - } - // generate dtor - if (compiled.dtor) - CodeGeneratorWriter::write(code, *compiled.dtor); - - // generate enums - for (const auto &enumeration : qAsConst(compiled.enums)) - CodeGeneratorWriter::write(code, enumeration); - - // generate (visible) functions - const auto isUserVisibleFunction = [](const QQmlJSAotMethod &function) { - return function.userVisible; - }; - dumpFunctions(code, compiled.functions, isUserVisibleFunction); - - code.appendToHeader(u"// -----------------"); - code.appendToHeader(u""); // blank line - code.appendToHeader(u"// Internal functionality (do NOT use it!):"); - - // below are the hidden parts of the class - - // generate (rest of the) ctors - if (compiled.ignoreInit) { // TODO: this branch should be eliminated - Q_ASSERT(compiled.baselineCtor.access == QQmlJSMetaMethod::Public); - code.appendToHeader(u"public:", -1); - CodeGeneratorWriter::write(code, compiled.baselineCtor); - } else { - code.appendToHeader(u"protected:", -1); - if (compiled.externalCtor.access != QQmlJSMetaMethod::Public) { - Q_ASSERT(compiled.externalCtor.access == QQmlJSMetaMethod::Protected); - CodeGeneratorWriter::write(code, compiled.externalCtor); - } - CodeGeneratorWriter::write(code, compiled.baselineCtor); - CodeGeneratorWriter::write(code, compiled.init); - CodeGeneratorWriter::write(code, compiled.endInit); - CodeGeneratorWriter::write(code, compiled.completeComponent); - CodeGeneratorWriter::write(code, compiled.finalizeComponent); - CodeGeneratorWriter::write(code, compiled.handleOnCompleted); - - // code.appendToHeader(u"public:", -1); - } - - // generate child types - code.appendToHeader(u"// BEGIN(children)"); - for (const auto &child : qAsConst(compiled.children)) - CodeGeneratorWriter::write(code, child); - code.appendToHeader(u"// END(children)"); - - // generate functions - code.appendToHeader(u"// BEGIN(hidden_functions)"); - dumpFunctions(code, compiled.functions, std::not_fn(isUserVisibleFunction)); - code.appendToHeader(u"// END(hidden_functions)"); - - if (!compiled.variables.isEmpty() || !compiled.properties.isEmpty()) { - code.appendToHeader(u""); // blank line - code.appendToHeader(u"protected:", -1); - } - // generate variables - if (!compiled.variables.isEmpty()) { - code.appendToHeader(u"// BEGIN(variables)"); - for (const auto &variable : qAsConst(compiled.variables)) - CodeGeneratorWriter::write(code, variable); - code.appendToHeader(u"// END(variables)"); - } - - // generate properties - if (!compiled.properties.isEmpty()) { - code.appendToHeader(u"// BEGIN(properties)"); - for (const auto &property : qAsConst(compiled.properties)) - CodeGeneratorWriter::write(code, property); - code.appendToHeader(u"// END(properties)"); - } - } - - code.appendToHeader(u"};"); -} - -void CodeGeneratorWriter::write(GeneratedCodeUtils &code, const QQmlJSAotEnum &compiled) -{ - code.appendToHeader(u"enum " + compiled.cppType + u" {"); - for (qsizetype i = 0; i < compiled.keys.size(); ++i) { - QString str; - if (compiled.values.isEmpty()) { - str += compiled.keys.at(i) + u","; - } else { - str += compiled.keys.at(i) + u" = " + compiled.values.at(i) + u","; - } - code.appendToHeader(str, 1); - } - code.appendToHeader(u"};"); - code.appendToHeader(compiled.ownMocLine); -} - -// NB: property generation is only concerned with property declaration in the header -void CodeGeneratorWriter::write(GeneratedCodeUtils &code, const QQmlJSAotVariable &compiled) -{ - if (compiled.defaultValue.isEmpty()) { - code.appendToHeader(compiled.cppType + u" " + compiled.name + u";"); - } else { - code.appendToHeader(compiled.cppType + u" " + compiled.name + u" = " + compiled.defaultValue - + u";"); - } -} - -void CodeGeneratorWriter::write(GeneratedCodeUtils &code, const QQmlJSAotProperty &prop) -{ - Q_ASSERT(prop.defaultValue.isEmpty()); // we don't support it yet - code.appendToHeader(u"Q_OBJECT_BINDABLE_PROPERTY(%1, %2, %3, &%1::%4)"_qs.arg( - prop.containingClass, prop.cppType, prop.name, prop.signalName)); -} - -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<QString, QString> functionSignatures(const QQmlJSAotMethodBase &m) -{ - const QString name = m.name; - const QList<QQmlJSAotVariable> ¶meterList = m.parameterList; - QStringList headerParamList; - QStringList implParamList; - for (const QQmlJSAotVariable &variable : parameterList) { - const QString commonPart = variable.cppType + u" " + variable.name; - implParamList << commonPart; - headerParamList << commonPart; - if (!variable.defaultValue.isEmpty()) - headerParamList.back() += u" = " + variable.defaultValue; - } - const QString headerSignature = name + u"(" + headerParamList.join(u", "_qs) + u")" - + prependSpace(m.modifiers.join(u" ")); - const QString implSignature = name + u"(" + implParamList.join(u", "_qs) + u")" - + prependSpace(m.modifiers.join(u" ")); - return { headerSignature, implSignature }; -} - -static QString functionReturnType(const QQmlJSAotMethodBase &m) -{ - return appendSpace(m.declPreambles.join(u" "_qs)) + m.returnType; -} - -void CodeGeneratorWriter::write(GeneratedCodeUtils &code, const QQmlJSAotMethod &compiled) -{ - const auto [hSignature, cppSignature] = functionSignatures(compiled); - // Note: augment return type with preambles in declaration - code.appendToHeader(functionReturnType(compiled) + u" " + hSignature + u";"); - - // do not generate method implementation if it is a signal - const auto methodType = compiled.type; - if (methodType != QQmlJSMetaMethod::Signal) { - code.appendToImpl(u""); // just new line - code.appendToImpl(compiled.returnType); - code.appendSignatureToImpl(cppSignature); - code.appendToImpl(u"{"); - { - GeneratedCodeUtils::ImplIndentationScope indentScope(code); - Q_UNUSED(indentScope); - for (const QString &line : qAsConst(compiled.firstLines)) - code.appendToImpl(line); - for (const QString &line : qAsConst(compiled.body)) - code.appendToImpl(line); - for (const QString &line : qAsConst(compiled.lastLines)) - code.appendToImpl(line); - } - code.appendToImpl(u"}"); - } -} - -void CodeGeneratorWriter::write(GeneratedCodeUtils &code, const QQmlJSAotSpecialMethod &compiled) -{ - const auto [hSignature, cppSignature] = functionSignatures(compiled); - const QString returnTypeWithSpace = - compiled.returnType.isEmpty() ? u""_qs : compiled.returnType + u" "; - - code.appendToHeader(returnTypeWithSpace + hSignature + u";"); - - code.appendToImpl(u""); // just new line - if (!returnTypeWithSpace.isEmpty()) - code.appendToImpl(returnTypeWithSpace); - code.appendSignatureToImpl(cppSignature); - if (!compiled.initializerList.isEmpty()) { - code.appendToImpl(u":", 1); - code.appendToImpl(compiled.initializerList.join(u","_qs + newLine + newLine - + u" "_qs.repeated(code.implIndent + 1)), - 1); - } - code.appendToImpl(u"{"); - { - GeneratedCodeUtils::ImplIndentationScope indentScope(code); - Q_UNUSED(indentScope); - for (const QString &line : qAsConst(compiled.firstLines)) - code.appendToImpl(line); - for (const QString &line : qAsConst(compiled.body)) - code.appendToImpl(line); - for (const QString &line : qAsConst(compiled.lastLines)) - code.appendToImpl(line); - } - code.appendToImpl(u"}"); -} - -void CodeGeneratorWriter::writeUrl(GeneratedCodeUtils &code, const QQmlJSAotMethod &urlMethod) -{ - const auto [hSignature, _] = functionSignatures(urlMethod); - Q_UNUSED(_); - Q_ASSERT(!urlMethod.returnType.isEmpty()); - code.appendToImpl(functionReturnType(urlMethod) + hSignature); - code.appendToImpl(u"{"); - { - GeneratedCodeUtils::ImplIndentationScope indentScope(code); - Q_UNUSED(indentScope); - Q_ASSERT(urlMethod.firstLines.isEmpty() && urlMethod.lastLines.isEmpty()); - for (const QString &line : qAsConst(urlMethod.body)) - code.appendToImpl(line); - } - code.appendToImpl(u"}"); -} - -void CodeGeneratorWriter::write(GeneratedCodeUtils &code, const QQmlJSProgram &compiled) -{ - writeGlobalHeader(code, compiled.url, compiled.hPath, compiled.cppPath, compiled.outNamespace, - compiled.includes); - - code.appendToImpl(u""); // just new line - writeUrl(code, compiled.urlMethod); - - // forward declare objects before writing them - for (const QQmlJSAotObject &compiled : qAsConst(compiled.compiledObjects)) - code.appendToHeader(u"class " + compiled.cppType + u";"); - - // write all the objects - for (const QQmlJSAotObject &compiled : qAsConst(compiled.compiledObjects)) - write(code, compiled); - - writeGlobalFooter(code, compiled.url, compiled.hPath, compiled.cppPath, compiled.outNamespace); -} diff --git a/tools/qmltc/prototype/codegeneratorwriter.h b/tools/qmltc/prototype/codegeneratorwriter.h deleted file mode 100644 index e03a2123ac..0000000000 --- a/tools/qmltc/prototype/codegeneratorwriter.h +++ /dev/null @@ -1,57 +0,0 @@ -/**************************************************************************** -** -** 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 CODEGENERATORWRITER_H -#define CODEGENERATORWRITER_H - -#include "generatedcodeprimitives.h" -#include "qmlcompiler.h" - -// writes compiled code into the GeneratedCode structure -struct CodeGeneratorWriter -{ - static void writeGlobalHeader(GeneratedCodeUtils &code, const QString &sourceName, - const QString &hPath, const QString &cppPath, - const QString &outNamespace, - const QSet<QString> &requiredCppIncludes); - static void writeGlobalFooter(GeneratedCodeUtils &code, const QString &sourceName, - const QString &hPath, const QString &cppPath, - const QString &outNamespace); - static void write(GeneratedCodeUtils &code, const QQmlJSAotObject &compiled); - static void write(GeneratedCodeUtils &code, const QQmlJSAotEnum &compiled); - static void write(GeneratedCodeUtils &code, const QQmlJSAotVariable &compiled); - static void write(GeneratedCodeUtils &code, const QQmlJSAotProperty &compiled); - static void write(GeneratedCodeUtils &code, const QQmlJSAotMethod &compiled); - static void write(GeneratedCodeUtils &code, const QQmlJSAotSpecialMethod &compiled); - static void write(GeneratedCodeUtils &code, const QQmlJSProgram &compiled); - -private: - static void writeUrl(GeneratedCodeUtils &code, const QQmlJSAotMethod &urlMethod); -}; - -#endif // CODEGENERATORWRITER_H diff --git a/tools/qmltc/prototype/generatedcodeprimitives.h b/tools/qmltc/prototype/generatedcodeprimitives.h deleted file mode 100644 index 46d7b1461f..0000000000 --- a/tools/qmltc/prototype/generatedcodeprimitives.h +++ /dev/null @@ -1,126 +0,0 @@ -/**************************************************************************** -** -** 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 GENERATEDCODEPRIMITIVES_H -#define GENERATEDCODEPRIMITIVES_H - -#include <QtCore/qstring.h> -#include <QtCore/qstack.h> - -// holds generated code for header and implementation files -struct GeneratedCode -{ - QString header; - QString implementation; -}; - -// utility class that provides pretty-printing of the generated code into the -// GeneratedCode buffer -struct GeneratedCodeUtils -{ - GeneratedCode &m_code; // buffer - - QStack<QString> memberNamespaceStack; // member names scopes e.g. MyClass::MySubclass:: - int headerIndent = 0; // header indentation level - int implIndent = 0; // implementation indentation level - - GeneratedCodeUtils(GeneratedCode &code) : m_code(code) { } - - // manages current scope of the generated code, which is necessary for - // implementation file generation. Example: - // class MyClass { MyClass(); }; - in header - // MyClass::MyClass() {} - in implementation file - // MemberNamespaceScope exists to be able to record and use "MyClass::" - struct MemberNamespaceScope - { - GeneratedCodeUtils &m_code; - MemberNamespaceScope(GeneratedCodeUtils &code, const QString &str) : m_code(code) - { - m_code.memberNamespaceStack.push(str); - } - ~MemberNamespaceScope() { m_code.memberNamespaceStack.pop(); } - }; - - // manages current indentation scope: upon creation, increases current - // scope, which is decreased back upon deletion. this is used by append* - // functions that work with GeneratedCode::header to correctly indent the - // input - struct HeaderIndentationScope - { - GeneratedCodeUtils &m_code; - HeaderIndentationScope(GeneratedCodeUtils &code) : m_code(code) { ++m_code.headerIndent; } - ~HeaderIndentationScope() { --m_code.headerIndent; } - }; - - // manages current indentation scope: upon creation, increases current - // scope, which is decreased back upon deletion. this is used by append* - // functions that work with GeneratedCode::implementation to correctly - // indent the input - struct ImplIndentationScope - { - GeneratedCodeUtils &m_code; - ImplIndentationScope(GeneratedCodeUtils &code) : m_code(code) { ++m_code.implIndent; } - ~ImplIndentationScope() { --m_code.implIndent; } - }; - - // appends string \a what with extra indentation \a extraIndent to current - // GeneratedCode::header string - template<typename String> - void appendToHeader(const String &what, int extraIndent = 0) - { - constexpr char16_t newLine[] = u"\n"; - m_code.header += QString((headerIndent + extraIndent) * 4, u' ') + what + newLine; - } - - // appends string \a what with extra indentation \a extraIndent to current - // GeneratedCode::implementation string - template<typename String> - void appendToImpl(const String &what, int extraIndent = 0) - { - constexpr char16_t newLine[] = u"\n"; - m_code.implementation += QString((implIndent + extraIndent) * 4, u' ') + what + newLine; - } - - // appends string \a what with extra indentation \a extraIndent to current - // GeneratedCode::implementation string. this is a special case function - // that expects \a what to be a function signature as \a what is prepended - // with member scope related text. for example, string "foo()" is converted - // to string "MyClass::foo()" before append - template<typename String> - void appendSignatureToImpl(const String &what, int extraIndent = 0) - { - constexpr char16_t newLine[] = u"\n"; - QString signatureScope; - for (const auto &subScope : memberNamespaceStack) - signatureScope += subScope + u"::"; - m_code.implementation += - signatureScope + QString((implIndent + extraIndent) * 4, u' ') + what + newLine; - } -}; - -#endif // GENERATEDCODEPRIMITIVES_H diff --git a/tools/qmltc/prototype/qml2cppcontext.h b/tools/qmltc/prototype/qml2cppcontext.h index 56bb5a12db..3c17a632df 100644 --- a/tools/qmltc/prototype/qml2cppcontext.h +++ b/tools/qmltc/prototype/qml2cppcontext.h @@ -29,8 +29,7 @@ #ifndef QML2CPPCONTEXT_H #define QML2CPPCONTEXT_H -#include "prototype/qmlcompiler.h" -#include "prototype/typeresolver.h" +#include "qmltctyperesolver.h" #include <private/qqmljsdiagnosticmessage_p.h> #include <QtQml/private/qqmlirbuilder_p.h> @@ -41,10 +40,12 @@ #include <variant> #include <functional> +QT_BEGIN_NAMESPACE + struct Qml2CppContext { const QmlIR::Document *document = nullptr; - const Qmltc::TypeResolver *typeResolver = nullptr; + const QmltcTypeResolver *typeResolver = nullptr; QString documentUrl; QQmlJSLogger *logger = nullptr; const QHash<QQmlJSScope::ConstPtr, qsizetype> *typeIndices = nullptr; // TODO: remove this? @@ -52,7 +53,7 @@ struct Qml2CppContext void recordError(const QQmlJS::SourceLocation &location, const QString &message) const { Q_ASSERT(logger); - logger->logCritical(message, Log_Compiler, location); + logger->log(message, Log_Compiler, location); } void recordError(const QV4::CompiledData::Location &location, const QString &message) const @@ -81,7 +82,7 @@ class Qml2CppCompilerPassExecutor QList<Qml2CppCompilerPass> m_passes; public: - Qml2CppCompilerPassExecutor(const QmlIR::Document *doc, const Qmltc::TypeResolver *resolver, + Qml2CppCompilerPassExecutor(const QmlIR::Document *doc, const QmltcTypeResolver *resolver, const QString &url, QList<Qml2CppObject> &objects, const QHash<QQmlJSScope::ConstPtr, qsizetype> &typeIndices) : m_context { doc, resolver, url, nullptr, &typeIndices }, m_objects { objects } @@ -105,4 +106,6 @@ public: } }; +QT_END_NAMESPACE + #endif // QML2CPPCONTEXT_H diff --git a/tools/qmltc/prototype/qml2cppdefaultpasses.cpp b/tools/qmltc/prototype/qml2cppdefaultpasses.cpp index 586cc85754..8372d397d0 100644 --- a/tools/qmltc/prototype/qml2cppdefaultpasses.cpp +++ b/tools/qmltc/prototype/qml2cppdefaultpasses.cpp @@ -35,6 +35,8 @@ #include <algorithm> +QT_BEGIN_NAMESPACE + static QString const cppKeywords[] = { u"alignas"_qs, u"alignof"_qs, @@ -534,7 +536,7 @@ static void setupQmlCppType(const Qml2CppContext &context, const QQmlJSScope::Pt context.recordError(type->sourceLocation(), u"QML type has unknown file path"_qs); return; } - if (!type->fileName().isEmpty()) // consider this one to be already set up + if (type->filePath().endsWith(u".h")) // consider this one to be already set up return; if (!filePath.endsWith(u".qml"_qs)) { context.recordError(type->sourceLocation(), @@ -545,7 +547,7 @@ static void setupQmlCppType(const Qml2CppContext &context, const QQmlJSScope::Pt // TODO: this does not cover QT_QMLTC_FILE_BASENAME renaming if (filePath != context.documentUrl) { // this file name will be discovered during findCppIncludes - type->setFileName(QFileInfo(filePath).baseName().toLower() + u".h"_qs); + type->setFilePath(QFileInfo(filePath).baseName().toLower() + u".h"_qs); } const auto properties = type->ownProperties(); @@ -786,7 +788,7 @@ static void addFirstCppIncludeFromType(QSet<QString> &cppIncludes, auto t = QQmlJSScope::nonCompositeBaseType(type); if (!t) return; - if (QString includeFile = t->fileName(); !includeFile.isEmpty()) + if (QString includeFile = t->filePath(); includeFile.endsWith(u".h")) cppIncludes.insert(includeFile); } @@ -816,7 +818,7 @@ static void populateCppIncludes(QSet<QString> &cppIncludes, const QQmlJSScope::C for (auto t = type; t; t = t->baseType()) { // NB: Composite types might have include files - this is custom qmltc // logic for local imports - if (QString includeFile = t->fileName(); !includeFile.isEmpty()) + if (QString includeFile = t->filePath(); includeFile.endsWith(u".h")) cppIncludes.insert(includeFile); // look in property types @@ -824,8 +826,10 @@ static void populateCppIncludes(QSet<QString> &cppIncludes, const QQmlJSScope::C for (const QQmlJSMetaProperty &p : properties) { addFirstCppIncludeFromType(cppIncludes, p.type()); - if (p.isPrivate()) { - const QString ownersInclude = QQmlJSScope::nonCompositeBaseType(t)->fileName(); + const auto baseType = QQmlJSScope::nonCompositeBaseType(t); + + if (p.isPrivate() && baseType->filePath().endsWith(u".h")) { + const QString ownersInclude = baseType->filePath(); QString privateInclude = constructPrivateInclude(ownersInclude); if (!privateInclude.isEmpty()) cppIncludes.insert(std::move(privateInclude)); @@ -1079,3 +1083,43 @@ QSet<QQmlJSScope::ConstPtr> collectIgnoredTypes(const Qml2CppContext &context, return ignored; } + +static void setDeferred(const Qml2CppContext &context, qsizetype objectIndex, + QList<Qml2CppObject> &objects) +{ + Q_UNUSED(objects); + + Qml2CppObject &o = objects[objectIndex]; + + // c.f. QQmlDeferredAndCustomParserBindingScanner::scanObject() + if (o.irObject->flags & QV4::CompiledData::Object::IsComponent) { + // unlike QmlIR compiler, qmltc should not care about anything within a + // component (let the QQmlComponent wrapper - at runtime anyway - take + // care of this type instead) + return; + } + + const auto setRecursive = [&](QmlIR::Binding &binding) { + if (binding.type >= QmlIR::Binding::Type_Object) + setDeferred(context, binding.value.objectIndex, objects); // Note: recursive call here! + + const QString propName = findPropertyName(context, o.type, binding); + Q_ASSERT(!propName.isEmpty()); + + if (o.type->isNameDeferred(propName)) { + binding.flags |= QV4::CompiledData::Binding::IsDeferredBinding; + o.irObject->flags |= QV4::CompiledData::Object::HasDeferredBindings; + } + }; + + std::for_each(o.irObject->bindingsBegin(), o.irObject->bindingsEnd(), setRecursive); +} + +void setDeferredBindings(const Qml2CppContext &context, QList<Qml2CppObject> &objects) +{ + // as we do not support InlineComponents just yet, we can shortcut the logic + // here to only work with root object + setDeferred(context, 0, objects); +} + +QT_END_NAMESPACE diff --git a/tools/qmltc/prototype/qml2cppdefaultpasses.h b/tools/qmltc/prototype/qml2cppdefaultpasses.h index 29e5acf985..6a1d0ca124 100644 --- a/tools/qmltc/prototype/qml2cppdefaultpasses.h +++ b/tools/qmltc/prototype/qml2cppdefaultpasses.h @@ -31,6 +31,8 @@ #include "prototype/qml2cppcontext.h" +QT_BEGIN_NAMESPACE + // verifies that each object, property (and etc.) has valid // QQmlJSScope::ConstPtr associated with it void verifyTypes(const Qml2CppContext &context, QList<Qml2CppObject> &objects); @@ -86,4 +88,8 @@ findImmediateParents(const Qml2CppContext &context, QList<Qml2CppObject> &object QSet<QQmlJSScope::ConstPtr> collectIgnoredTypes(const Qml2CppContext &context, QList<Qml2CppObject> &objects); +void setDeferredBindings(const Qml2CppContext &context, QList<Qml2CppObject> &objects); + +QT_END_NAMESPACE + #endif // QML2CPPPASSES_H diff --git a/tools/qmltc/prototype/qml2cpppropertyutils.h b/tools/qmltc/prototype/qml2cpppropertyutils.h index 4b3e513b90..31f3f73297 100644 --- a/tools/qmltc/prototype/qml2cpppropertyutils.h +++ b/tools/qmltc/prototype/qml2cpppropertyutils.h @@ -31,6 +31,8 @@ #include <private/qqmljsmetatypes_p.h> +QT_BEGIN_NAMESPACE + inline QString getUnderlyingType(const QQmlJSMetaProperty &p) { QString underlyingType = p.type()->internalName(); @@ -64,4 +66,6 @@ struct Qml2CppPropertyData QString notify; }; +QT_END_NAMESPACE + #endif // QML2CPPPROPERTYUTILS_H diff --git a/tools/qmltc/prototype/qmlcompiler.h b/tools/qmltc/prototype/qmlcompiler.h deleted file mode 100644 index 3c770612cc..0000000000 --- a/tools/qmltc/prototype/qmlcompiler.h +++ /dev/null @@ -1,179 +0,0 @@ -/**************************************************************************** -** -** 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 QMLCOMPILER_H -#define QMLCOMPILER_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. -// - -#include <QtCore/qstring.h> -#include <QtCore/qstringlist.h> - -#include <private/qqmljscompiler_p.h> -#include <private/qqmljsmetatypes_p.h> - -struct Options -{ - QString outputCppFile; - QString outputHFile; - QString moduleUri; - QString resourcePath; - QString outNamespace; - bool debugGenerateLineDirective = false; -}; - -// TODO: rename the classes into Qmltc* pattern - -// Below are the classes that represent a compiled QML types in a string data -// form. These classes should be used to generate C++ code. - -// Represents QML->C++ compiled enumeration type -struct QQmlJSAotEnum -{ - QString cppType; // C++ type of enum - QStringList keys; // enumerator - QStringList values; // enumerator value - QString ownMocLine; // special MOC line that follows enum declaration - - QQmlJSAotEnum() = default; - QQmlJSAotEnum(const QString &t, const QStringList &ks, const QStringList &vs, const QString &l) - : cppType(t), keys(ks), values(vs), ownMocLine(l) - { - } -}; - -// Represents C++ member variable -struct QQmlJSAotVariable -{ - QString cppType; // C++ type of a variable - QString name; // variable name - QString defaultValue; // optional default value - - QQmlJSAotVariable() = default; - QQmlJSAotVariable(const QString &t, const QString &n, const QString &v) - : cppType(t), name(n), defaultValue(v) - { - } -}; - -struct QQmlJSAotProperty : QQmlJSAotVariable -{ - QString containingClass; - QString signalName; - - QQmlJSAotProperty() = default; - QQmlJSAotProperty(const QString t, const QString &n, const QString &c, const QString &s) - : QQmlJSAotVariable(t, n, QString()), containingClass(c), signalName(s) - { - } -}; - -struct QQmlJSAotMethodBase -{ - QString returnType; // C++ return type - QString name; // C++ function name - QList<QQmlJSAotVariable> parameterList; // C++ function parameter list - QStringList body; // C++ code of function body by line - QStringList declPreambles; // e.g. "static" keyword - QStringList modifiers; // e.g. cv-qualifiers, ref-qualifier, noexcept, attributes - - // TODO: these are only needed for Component.onCompleted -- any better way? - QStringList firstLines; // C++ to run at the very beginning of a function - QStringList lastLines; // C++ to run at the very end of a function - - QQmlJSMetaMethod::Access access = QQmlJSMetaMethod::Public; // access specifier -}; - -// Represents QML->C++ compiled member function -struct QQmlJSAotMethod : QQmlJSAotMethodBase -{ - QQmlJSMetaMethod::Type type = QQmlJSMetaMethod::Method; // Qt function type - bool userVisible = false; // tells if a function is prioritized during the output generation -}; - -// Represents C++ special member function -struct QQmlJSAotSpecialMethod : QQmlJSAotMethodBase -{ - QStringList initializerList; // C++ ctor initializer list -}; - -// Represents QML->C++ compiled class type that is used for C++ code generation -struct QQmlJSAotObject -{ - QString cppType; // C++ class name of the QML object - QStringList baseClasses; // C++ class names of base classes - // TODO: also add "creation string"? - QStringList mocCode; - QStringList otherCode; // code that doesn't fit any category, e.g. friend declarations - - // TODO: does it really need to be QHash and not QList? - - // member types: enumerations and child types - QList<QQmlJSAotEnum> enums; - QList<QQmlJSAotObject> children; // these are pretty much always empty - // special member functions - QQmlJSAotSpecialMethod baselineCtor = {}; // does primary initialization - QQmlJSAotMethod init = {}; // begins secondary initialization - QQmlJSAotMethod endInit = {}; // ends initialization (with binding setup) - QQmlJSAotMethod completeComponent = {}; // calls componentComplete() - QQmlJSAotMethod finalizeComponent = {}; // invokes finalizer callbacks - QQmlJSAotMethod handleOnCompleted = {}; // calls Component.onCompleted - QQmlJSAotSpecialMethod externalCtor = {}; // calls baselineCtor, calls init - std::optional<QQmlJSAotSpecialMethod> dtor = {}; - // member functions: methods, signals and slots - QList<QQmlJSAotMethod> functions; - // member variables - QList<QQmlJSAotVariable> variables; - // member properties - QList<QQmlJSAotProperty> properties; - - // TODO: only needed for binding callables - should be revisited - bool ignoreInit = false; // specifies whether init and externalCtor should be ignored -}; - -struct QQmlJSProgram -{ - QList<QQmlJSAotObject> compiledObjects; - QQmlJSAotMethod urlMethod; - QString url; - QString hPath; - QString cppPath; - QString outNamespace; - QSet<QString> includes; -}; - -#endif // QMLCOMPILER_H diff --git a/tools/qmltc/prototype/typeresolver.h b/tools/qmltc/prototype/typeresolver.h deleted file mode 100644 index a382c4c07a..0000000000 --- a/tools/qmltc/prototype/typeresolver.h +++ /dev/null @@ -1,64 +0,0 @@ -/**************************************************************************** -** -** 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 TYPERESOLVER_H -#define TYPERESOLVER_H - -#include "prototype/visitor.h" - -#include <private/qqmljsscope_p.h> -#include <private/qqmljsast_p.h> -#include <private/qqmlirbuilder_p.h> -#include <private/qqmljstyperesolver_p.h> - -namespace Qmltc { -class TypeResolver : public QQmlJSTypeResolver -{ -public: - TypeResolver(QQmlJSImporter *importer); - - void init(Visitor &visitor, QQmlJS::AST::Node *program); - - // TODO: this shouldn't be exposed. instead, all the custom passes on - // QQmlJSScope types must happen inside Visitor - QQmlJSScope::Ptr root() const { return m_root; } - - QQmlJSScope::Ptr scopeForLocation(const QV4::CompiledData::Location &location) const; - - // returns an import pair {url, modifiable type} for a given \a type - QPair<QString, QQmlJSScope::Ptr> importedType(const QQmlJSScope::ConstPtr &type) const; - -private: - QQmlJSImporter *m_importer = nullptr; - - QHash<QV4::CompiledData::Location, QQmlJSScope::Ptr> m_objectsByLocationNonConst; - QQmlJSScope::Ptr m_root; -}; -} - -#endif // TYPERESOLVER_H diff --git a/tools/qmltc/prototype/visitor.cpp b/tools/qmltc/prototype/visitor.cpp deleted file mode 100644 index 307cb992ea..0000000000 --- a/tools/qmltc/prototype/visitor.cpp +++ /dev/null @@ -1,51 +0,0 @@ -/**************************************************************************** -** -** 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 "prototype/visitor.h" - -#include <QtCore/qdir.h> -#include <QtCore/qfileinfo.h> - -namespace Qmltc { -Visitor::Visitor(QQmlJSImporter *importer, QQmlJSLogger *logger, - const QString &implicitImportDirectory, const QStringList &qmltypesFiles) - : QQmlJSImportVisitor(importer, logger, implicitImportDirectory, qmltypesFiles) -{ -} - -bool Visitor::visit(QQmlJS::AST::UiInlineComponent *component) -{ - if (!QQmlJSImportVisitor::visit(component)) - return false; - m_logger->logCritical(u"Inline components are not supported"_qs, Log_Compiler, - component->firstSourceLocation()); - // despite the failure, return true here so that we do not assert in - // QQmlJSImportVisitor::endVisit(UiInlineComponent) - return true; -} -} diff --git a/tools/qmltc/prototype/visitor.h b/tools/qmltc/prototype/visitor.h deleted file mode 100644 index 730021b66c..0000000000 --- a/tools/qmltc/prototype/visitor.h +++ /dev/null @@ -1,55 +0,0 @@ -/**************************************************************************** -** -** 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 VISITOR_H -#define VISITOR_H - -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It exists purely as an -// implementation detail. This header file may change from version to -// version without notice, or even be removed. -// -// We mean it. - -#include <private/qqmljsimportvisitor_p.h> - -namespace Qmltc { -class Visitor : public QQmlJSImportVisitor -{ -public: - Visitor(QQmlJSImporter *importer, QQmlJSLogger *logger, const QString &implicitImportDirectory, - const QStringList &qmltypesFiles = QStringList()); - - bool visit(QQmlJS::AST::UiInlineComponent *) override; -}; -} - -#endif // VISITOR_H diff --git a/tools/qmltc/qmltccodewriter.cpp b/tools/qmltc/qmltccodewriter.cpp index 6c2c018833..67e3c7f06b 100644 --- a/tools/qmltc/qmltccodewriter.cpp +++ b/tools/qmltc/qmltccodewriter.cpp @@ -74,6 +74,20 @@ static QString getFunctionCategory(const QmltcMethod &method) 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<QString, QString> functionSignatures(const QmltcMethodBase &method) { const QString name = method.name; @@ -89,11 +103,18 @@ static std::pair<QString, QString> functionSignatures(const QmltcMethodBase &met 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")"; + const QString headerSignature = name + u"(" + headerParamList.join(u", "_qs) + u")" + + prependSpace(method.modifiers.join(u" ")); + const QString cppSignature = name + u"(" + cppParamList.join(u", "_qs) + u")" + + prependSpace(method.modifiers.join(u" ")); return { headerSignature, cppSignature }; } +static QString functionReturnType(const QmltcMethod &m) +{ + return appendSpace(m.declarationPrefixes.join(u" "_qs)) + m.returnType; +} + void QmltcCodeWriter::writeGlobalHeader(QmltcOutputWrapper &code, const QString &sourcePath, const QString &hPath, const QString &cppPath, const QString &outNamespace, @@ -129,14 +150,18 @@ void QmltcCodeWriter::writeGlobalHeader(QmltcOutputWrapper &code, const QString for (const auto &requiredInclude : requiredCppIncludes) code.rawAppendToHeader(u"#include \"" + requiredInclude + u"\""); code.rawAppendToHeader(u"// END(custom_cpp_includes)"); - code.rawAppendToHeader(u"// qmltc support library:"); - code.rawAppendToHeader(u"#include <private/qqmltcobjectcreationhelper_p.h>"); code.rawAppendToCpp(u"#include \"" + hPath + u"\""); // include own .h file + code.rawAppendToCpp(u"// qmltc support library:"); + code.rawAppendToCpp(u"#include <private/qqmlcppbinding_p.h>"); // QmltcSupportLib + code.rawAppendToCpp(u"#include <private/qqmlcpponassignment_p.h>"); // QmltcSupportLib - code.rawAppendToCpp(u""); // blank line + code.rawAppendToCpp(u"#include <private/qqmlobjectcreator_p.h>"); // createComponent() + code.rawAppendToCpp(u"#include <private/qqmlcomponent_p.h>"); // QQmlComponentPrivate::get() + + code.rawAppendToCpp(u""); code.rawAppendToCpp(u"#include <private/qobject_p.h>"); // NB: for private properties - code.rawAppendToCpp(u"#include <private/qqmlglobal_p.h>"); // QQml_setParent_noEvent() + code.rawAppendToCpp(u"#include <private/qqmlobjectcreator_p.h>"); // for finalize callbacks code.rawAppendToCpp(u""); // blank line code.rawAppendToCpp(u"QT_USE_NAMESPACE // avoid issues with QT_NAMESPACE"); @@ -187,10 +212,6 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProgram &progra { writeGlobalHeader(code, program.url, program.hPath, program.cppPath, program.outNamespace, 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 */"); // url method comes first writeUrl(code, program.urlMethod); @@ -207,12 +228,36 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProgram &progra writeToFile(program.cppPath, code.code().cpp.toUtf8()); } +template<typename Predicate> +static void dumpFunctions(QmltcOutputWrapper &code, const QList<QmltcMethod> &functions, + Predicate pred) +{ + // functions are _ordered_ by access and kind. ordering is important to + // provide consistent output + QMap<QString, QList<const QmltcMethod *>> 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 : qAsConst(it.value())) + QmltcCodeWriter::write(code, *function); + } +} + 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); + 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 "_qs); return str; }; @@ -233,42 +278,70 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type) 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); + // 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 - // enums - if (!type.enums.isEmpty()) { - code.rawAppendToHeader(u""); // blank line - code.rawAppendToHeader(u"public:", -1); + code.rawAppendToHeader(u"/* ----------------- */"); + code.rawAppendToHeader(u"/* External C++ API */"); + code.rawAppendToHeader(u"public:", -1); + + // 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); } + + // dtor + if (type.dtor) + QmltcCodeWriter::write(code, *type.dtor); + + // enums for (const auto &enumeration : qAsConst(type.enums)) - write(code, enumeration); + QmltcCodeWriter::write(code, enumeration); - // child types - if (!type.children.isEmpty()) - code.rawAppendToHeader(u""); // blank line - for (const auto &child : qAsConst(type.children)) - write(code, child); + // visible functions + const auto isUserVisibleFunction = [](const QmltcMethod &function) { + return function.userVisible; + }; + dumpFunctions(code, type.functions, isUserVisibleFunction); - // 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); + code.rawAppendToHeader(u"/* ----------------- */"); + code.rawAppendToHeader(u""); // blank line + code.rawAppendToHeader(u"/* Internal functionality (do NOT use it!) */"); - 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); + // 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.completeComponent); + QmltcCodeWriter::write(code, type.finalizeComponent); + QmltcCodeWriter::write(code, type.handleOnCompleted); + + // code.rawAppendToHeader(u"public:", -1); } + // children + for (const auto &child : qAsConst(type.children)) + QmltcCodeWriter::write(code, child); + + // (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 @@ -314,10 +387,7 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcMethod &method) { const auto [hSignature, cppSignature] = functionSignatures(method); // Note: augment return type with preambles in declaration - QString prefix = method.declarationPrefixes.join(u' '); - if (!prefix.isEmpty()) - prefix.append(u' '); - code.rawAppendToHeader(prefix + method.returnType + u" " + hSignature + u";"); + code.rawAppendToHeader(functionReturnType(method) + u" " + hSignature + u";"); // do not generate method implementation if it is a signal const auto methodType = method.type; @@ -329,39 +399,65 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcMethod &method) { QmltcOutputWrapper::CppIndentationScope cppIndent(&code); Q_UNUSED(cppIndent); + for (const QString &line : qAsConst(method.firstLines)) + code.rawAppendToCpp(line); for (const QString &line : qAsConst(method.body)) code.rawAppendToCpp(line); + for (const QString &line : qAsConst(method.lastLines)) + code.rawAppendToCpp(line); } code.rawAppendToCpp(u"}"); } } -void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcCtor &ctor) +template<typename WriteInitialization> +static void writeSpecialMethod(QmltcOutputWrapper &code, const QmltcMethodBase &specialMethod, + WriteInitialization writeInit) { - const auto [hSignature, cppSignature] = functionSignatures(ctor); - QString prefix = ctor.declarationPrefixes.join(u' '); - if (!prefix.isEmpty()) - prefix.append(u' '); - code.rawAppendToHeader(prefix + hSignature + u";"); + const auto [hSignature, cppSignature] = functionSignatures(specialMethod); + code.rawAppendToHeader(hSignature + u";"); code.rawAppendToCpp(u""); // blank line 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); - } + + writeInit(specialMethod); + code.rawAppendToCpp(u"{"); { QmltcOutputWrapper::CppIndentationScope cppIndent(&code); Q_UNUSED(cppIndent); - for (const QString &line : qAsConst(ctor.body)) + for (const QString &line : qAsConst(specialMethod.firstLines)) + code.rawAppendToCpp(line); + for (const QString &line : qAsConst(specialMethod.body)) + code.rawAppendToCpp(line); + for (const QString &line : qAsConst(specialMethod.lastLines)) code.rawAppendToCpp(line); } code.rawAppendToCpp(u"}"); } +void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcCtor &ctor) +{ + const auto writeInitializerList = [&](const QmltcMethodBase &ctorBase) { + auto ctor = static_cast<const QmltcCtor &>(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" "_qs.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""_qs : u" = " + var.defaultValue; @@ -370,7 +466,7 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcVariable &var) void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProperty &prop) { - Q_ASSERT(prop.defaultValue.isEmpty()); // we don't support it yet + 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)"_qs.arg( prop.containingClass, prop.cppType, prop.name, prop.signalName)); } @@ -382,14 +478,12 @@ void QmltcCodeWriter::writeUrl(QmltcOutputWrapper &code, const QmltcMethod &urlM const auto [hSignature, _] = functionSignatures(urlMethod); Q_UNUSED(_); // Note: augment return type with preambles in declaration - QString prefix = urlMethod.declarationPrefixes.join(u' '); - if (!prefix.isEmpty()) - prefix.append(u' '); - code.rawAppendToCpp(prefix + urlMethod.returnType + u" " + hSignature); + code.rawAppendToCpp(functionReturnType(urlMethod) + hSignature); code.rawAppendToCpp(u"{"); { QmltcOutputWrapper::CppIndentationScope cppIndent(&code); Q_UNUSED(cppIndent); + Q_ASSERT(urlMethod.firstLines.isEmpty() && urlMethod.lastLines.isEmpty()); for (const QString &line : qAsConst(urlMethod.body)) code.rawAppendToCpp(line); } diff --git a/tools/qmltc/qmltccodewriter.h b/tools/qmltc/qmltccodewriter.h index 1d1e023d43..824a3f97db 100644 --- a/tools/qmltc/qmltccodewriter.h +++ b/tools/qmltc/qmltccodewriter.h @@ -49,9 +49,11 @@ struct QmltcCodeWriter 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 QmltcDtor &dtor); static void write(QmltcOutputWrapper &code, const QmltcVariable &var); static void write(QmltcOutputWrapper &code, const QmltcProperty &prop); +private: static void writeUrl(QmltcOutputWrapper &code, const QmltcMethod &urlMethod); // special }; diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp index 6510a6d51d..2b69a81b85 100644 --- a/tools/qmltc/qmltccompiler.cpp +++ b/tools/qmltc/qmltccompiler.cpp @@ -115,7 +115,7 @@ void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr current.baseClasses = { baseClass }; if (!documentRoot) { - // make document root a friend to allow it to access init and finalize + // make document root a friend to allow it to access init and endInit current.otherCode << u"friend class %1;"_qs.arg(rootType->internalName()); } else { // make QQmltcObjectCreationBase<DocumentRoot> a friend to allow it to @@ -147,68 +147,70 @@ void QmltcCompiler::compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr }; // add special member functions - current.basicCtor.access = QQmlJSMetaMethod::Protected; + current.baselineCtor.access = QQmlJSMetaMethod::Protected; current.init.access = QQmlJSMetaMethod::Protected; - current.finalize.access = QQmlJSMetaMethod::Protected; - current.fullCtor.access = QQmlJSMetaMethod::Public; + current.endInit.access = QQmlJSMetaMethod::Protected; + current.externalCtor.access = QQmlJSMetaMethod::Public; - current.basicCtor.name = current.cppType; - current.fullCtor.name = current.cppType; + current.baselineCtor.name = current.cppType; + current.externalCtor.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; + current.endInit.name = u"qmltc_finalize"_qs; + current.endInit.returnType = u"void"_qs; QmltcVariable creator(u"QQmltcObjectCreationHelper*"_qs, u"creator"_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.baselineCtor.parameterList = { parent }; QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_qs, u"parentContext"_qs); QmltcVariable finalizeFlag(u"bool"_qs, u"canFinalize"_qs); if (documentRoot) { - current.fullCtor.parameterList = { engine, parent }; + current.externalCtor.parameterList = { engine, parent }; current.init.parameterList = { creator, engine, ctxtdata, finalizeFlag }; - current.finalize.parameterList = { creator, engine, finalizeFlag }; + current.endInit.parameterList = { creator, engine, finalizeFlag }; } else { - current.fullCtor.parameterList = { creator, engine, parent }; + current.externalCtor.parameterList = { creator, engine, parent }; current.init.parameterList = { creator, engine, ctxtdata }; - current.finalize.parameterList = { creator, engine }; + current.endInit.parameterList = { creator, engine }; } - current.fullCtor.initializerList = { current.basicCtor.name + u"(" + parent.name + u")" }; + current.externalCtor.initializerList = { current.baselineCtor.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")" }; + current.baselineCtor.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");"; + current.baselineCtor.body << u"QQml_setParent_noEvent(this, " + parent.name + u");"; } QmltcCodeGenerator generator { rootType }; // compilation stub: - current.fullCtor.body << u"Q_UNUSED(engine);"_qs; - current.finalize.body << u"Q_UNUSED(engine);"_qs; - current.finalize.body << u"Q_UNUSED(creator);"_qs; + current.externalCtor.body << u"Q_UNUSED(engine);"_qs; + current.endInit.body << u"Q_UNUSED(engine);"_qs; + current.endInit.body << u"Q_UNUSED(creator);"_qs; if (documentRoot) { - current.fullCtor.body << u"// document root:"_qs; + current.externalCtor.body << u"// document root:"_qs; // if it's document root, we want to create our QQmltcObjectCreationBase // that would store all the created objects - current.fullCtor.body << u"QQmltcObjectCreationBase<%1> objectHolder;"_qs.arg( + current.externalCtor.body << u"QQmltcObjectCreationBase<%1> objectHolder;"_qs.arg( type->internalName()); - current.fullCtor.body << u"QQmltcObjectCreationHelper creator = objectHolder.view();"_qs; + current.externalCtor.body + << u"QQmltcObjectCreationHelper creator = objectHolder.view();"_qs; // now call init - current.fullCtor.body << current.init.name + current.externalCtor.body << current.init.name + u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* " - u"finalize */ true);"; + u"endInit */ true);"; - current.finalize.body << u"Q_UNUSED(canFinalize);"_qs; + current.endInit.body << u"Q_UNUSED(canFinalize);"_qs; } else { - current.fullCtor.body << u"// not document root:"_qs; + current.externalCtor.body << u"// not document root:"_qs; // just call init, we don't do any setup here otherwise - current.fullCtor.body << current.init.name + current.externalCtor.body << current.init.name + u"(creator, engine, QQmlData::get(parent)->outerContext);"; } @@ -353,9 +355,9 @@ void QmltcCompiler::compileProperty(QmltcType ¤t, const QQmlJSMetaProperty 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")))"); + current.baselineCtor.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 @@ -440,18 +442,18 @@ void QmltcCompiler::compileBinding(QmltcType ¤t, const QQmlJSMetaPropertyB switch (binding.bindingType()) { case QQmlJSMetaPropertyBinding::BoolLiteral: { - const bool value = binding.literalValue().toBool(); + const bool value = binding.boolValue(); generator.generate_assignToProperty(current, type, p, value ? u"true"_qs : u"false"_qs, accessor.name); break; } case QQmlJSMetaPropertyBinding::NumberLiteral: { - const QString value = QString::number(binding.literalValue().toDouble()); + const QString value = QString::number(binding.numberValue()); generator.generate_assignToProperty(current, type, p, value, accessor.name); break; } case QQmlJSMetaPropertyBinding::StringLiteral: { - const QString value = binding.literalValue().toString(); + const QString value = binding.stringValue(); generator.generate_assignToProperty(current, type, p, QQmlJSUtils::toLiteral(value), accessor.name); break; @@ -486,8 +488,8 @@ void QmltcCompiler::compileBinding(QmltcType ¤t, const QQmlJSMetaPropertyB break; } default: { - m_logger->logWarning(u"Binding type is not supported (yet)"_qs, Log_Compiler, - binding.sourceLocation()); + m_logger->log(u"Binding type is not supported (yet)"_qs, Log_Compiler, + binding.sourceLocation()); break; } } diff --git a/tools/qmltc/qmltccompiler.h b/tools/qmltc/qmltccompiler.h index 0574b03088..589c70bafe 100644 --- a/tools/qmltc/qmltccompiler.h +++ b/tools/qmltc/qmltccompiler.h @@ -91,13 +91,13 @@ private: void compileBinding(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor); - bool hasErrors() const { return m_logger->hasErrors() || m_logger->hasWarnings(); } + bool hasErrors() const { return m_logger->hasErrors(); } 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); + m_logger->log(message, category, location); } void recordError(const QV4::CompiledData::Location &location, const QString &message, QQmlJSLoggerCategory category = Log_Compiler) diff --git a/tools/qmltc/qmltccompilerpieces.h b/tools/qmltc/qmltccompilerpieces.h index e0c587b5f3..d78be5a652 100644 --- a/tools/qmltc/qmltccompilerpieces.h +++ b/tools/qmltc/qmltccompilerpieces.h @@ -147,7 +147,7 @@ QmltcCodeGenerator::generate_qmlContextSetup(QmltcType ¤t, const QQmlJSSco current.init.body << u"// 4. call finalize in the document root"_qs; current.init.body << u"if (canFinalize) {"_qs; current.init.body << QStringLiteral(" %1(creator, engine, /* finalize */ true);") - .arg(current.finalize.name); + .arg(current.endInit.name); current.init.body << u"}"_qs; } current.init.body << u"return context;"_qs; diff --git a/tools/qmltc/qmltcoutputir.h b/tools/qmltc/qmltcoutputir.h index 9d000cbc11..0a3e7617bd 100644 --- a/tools/qmltc/qmltcoutputir.h +++ b/tools/qmltc/qmltcoutputir.h @@ -92,6 +92,12 @@ struct QmltcMethodBase QStringList body; // C++ function code QQmlJSMetaMethod::Access access = QQmlJSMetaMethod::Public; // access specifier QStringList declarationPrefixes; + QStringList modifiers; // cv-qualifiers, ref-qualifier, noexcept, attributes + + // TODO: these are only needed for Component.onCompleted/onDestruction. this + // has to be re-written anyhow later + QStringList firstLines; // C++ to run at the very beginning of a function + QStringList lastLines; // C++ to run at the very end of a function }; // Represents QML -> C++ compiled function @@ -99,6 +105,9 @@ struct QmltcMethod : QmltcMethodBase { QString returnType; // C++ return type QQmlJSMetaMethod::Type type = QQmlJSMetaMethod::Method; // Qt function type + + // TODO: should be a better way to handle this + bool userVisible = false; // tells if a function is prioritized during the output generation }; // Represents C++ ctor of a type @@ -107,6 +116,11 @@ struct QmltcCtor : QmltcMethodBase QStringList initializerList; // C++ ctor's initializer list }; +// Represents C++ dtor of a type +struct QmltcDtor : QmltcMethodBase +{ +}; + // Represents QML -> C++ compiled type struct QmltcType { @@ -120,10 +134,15 @@ struct QmltcType 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.) + QmltcCtor baselineCtor {}; // does basic contruction + QmltcCtor externalCtor {}; // calls basicCtor, calls init + QmltcMethod init {}; // starts object initialization (context setup), calls finalize + QmltcMethod endInit {}; // ends object initialization (with binding setup) + QmltcMethod completeComponent {}; // calls componentComplete() + QmltcMethod finalizeComponent {}; // calls componentFinalized() + QmltcMethod handleOnCompleted {}; // calls Component.onCompleted + + std::optional<QmltcDtor> dtor {}; // member functions: methods, signals and slots QList<QmltcMethod> functions; @@ -133,6 +152,9 @@ struct QmltcType // QML document root specific: std::optional<QmltcVariable> typeCount; // the number of QML types defined in a document + + // TODO: only needed for binding callables - should not be needed, generally + bool ignoreInit = false; // specifies whether init and externalCtor should be ignored }; // Represents whole QML program, compiled to C++ diff --git a/tools/qmltc/prototype/typeresolver.cpp b/tools/qmltc/qmltctyperesolver.cpp index 377787830e..2d46cf38b3 100644 --- a/tools/qmltc/prototype/typeresolver.cpp +++ b/tools/qmltc/qmltctyperesolver.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2021 The Qt Company Ltd. +** Copyright (C) 2022 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the tools applications of the Qt Toolkit. @@ -26,8 +26,7 @@ ** ****************************************************************************/ -#include "prototype/typeresolver.h" -#include "prototype/visitor.h" +#include "qmltctyperesolver.h" #include <private/qqmljsimporter_p.h> #include <private/qv4value_p.h> @@ -37,17 +36,9 @@ #include <QtCore/qfileinfo.h> #include <QtCore/qdiriterator.h> -Q_LOGGING_CATEGORY(lcTypeResolver2, "qml.compiler.typeresolver", QtInfoMsg); +Q_LOGGING_CATEGORY(lcTypeResolver2, "qml.qmltc.typeresolver", QtInfoMsg); -namespace Qmltc { - -TypeResolver::TypeResolver(QQmlJSImporter *importer) - : QQmlJSTypeResolver(importer), m_importer(importer) -{ - Q_ASSERT(m_importer); -} - -void TypeResolver::init(Visitor &visitor, QQmlJS::AST::Node *program) +void QmltcTypeResolver::init(QmltcVisitor &visitor, QQmlJS::AST::Node *program) { QQmlJSTypeResolver::init(&visitor, program); m_root = visitor.result(); @@ -67,14 +58,16 @@ void TypeResolver::init(Visitor &visitor, QQmlJS::AST::Node *program) } } -QQmlJSScope::Ptr TypeResolver::scopeForLocation(const QV4::CompiledData::Location &location) const +QQmlJSScope::Ptr +QmltcTypeResolver::scopeForLocation(const QV4::CompiledData::Location &location) const { qCDebug(lcTypeResolver2()).nospace() << "looking for object at " << location.line << ':' << location.column; return m_objectsByLocationNonConst.value(location); } -QPair<QString, QQmlJSScope::Ptr> TypeResolver::importedType(const QQmlJSScope::ConstPtr &type) const +QPair<QString, QQmlJSScope::Ptr> +QmltcTypeResolver::importedType(const QQmlJSScope::ConstPtr &type) const { const auto files = m_importer->importedFiles(); auto it = std::find_if(files.cbegin(), files.cend(), [&](const QQmlJSScope::Ptr &importedType) { @@ -84,4 +77,3 @@ QPair<QString, QQmlJSScope::Ptr> TypeResolver::importedType(const QQmlJSScope::C return {}; return { it.key(), it.value() }; } -} diff --git a/tools/qmltc/qmltctyperesolver.h b/tools/qmltc/qmltctyperesolver.h index 495f78b81d..ae9e98c23e 100644 --- a/tools/qmltc/qmltctyperesolver.h +++ b/tools/qmltc/qmltctyperesolver.h @@ -29,6 +29,8 @@ #ifndef QMLTCTYPERESOLVER_H #define QMLTCTYPERESOLVER_H +#include "qmltcvisitor.h" + #include <QtQml/private/qqmlirbuilder_p.h> #include <private/qqmljstyperesolver_p.h> #include <private/qqmljsimporter_p.h> @@ -39,7 +41,27 @@ QT_BEGIN_NAMESPACE class QmltcTypeResolver : public QQmlJSTypeResolver { public: - QmltcTypeResolver(QQmlJSImporter *importer) : QQmlJSTypeResolver(importer) {} + QmltcTypeResolver(QQmlJSImporter *importer) : QQmlJSTypeResolver(importer), m_importer(importer) + { + Q_ASSERT(importer); + } + + void init(QmltcVisitor &visitor, QQmlJS::AST::Node *program); + + // TODO: this shouldn't be exposed. instead, all the custom passes on + // QQmlJSScope types must happen inside Visitor + QQmlJSScope::Ptr root() const { return m_root; } + + QQmlJSScope::Ptr scopeForLocation(const QV4::CompiledData::Location &location) const; + + // returns an import pair {url, modifiable type} for a given \a type + QPair<QString, QQmlJSScope::Ptr> importedType(const QQmlJSScope::ConstPtr &type) const; + +private: + QQmlJSImporter *m_importer = nullptr; + + QHash<QV4::CompiledData::Location, QQmlJSScope::Ptr> m_objectsByLocationNonConst; + QQmlJSScope::Ptr m_root; }; QT_END_NAMESPACE diff --git a/tools/qmltc/qmltcvisitor.cpp b/tools/qmltc/qmltcvisitor.cpp index 691496d7c7..b70e3fe329 100644 --- a/tools/qmltc/qmltcvisitor.cpp +++ b/tools/qmltc/qmltcvisitor.cpp @@ -71,8 +71,8 @@ void QmltcVisitor::findCppIncludes() if (t != type && visitType(t)) return; - QString includeFile = t->fileName(); - if (!includeFile.isEmpty()) + QString includeFile = t->filePath(); + if (includeFile.endsWith(u".h")) m_cppIncludes.insert(std::move(includeFile)); }; @@ -97,7 +97,7 @@ void QmltcVisitor::findCppIncludes() if (visitType(t)) break; // look in type - if (auto includeFile = t->fileName(); !includeFile.isEmpty()) + if (auto includeFile = t->filePath(); includeFile.endsWith(u".h")) m_cppIncludes.insert(std::move(includeFile)); // look in properties @@ -105,8 +105,8 @@ void QmltcVisitor::findCppIncludes() for (const QQmlJSMetaProperty &p : properties) { populateFromType(p.type()); - if (p.isPrivate()) { - const QString ownersInclude = t->fileName(); + if (p.isPrivate() && t->filePath().endsWith(u".h")) { + const QString ownersInclude = t->filePath(); QString privateInclude = constructPrivateInclude(ownersInclude); if (!privateInclude.isEmpty()) m_cppIncludes.insert(std::move(privateInclude)); @@ -187,37 +187,45 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiPublicMember *publicMember) // 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); - if (prop.isWritable()) - prop.setWrite(u"set" + nameWithUppercase); - prop.setBindable(u"bindable" + nameWithUppercase); - prop.setNotify(name + u"Changed"); + + // TODO: we should set the composite type property methods here, but as + // of now this is done in the pass over the types after the ast + // traversal + + const QString notifyName = name + u"Changed"_qs; // also check that notify is already a method of m_currentScope { - const auto methods = m_currentScope->ownMethods(prop.notify()); + const auto methods = m_currentScope->ownMethods(notifyName); if (methods.size() != 1) { const QString errorString = methods.isEmpty() ? u"no signal"_qs : u"too many signals"_qs; - m_logger->logCritical( + m_logger->log( 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); + m_logger->log(u"internal error: method %1 of property %2 must be a signal"_qs.arg( + notifyName, name), + Log_Compiler, publicMember->identifierToken); return false; } } - m_currentScope->addOwnProperty(prop); } return true; } +bool QmltcVisitor::visit(QQmlJS::AST::UiInlineComponent *component) +{ + if (!QQmlJSImportVisitor::visit(component)) + return false; + m_logger->log(u"Inline components are not supported"_qs, Log_Compiler, + component->firstSourceLocation()); + // despite the failure, return true here so that we do not assert in + // QQmlJSImportVisitor::endVisit(UiInlineComponent) + 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 d69c84d3ca..a543a4271c 100644 --- a/tools/qmltc/qmltcvisitor.h +++ b/tools/qmltc/qmltcvisitor.h @@ -56,10 +56,10 @@ public: bool visit(QQmlJS::AST::UiPublicMember *) override; + bool visit(QQmlJS::AST::UiInlineComponent *) override; + void endVisit(QQmlJS::AST::UiProgram *) override; - // NB: overwrite result() method to return ConstPtr - QQmlJSScope::ConstPtr result() const { return QQmlJSImportVisitor::result(); } QList<QQmlJSScope::ConstPtr> qmlScopesWithQmlBases() const { return m_qmlTypesWithQmlBases; } QSet<QString> cppIncludeFiles() const { return m_cppIncludes; } |