diff options
Diffstat (limited to 'tools/qmltc')
-rw-r--r-- | tools/qmltc/CMakeLists.txt | 2 | ||||
-rw-r--r-- | tools/qmltc/main.cpp | 55 | ||||
-rw-r--r-- | tools/qmltc/qmltccodewriter.cpp | 99 | ||||
-rw-r--r-- | tools/qmltc/qmltccodewriter.h | 4 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.cpp | 397 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.h | 8 | ||||
-rw-r--r-- | tools/qmltc/qmltccompilerpieces.cpp | 17 | ||||
-rw-r--r-- | tools/qmltc/qmltccompilerpieces.h | 27 | ||||
-rw-r--r-- | tools/qmltc/qmltcoutputir.h | 46 | ||||
-rw-r--r-- | tools/qmltc/qmltcpropertyutils.h | 11 | ||||
-rw-r--r-- | tools/qmltc/qmltctyperesolver.cpp | 5 | ||||
-rw-r--r-- | tools/qmltc/qmltcvisitor.cpp | 79 | ||||
-rw-r--r-- | tools/qmltc/qmltcvisitor.h | 8 |
13 files changed, 613 insertions, 145 deletions
diff --git a/tools/qmltc/CMakeLists.txt b/tools/qmltc/CMakeLists.txt index 8e7ce0a586..42e47e24da 100644 --- a/tools/qmltc/CMakeLists.txt +++ b/tools/qmltc/CMakeLists.txt @@ -3,7 +3,7 @@ qt_get_tool_target_name(target_name qmltc) qt_internal_add_tool(${target_name} - TARGET_DESCRIPTION "QML Type Compiler" + TARGET_DESCRIPTION "QML type compiler" TOOLS_TARGET Qml SOURCES main.cpp diff --git a/tools/qmltc/main.cpp b/tools/qmltc/main.cpp index 6ef0afe105..1899f4087e 100644 --- a/tools/qmltc/main.cpp +++ b/tools/qmltc/main.cpp @@ -9,6 +9,7 @@ #include <private/qqmljscompiler_p.h> #include <private/qqmljsresourcefilemapper_p.h> +#include <private/qqmljsutils_p.h> #include <QtCore/qcoreapplication.h> #include <QtCore/qurl.h> @@ -24,6 +25,8 @@ #include <QtQml/private/qqmljsastvisitor_p.h> #include <QtQml/private/qqmljsast_p.h> #include <QtQml/private/qqmljsdiagnosticmessage_p.h> +#include <QtQmlCompiler/qqmlsa.h> +#include <QtQmlCompiler/private/qqmljsliteralbindingcheck_p.h> #include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE @@ -31,8 +34,8 @@ using namespace Qt::StringLiterals; void setupLogger(QQmlJSLogger &logger) // prepare logger to work with compiler { - for (const QQmlJSLogger::Category &category : logger.categories()) { - if (category == qmlUnusedImports) + for (const QQmlJS::LoggerCategory &category : logger.categories()) { + if (category.id() == qmlUnusedImports) continue; logger.setCategoryLevel(category.id(), QtCriticalMsg); logger.setCategoryIgnored(category.id(), false); @@ -99,6 +102,25 @@ int main(int argc, char **argv) QCoreApplication::translate("main", "namespace") }; parser.addOption(namespaceOption); + QCommandLineOption moduleOption{ + u"module"_s, + QCoreApplication::translate("main", + "Name of the QML module that this QML code belongs to."), + QCoreApplication::translate("main", "module") + }; + parser.addOption(moduleOption); + QCommandLineOption exportOption{ u"export"_s, + QCoreApplication::translate( + "main", "Export macro used in the generated C++ code"), + QCoreApplication::translate("main", "export") }; + parser.addOption(exportOption); + QCommandLineOption exportIncludeOption{ + u"exportInclude"_s, + QCoreApplication::translate( + "main", "Header defining the export macro to be used in the generated C++ code"), + QCoreApplication::translate("main", "exportInclude") + }; + parser.addOption(exportIncludeOption); parser.process(app); @@ -153,7 +175,7 @@ int main(int argc, char **argv) if (!parser.isSet(bareOption)) importPaths.append(QLibraryInfo::path(QLibraryInfo::QmlImportsPath)); - QStringList qmldirFiles = parser.values(qmldirOption); + QStringList qmldirFiles = QQmlJSUtils::cleanPaths(parser.values(qmldirOption)); QString outputCppFile; if (!parser.isSet(outputCppOption)) { @@ -224,6 +246,8 @@ int main(int argc, char **argv) info.outputHFile = parser.value(outputHOption); info.resourcePath = firstQml(paths); info.outputNamespace = parser.value(namespaceOption); + info.exportMacro = parser.value(exportOption); + info.exportInclude = parser.value(exportIncludeOption); if (info.outputCppFile.isEmpty()) { fprintf(stderr, "An output C++ file is required. Pass one using --impl"); @@ -236,24 +260,37 @@ int main(int argc, char **argv) QQmlJSImporter importer { importPaths, &mapper }; importer.setMetaDataMapper(&metaDataMapper); - auto createQmltcVisitor = [](const QQmlJSScope::Ptr &root, QQmlJSImporter *importer, - QQmlJSLogger *logger, const QString &implicitImportDirectory, - const QStringList &qmldirFiles) -> QQmlJSImportVisitor * { - return new QmltcVisitor(root, importer, logger, implicitImportDirectory, qmldirFiles); + auto qmltcVisitor = [](QQmlJS::AST::Node *rootNode, QQmlJSImporter *self, + const QQmlJSImporter::ImportVisitorPrerequisites &p) { + QmltcVisitor v(p.m_target, self, p.m_logger, p.m_implicitImportDirectory, p.m_qmldirFiles); + QQmlJS::AST::Node::accept(rootNode, &v); }; - importer.setImportVisitorCreator(createQmltcVisitor); + importer.setImportVisitor(qmltcVisitor); QQmlJSLogger logger; logger.setFileName(url); logger.setCode(sourceCode); setupLogger(logger); - QmltcVisitor visitor(QQmlJSScope::create(), &importer, &logger, + auto currentScope = QQmlJSScope::create(); + if (parser.isSet(moduleOption)) + currentScope->setOwnModuleName(parser.value(moduleOption)); + + QmltcVisitor visitor(currentScope, &importer, &logger, QQmlJSImportVisitor::implicitImportDirectory(url, &mapper), qmldirFiles); visitor.setMode(QmltcVisitor::Compile); QmltcTypeResolver typeResolver { &importer }; typeResolver.init(&visitor, qmlParser.rootNode()); + using PassManagerPtr = + std::unique_ptr<QQmlSA::PassManager, + decltype(&QQmlSA::PassManagerPrivate::deletePassManager)>; + PassManagerPtr passMan(QQmlSA::PassManagerPrivate::createPassManager(&visitor, &typeResolver), + &QQmlSA::PassManagerPrivate::deletePassManager); + passMan->registerPropertyPass(std::make_unique<QQmlJSLiteralBindingCheck>(passMan.get()), + QString(), QString(), QString()); + passMan->analyze(QQmlJSScope::createQQmlSAElement(visitor.result())); + if (logger.hasErrors()) return EXIT_FAILURE; diff --git a/tools/qmltc/qmltccodewriter.cpp b/tools/qmltc/qmltccodewriter.cpp index 8010f1066c..134de0f98e 100644 --- a/tools/qmltc/qmltccodewriter.cpp +++ b/tools/qmltc/qmltccodewriter.cpp @@ -42,14 +42,14 @@ static QString getFunctionCategory(const QmltcMethod &method) { QString category = getFunctionCategory(static_cast<const QmltcMethodBase &>(method)); switch (method.type) { - case QQmlJSMetaMethod::Signal: + case QQmlJSMetaMethodType::Signal: category = u"Q_SIGNALS"_s; break; - case QQmlJSMetaMethod::Slot: + case QQmlJSMetaMethodType::Slot: category += u" Q_SLOTS"_s; break; - case QQmlJSMetaMethod::Method: - case QQmlJSMetaMethod::StaticMethod: + case QQmlJSMetaMethodType::Method: + case QQmlJSMetaMethodType::StaticMethod: break; } return category; @@ -118,6 +118,7 @@ void QmltcCodeWriter::writeGlobalHeader(QmltcOutputWrapper &code, const QString code.rawAppendToHeader(u"#include <QtCore/qproperty.h>"); code.rawAppendToHeader(u"#include <QtCore/qobject.h>"); code.rawAppendToHeader(u"#include <QtCore/qcoreapplication.h>"); + code.rawAppendToHeader(u"#include <QtCore/qxpfunctional.h>"); code.rawAppendToHeader(u"#include <QtQml/qqmlengine.h>"); code.rawAppendToHeader(u"#include <QtCore/qurl.h>"); // used in engine execution code.rawAppendToHeader(u"#include <QtQml/qqml.h>"); // used for attached properties @@ -160,6 +161,64 @@ void QmltcCodeWriter::writeGlobalHeader(QmltcOutputWrapper &code, const QString } } +void QmltcCodeWriter::write(QmltcOutputWrapper &code, + const QmltcPropertyInitializer &propertyInitializer, + const QmltcType &wrappedType) +{ + code.rawAppendToHeader(u"class " + propertyInitializer.name + u" {"); + + { + { + [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code); + + code.rawAppendToHeader(u"friend class " + wrappedType.cppType + u";"); + } + + code.rawAppendToHeader(u"public:"_s); + + [[maybe_unused]] QmltcOutputWrapper::MemberNameScope typeScope(&code, propertyInitializer.name); + { + [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code); + + write(code, propertyInitializer.constructor); + code.rawAppendToHeader(u""); // blank line + + for (const auto &propertySetter : propertyInitializer.propertySetters) { + write(code, propertySetter); + } + } + + code.rawAppendToHeader(u""); // blank line + code.rawAppendToHeader(u"private:"_s); + + { + [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code); + + write(code, propertyInitializer.component); + write(code, propertyInitializer.initializedCache); + } + } + + code.rawAppendToHeader(u"};"_s); + code.rawAppendToHeader(u""); // blank line +} + +void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcRequiredPropertiesBundle &requiredPropertiesBundle) +{ + code.rawAppendToHeader(u"struct " + requiredPropertiesBundle.name + u" {"); + + { + [[maybe_unused]] QmltcOutputWrapper::HeaderIndentationScope headerIndent(&code); + + for (const auto &member : requiredPropertiesBundle.members) { + write(code, member); + } + } + + code.rawAppendToHeader(u"};"_s); + code.rawAppendToHeader(u""); // blank line +} + void QmltcCodeWriter::writeGlobalFooter(QmltcOutputWrapper &code, const QString &sourcePath, const QString &outNamespace) { @@ -187,12 +246,14 @@ static void writeToFile(const QString &path, const QByteArray &data) QFileInfo fi(path); if (fi.exists() && fi.size() == data.size()) { QFile oldFile(path); - oldFile.open(QIODevice::ReadOnly); - if (oldFile.readAll() == data) - return; + if (oldFile.open(QIODevice::ReadOnly)) { + if (oldFile.readAll() == data) + return; + } } QFile file(path); - file.open(QIODevice::WriteOnly); + if (!file.open(QIODevice::WriteOnly)) + qFatal("Could not open file %s", qPrintable(path)); file.write(data); } @@ -209,7 +270,7 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProgram &progra code.rawAppendToHeader(u"class " + type.cppType + u";"); // write all the types and their content for (const QmltcType &type : std::as_const(program.compiledTypes)) - write(code, type); + write(code, type, program.exportMacro); // add typeCount definitions. after all types have been written down (so // they are now complete types as per C++). practically, this only concerns @@ -251,10 +312,14 @@ static void dumpFunctions(QmltcOutputWrapper &code, const QList<QmltcMethod> &fu } } -void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type) +void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type, + const QString &exportMacro) { const auto constructClassString = [&]() { - QString str = u"class " + type.cppType; + QString str = u"class "_s; + if (!exportMacro.isEmpty()) + str.append(exportMacro).append(u" "_s); + str.append(type.cppType); QStringList nonEmptyBaseClasses; nonEmptyBaseClasses.reserve(type.baseClasses.size()); std::copy_if(type.baseClasses.cbegin(), type.baseClasses.cend(), @@ -287,6 +352,12 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type) code.rawAppendToHeader(u"/* External C++ API */"); code.rawAppendToHeader(u"public:", -1); + if (!type.propertyInitializer.name.isEmpty()) + write(code, type.propertyInitializer, type); + + if (type.requiredPropertiesBundle) + write(code, *type.requiredPropertiesBundle); + // NB: when non-document root, the externalCtor won't be public - but we // really don't care about the output format of such types if (!type.ignoreInit && type.externalCtor.access == QQmlJSMetaMethod::Public) { @@ -340,7 +411,7 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcType &type) // children for (const auto &child : std::as_const(type.children)) - QmltcCodeWriter::write(code, child); + QmltcCodeWriter::write(code, child, exportMacro); // (non-visible) functions dumpFunctions(code, type.functions, std::not_fn(isUserVisibleFunction)); @@ -392,14 +463,14 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcMethod &method) { const auto [hSignature, cppSignature] = functionSignatures(method); // Note: augment return type with preambles in declaration - code.rawAppendToHeader((method.type == QQmlJSMetaMethod::StaticMethod + code.rawAppendToHeader((method.type == QQmlJSMetaMethodType::StaticMethod ? u"static " + functionReturnType(method) : functionReturnType(method)) + u" " + hSignature + u";"); // do not generate method implementation if it is a signal const auto methodType = method.type; - if (methodType != QQmlJSMetaMethod::Signal) { + if (methodType != QQmlJSMetaMethodType::Signal) { code.rawAppendToCpp(u""_s); // blank line if (method.comments.size() > 0) { code.rawAppendToCpp(u"/*! \\internal"_s); diff --git a/tools/qmltc/qmltccodewriter.h b/tools/qmltc/qmltccodewriter.h index e95777998e..20b0262737 100644 --- a/tools/qmltc/qmltccodewriter.h +++ b/tools/qmltc/qmltccodewriter.h @@ -20,13 +20,15 @@ struct QmltcCodeWriter static void writeGlobalFooter(QmltcOutputWrapper &code, const QString &sourcePath, const QString &outNamespace); static void write(QmltcOutputWrapper &code, const QmltcProgram &program); - static void write(QmltcOutputWrapper &code, const QmltcType &type); + static void write(QmltcOutputWrapper &code, const QmltcType &type, const QString &exportMacro); 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); + static void write(QmltcOutputWrapper &code, const QmltcPropertyInitializer &propertyInitializer, const QmltcType& wrappedType); + static void write(QmltcOutputWrapper &code, const QmltcRequiredPropertiesBundle &requiredPropertiesBundle); private: static void writeUrl(QmltcOutputWrapper &code, const QmltcMethod &urlMethod); // special diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp index 4050136ef2..75bd580e07 100644 --- a/tools/qmltc/qmltccompiler.cpp +++ b/tools/qmltc/qmltccompiler.cpp @@ -8,6 +8,7 @@ #include "qmltccompilerpieces.h" #include <QtCore/qloggingcategory.h> +#include <QtQml/private/qqmlsignalnames_p.h> #include <private/qqmljsutils_p.h> #include <algorithm> @@ -22,6 +23,137 @@ bool qIsReferenceTypeList(const QQmlJSMetaProperty &p) return false; } +static QList<QQmlJSMetaProperty> unboundRequiredProperties( + const QQmlJSScope::ConstPtr &type, + QmltcTypeResolver *resolver +) { + QList<QQmlJSMetaProperty> requiredProperties{}; + + auto isPropertyRequired = [&type, &resolver](const auto &property) { + if (!type->isPropertyRequired(property.propertyName())) + return false; + + if (type->hasPropertyBindings(property.propertyName())) + return false; + + if (property.isAlias()) { + QQmlJSUtils::AliasResolutionVisitor aliasVisitor; + + QQmlJSUtils::ResolvedAlias result = + QQmlJSUtils::resolveAlias(resolver, property, type, aliasVisitor); + + if (result.kind != QQmlJSUtils::AliasTarget_Property) + return false; + + // If the top level alias targets a property that is in + // the top level scope and that property is required, then + // we will already pick up the property during one of the + // iterations. + // Setting the property or the alias is the same so we + // discard one of the two, as otherwise we would require + // the user to pass two values for the same property ,in + // this case the alias. + // + // For example in: + // + // ``` + // Item { + // id: self + // required property int foo + // property alias bar: self.foo + // } + // ``` + // + // Both foo and bar are required but setting one or the + // other is the same operation so that we should choose + // only one. + if (result.owner == type && + type->isPropertyRequired(result.property.propertyName())) + return false; + + if (result.owner->hasPropertyBindings(result.property.propertyName())) + return false; + } + + return true; + }; + + const auto properties = type->properties(); + std::copy_if(properties.cbegin(), properties.cend(), + std::back_inserter(requiredProperties), isPropertyRequired); + std::sort(requiredProperties.begin(), requiredProperties.end(), + [](const auto &left, const auto &right) { + return left.propertyName() < right.propertyName(); + }); + + return requiredProperties; +} + + +// Populates the internal representation for a +// RequiredPropertiesBundle, a class that acts as a bundle of initial +// values that should be set for the required properties of a type. +static void compileRequiredPropertiesBundle( + QmltcType ¤t, + const QQmlJSScope::ConstPtr &type, + QmltcTypeResolver *resolver +) { + + QList<QQmlJSMetaProperty> requiredProperties = unboundRequiredProperties(type, resolver); + + if (requiredProperties.isEmpty()) + return; + + current.requiredPropertiesBundle.emplace(); + current.requiredPropertiesBundle->name = u"RequiredPropertiesBundle"_s; + + current.requiredPropertiesBundle->members.reserve(requiredProperties.size()); + std::transform(requiredProperties.cbegin(), requiredProperties.cend(), + std::back_inserter(current.requiredPropertiesBundle->members), + [](const QQmlJSMetaProperty &property) { + QString type = qIsReferenceTypeList(property) + ? u"const QList<%1*>&"_s.arg( + property.type()->valueType()->internalName()) + : u"passByConstRefOrValue<%1>"_s.arg( + property.type()->augmentedInternalName()); + return QmltcVariable{ type, property.propertyName() }; + }); +} + +static void compileRootExternalConstructorBody( + QmltcType& current, + const QQmlJSScope::ConstPtr &type +) { + current.externalCtor.body << u"// document root:"_s; + // if it's document root, we want to create our QQmltcObjectCreationBase + // that would store all the created objects + current.externalCtor.body << u"QQmltcObjectCreationBase<%1> objectHolder;"_s.arg( + type->internalName()); + current.externalCtor.body + << u"QQmltcObjectCreationHelper creator = objectHolder.view();"_s; + current.externalCtor.body << u"creator.set(0, this);"_s; // special case + + QString initializerName = u"initializer"_s; + if (current.requiredPropertiesBundle) { + // Compose new initializer based on the initial values for required properties. + current.externalCtor.body << u"auto newInitializer = [&](auto& propertyInitializer) {"_s; + for (const auto& member : current.requiredPropertiesBundle->members) { + current.externalCtor.body << u" propertyInitializer.%1(requiredPropertiesBundle.%2);"_s.arg( + QmltcPropertyData(member.name).write, member.name + ); + } + current.externalCtor.body << u" initializer(propertyInitializer);"_s; + current.externalCtor.body << u"};"_s; + + initializerName = u"newInitializer"_s; + } + + // now call init + current.externalCtor.body << current.init.name + + u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* " + u"endInit */ true, %1);"_s.arg(initializerName); +}; + Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg); const QString QmltcCodeGenerator::privateEngineName = u"ePriv"_s; @@ -87,6 +219,9 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info) const auto *inlineComponentAName = std::get_if<InlineComponentNameType>(&a); const auto *inlineComponentBName = std::get_if<InlineComponentNameType>(&b); + if (inlineComponentAName == inlineComponentBName) + return false; + // the root comes at last, so (a < b) == true when b is the root and a is not if (inlineComponentAName && !inlineComponentBName) return true; @@ -127,7 +262,7 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info) }; for (const auto &type : pureTypes) { - Q_ASSERT(type->scopeType() == QQmlJSScope::QMLScope); + Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope); compiledTypes.emplaceBack(); // create empty type compileType(compiledTypes.back(), type, compile); } @@ -142,8 +277,11 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info) program.cppPath = m_info.outputCppFile; program.hPath = m_info.outputHFile; program.outNamespace = m_info.outputNamespace; + program.exportMacro = m_info.exportMacro; program.compiledTypes = compiledTypes; program.includes = m_visitor->cppIncludeFiles(); + if (!m_info.exportMacro.isEmpty() && !m_info.exportInclude.isEmpty()) + program.includes += (m_info.exportInclude); program.urlMethod = urlMethod; QmltcOutput out; @@ -186,7 +324,8 @@ void QmltcCompiler::compileType( // make document root a friend to allow it to access init and endInit const QString rootInternalName = m_visitor->inlineComponent(type->enclosingInlineComponentName())->internalName(); - current.otherCode << u"friend class %1;"_s.arg(rootInternalName); + if (rootInternalName != current.cppType) // avoid GCC13 warning on self-befriending + current.otherCode << "friend class %1;"_L1.arg(rootInternalName); } if (documentRoot || inlineComponent) { auto name = type->inlineComponentName() @@ -210,8 +349,11 @@ void QmltcCompiler::compileType( return scope->parentScope(); return scope; }; - current.otherCode << u"friend class %1;"_s.arg( - realQmlScope(type->parentScope())->internalName()); + + const auto& realScope = realQmlScope(type->parentScope()); + if (realScope != rootType) { + current.otherCode << u"friend class %1;"_s.arg(realScope->internalName()); + } } // make QQmltcObjectCreationHelper a friend of every type since it provides @@ -240,6 +382,17 @@ void QmltcCompiler::compileType( current.finalizeComponent.access = QQmlJSMetaMethod::Protected; current.handleOnCompleted.access = QQmlJSMetaMethod::Protected; + current.propertyInitializer.name = u"PropertyInitializer"_s; + current.propertyInitializer.constructor.access = QQmlJSMetaMethod::Public; + current.propertyInitializer.constructor.name = current.propertyInitializer.name; + current.propertyInitializer.constructor.parameterList = { + QmltcVariable(u"%1&"_s.arg(current.cppType), u"component"_s) + }; + current.propertyInitializer.component.cppType = current.cppType + u"&"; + current.propertyInitializer.component.name = u"component"_s; + current.propertyInitializer.initializedCache.cppType = u"QSet<QString>"_s; + current.propertyInitializer.initializedCache.name = u"initializedCache"_s; + current.baselineCtor.name = current.cppType; current.externalCtor.name = current.cppType; current.init.name = u"QML_init"_s; @@ -259,19 +412,40 @@ void QmltcCompiler::compileType( QmltcVariable creator(u"QQmltcObjectCreationHelper*"_s, u"creator"_s); QmltcVariable engine(u"QQmlEngine*"_s, u"engine"_s); QmltcVariable parent(u"QObject*"_s, u"parent"_s, u"nullptr"_s); + QmltcVariable initializedCache( + u"[[maybe_unused]] const QSet<QString>&"_s, + u"initializedCache"_s, + u"{}"_s + ); QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_s, u"parentContext"_s); QmltcVariable finalizeFlag(u"bool"_s, u"canFinalize"_s); current.baselineCtor.parameterList = { parent }; current.endInit.parameterList = { creator, engine }; - current.setComplexBindings.parameterList = { creator, engine }; + current.setComplexBindings.parameterList = { creator, engine, initializedCache }; current.handleOnCompleted.parameterList = { creator }; if (documentRoot || inlineComponent) { - current.externalCtor.parameterList = { engine, parent }; - current.init.parameterList = { creator, engine, ctxtdata, finalizeFlag }; + const QmltcVariable initializer( + u"[[maybe_unused]] qxp::function_ref<void(%1&)>"_s.arg(current.propertyInitializer.name), + u"initializer"_s, + u"[](%1&){}"_s.arg(current.propertyInitializer.name)); + + current.init.parameterList = { creator, engine, ctxtdata, finalizeFlag, initializer }; current.beginClass.parameterList = { creator, finalizeFlag }; current.completeComponent.parameterList = { creator, finalizeFlag }; current.finalizeComponent.parameterList = { creator, finalizeFlag }; + + compileRequiredPropertiesBundle(current, type, m_typeResolver); + + if (current.requiredPropertiesBundle) { + QmltcVariable bundle{ + u"const %1&"_s.arg(current.requiredPropertiesBundle->name), + u"requiredPropertiesBundle"_s, + }; + current.externalCtor.parameterList = { engine, bundle, parent, initializer }; + } else { + current.externalCtor.parameterList = { engine, parent, initializer }; + } } else { current.externalCtor.parameterList = { creator, engine, parent }; current.init.parameterList = { creator, engine, ctxtdata }; @@ -295,18 +469,7 @@ void QmltcCompiler::compileType( // compilation stub: current.externalCtor.body << u"Q_UNUSED(engine)"_s; if (documentRoot || inlineComponent) { - current.externalCtor.body << u"// document root:"_s; - // if it's document root, we want to create our QQmltcObjectCreationBase - // that would store all the created objects - current.externalCtor.body << u"QQmltcObjectCreationBase<%1> objectHolder;"_s.arg( - type->internalName()); - current.externalCtor.body - << u"QQmltcObjectCreationHelper creator = objectHolder.view();"_s; - current.externalCtor.body << u"creator.set(0, this);"_s; // special case - // now call init - current.externalCtor.body << current.init.name - + u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* " - u"endInit */ true);"; + compileRootExternalConstructorBody(current, type); } else { current.externalCtor.body << u"// not document root:"_s; // just call init, we don't do any setup here otherwise @@ -321,7 +484,7 @@ void QmltcCompiler::compileType( staticCreate.comments << u"Used by the engine for singleton creation."_s << u"See also \\l {https://doc.qt.io/qt-6/qqmlengine.html#QML_SINGLETON}."_s; - staticCreate.type = QQmlJSMetaMethod::StaticMethod; + staticCreate.type = QQmlJSMetaMethodType::StaticMethod; staticCreate.access = QQmlJSMetaMethod::Public; staticCreate.name = u"create"_s; staticCreate.returnType = u"%1 *"_s.arg(current.cppType); @@ -354,6 +517,102 @@ static Iterator partitionBindings(Iterator first, Iterator last) }); } +// Populates the propertyInitializer of the current type based on the +// available properties. +// +// A propertyInitializer is a generated class that provides a +// restricted interface that only allows setting property values and +// internally keep tracks of which properties where actually set, +// intended to be used to allow the user to set up the initial values +// when creating an instance of a component. +// +// For each property of the current type that is known, is not private +// and is writable, a setter method is generated. +// Each setter method knows how to set a specific property, so as to +// provide a strongly typed interface to property setting, as if the +// relevant C++ type was used directly. +// +// Each setter uses the write method for the proprerty when available +// and otherwise falls back to a the more generic +// `QObject::setProperty` for properties where a WRITE method is not +// available or in scope. +static void compilePropertyInitializer(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { + static auto isFromExtension = [](const QQmlJSMetaProperty &property, const QQmlJSScope::ConstPtr &scope) { + return scope->ownerOfProperty(scope, property.propertyName()).extensionSpecifier != QQmlJSScope::NotExtension; + }; + + current.propertyInitializer.constructor.initializerList << u"component{component}"_s; + + auto properties = type->properties().values(); + for (auto& property: properties) { + if (property.index() == -1) continue; + if (property.isPrivate()) continue; + if (!property.isWritable() && !qIsReferenceTypeList(property)) continue; + + const QString name = property.propertyName(); + + current.propertyInitializer.propertySetters.emplace_back(); + auto& compiledSetter = current.propertyInitializer.propertySetters.back(); + + compiledSetter.userVisible = true; + compiledSetter.returnType = u"void"_s; + compiledSetter.name = QmltcPropertyData(property).write; + + if (qIsReferenceTypeList(property)) { + compiledSetter.parameterList.emplaceBack( + QQmlJSUtils::constRefify(u"QList<%1*>"_s.arg(property.type()->valueType()->internalName())), + name + u"_", QString() + ); + } else { + compiledSetter.parameterList.emplaceBack( + QQmlJSUtils::constRefify(getUnderlyingType(property)), name + u"_", QString() + ); + } + + if (qIsReferenceTypeList(property)) { + compiledSetter.body << u"QQmlListReference list_ref_(&%1, \"%2\");"_s.arg( + current.propertyInitializer.component.name, name + ); + compiledSetter.body << u"list_ref_.clear();"_s; + compiledSetter.body << u"for (const auto& list_item_ : %1_)"_s.arg(name); + compiledSetter.body << u" list_ref_.append(list_item_);"_s; + } else if ( + QQmlJSUtils::bindablePropertyHasDefaultAccessor(property, QQmlJSUtils::PropertyAccessor_Write) + ) { + compiledSetter.body << u"%1.%2().setValue(%3_);"_s.arg( + current.propertyInitializer.component.name, property.bindable(), name); + } else if (type->hasOwnProperty(name)) { + compiledSetter.body << u"%1.%2(%3_);"_s.arg( + current.propertyInitializer.component.name, QmltcPropertyData(property).write, name); + } else if (property.write().isEmpty() || isFromExtension(property, type)) { + // We can end here if a WRITE method is not available or + // if the method is available but not in this scope, so + // that we fallback to the string-based setters.. + // + // For example, types that makes use of QML_EXTENDED + // types, will have the extension types properties + // available and with a WRITE method, but the WRITE method + // will not be available to the extended type, from C++, + // as the type does not directly inherit from the + // extension type. + // + // We specifically scope `setProperty` to `QObject` as + // certain types might have shadowed the method. + // For example, in QtQuick, some types have a property + // called `property` with a `setProperty` WRITE method + // that will produce the shadowing. + compiledSetter.body << u"%1.QObject::setProperty(\"%2\", QVariant::fromValue(%2_));"_s.arg( + current.propertyInitializer.component.name, name); + } else { + compiledSetter.body << u"%1.%2(%3_);"_s.arg( + current.propertyInitializer.component.name, property.write(), name); + } + + compiledSetter.body << u"%1.insert(\"%2\");"_s.arg( + current.propertyInitializer.initializedCache.name, name); + } +} + void QmltcCompiler::compileTypeElements(QmltcType ¤t, const QQmlJSScope::ConstPtr &type) { // compile components of a type: @@ -396,6 +655,7 @@ void QmltcCompiler::compileTypeElements(QmltcType ¤t, const QQmlJSScope::C auto bindings = type->ownPropertyBindingsInQmlIROrder(); partitionBindings(bindings.begin(), bindings.end()); + compilePropertyInitializer(current, type); compileBinding(current, bindings.begin(), bindings.end(), type, { type }); } @@ -450,7 +710,7 @@ compileMethodParameters(const QList<QQmlJSMetaParameter> ¶meterInfos, bool a static QString figureReturnType(const QQmlJSMetaMethod &m) { const bool isVoidMethod = - m.returnTypeName() == u"void" || m.methodType() == QQmlJSMetaMethod::Signal; + m.returnTypeName() == u"void" || m.methodType() == QQmlJSMetaMethodType::Signal; Q_ASSERT(isVoidMethod || m.returnType()); QString type; if (isVoidMethod) { @@ -467,10 +727,10 @@ void QmltcCompiler::compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m, const auto returnType = figureReturnType(m); const QList<QmltcVariable> compiledParams = compileMethodParameters(m.parameters()); - const auto methodType = QQmlJSMetaMethod::Type(m.methodType()); + const auto methodType = m.methodType(); QStringList code; - if (methodType != QQmlJSMetaMethod::Signal) { + if (methodType != QQmlJSMetaMethodType::Signal) { QmltcCodeGenerator urlGenerator { m_url, m_visitor }; QmltcCodeGenerator::generate_callExecuteRuntimeFunction( &code, urlGenerator.urlMethodName() + u"()", @@ -485,7 +745,7 @@ void QmltcCompiler::compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m, compiled.body = std::move(code); compiled.type = methodType; compiled.access = m.access(); - if (methodType != QQmlJSMetaMethod::Signal) { + if (methodType != QQmlJSMetaMethodType::Signal) { compiled.declarationPrefixes << u"Q_INVOKABLE"_s; compiled.userVisible = m.access() == QQmlJSMetaMethod::Public; } else { @@ -1057,7 +1317,7 @@ void QmltcCompiler::compileObjectBinding(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { - Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::Object); + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Object); const QString &propertyName = binding.propertyName(); const QQmlJSMetaProperty property = type->property(propertyName); @@ -1115,7 +1375,7 @@ void QmltcCompiler::compileObjectBinding(QmltcType ¤t, Q_ASSERT(object->baseType()->internalName() == u"QQmlComponent"_s); if (int id = m_visitor->runtimeId(object); id >= 0) { - QString idString = m_visitor->addressableScopes().id(object); + QString idString = m_visitor->addressableScopes().id(object, object); if (idString.isEmpty()) idString = u"<unknown>"_s; QmltcCodeGenerator::generate_setIdValue(block, u"thisContext"_s, id, objectName, @@ -1154,8 +1414,8 @@ void QmltcCompiler::compileValueSourceOrInterceptorBinding(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { - Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::ValueSource - || binding.bindingType() == QQmlJSMetaPropertyBinding::Interceptor); + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::ValueSource + || binding.bindingType() == QQmlSA::BindingType::Interceptor); const QString &propertyName = binding.propertyName(); const QQmlJSMetaProperty property = type->property(propertyName); @@ -1163,7 +1423,7 @@ void QmltcCompiler::compileValueSourceOrInterceptorBinding(QmltcType ¤t, // NB: object is compiled with compileType(), here just need to use it QSharedPointer<const QQmlJSScope> object; - if (binding.bindingType() == QQmlJSMetaPropertyBinding::Interceptor) + if (binding.bindingType() == QQmlSA::BindingType::Interceptor) object = binding.interceptorType(); else object = binding.valueSourceType(); @@ -1212,7 +1472,7 @@ void QmltcCompiler::compileAttachedPropertyBinding(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { - Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::AttachedProperty); + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::AttachedProperty); const QString &propertyName = binding.propertyName(); const QQmlJSMetaProperty property = type->property(propertyName); @@ -1276,7 +1536,7 @@ void QmltcCompiler::compileGroupPropertyBinding(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { - Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::GroupProperty); + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::GroupProperty); const QString &propertyName = binding.propertyName(); const QQmlJSMetaProperty property = type->property(propertyName); @@ -1333,7 +1593,7 @@ void QmltcCompiler::compileGroupPropertyBinding(QmltcType ¤t, auto it = subbindings.begin(); Q_ASSERT(std::all_of(it, firstScript, [](const auto &x) { - return x.bindingType() != QQmlJSMetaPropertyBinding::Script; + return x.bindingType() != QQmlSA::BindingType::Script; })); compile(it, firstScript); it = firstScript; @@ -1354,7 +1614,7 @@ void QmltcCompiler::compileGroupPropertyBinding(QmltcType ¤t, // once the value is written back, process the script bindings Q_ASSERT(std::all_of(it, subbindings.end(), [](const auto &x) { - return x.bindingType() == QQmlJSMetaPropertyBinding::Script; + return x.bindingType() == QQmlSA::BindingType::Script; })); compile(it, subbindings.end()); } @@ -1368,8 +1628,8 @@ void QmltcCompiler::compileTranslationBinding(QmltcType ¤t, const QQmlJSScope::ConstPtr &type, const BindingAccessorData &accessor) { - Q_ASSERT(binding.bindingType() == QQmlJSMetaPropertyBinding::Translation - || binding.bindingType() == QQmlJSMetaPropertyBinding::TranslationById); + Q_ASSERT(binding.bindingType() == QQmlSA::BindingType::Translation + || binding.bindingType() == QQmlSA::BindingType::TranslationById); const QString &propertyName = binding.propertyName(); @@ -1446,7 +1706,7 @@ void QmltcCompiler::compileBinding(QmltcType ¤t, const auto location = binding.sourceLocation(); // make sure group property is not generalized by checking if type really has a property // called propertyName. If not, it is probably an id. - if (binding.bindingType() == QQmlJSMetaPropertyBinding::GroupProperty + if (binding.bindingType() == QQmlSA::BindingType::GroupProperty && type->hasProperty(propertyName)) { qCWarning(lcQmltcCompiler) << QStringLiteral("Binding at line %1 column %2 is not deferred as it is a " @@ -1493,26 +1753,32 @@ void QmltcCompiler::compileBindingByType(QmltcType ¤t, accessor.name, constructFromQObject); }; switch (binding.bindingType()) { - case QQmlJSMetaPropertyBinding::BoolLiteral: { + case QQmlSA::BindingType::BoolLiteral: { const bool value = binding.boolValue(); assignToProperty(metaProperty, value ? u"true"_s : u"false"_s); break; } - case QQmlJSMetaPropertyBinding::NumberLiteral: { + case QQmlSA::BindingType::NumberLiteral: { assignToProperty(metaProperty, QString::number(binding.numberValue())); break; } - case QQmlJSMetaPropertyBinding::StringLiteral: { - assignToProperty(metaProperty, QQmlJSUtils::toLiteral(binding.stringValue())); + case QQmlSA::BindingType::StringLiteral: { + QString value = QQmlJSUtils::toLiteral(binding.stringValue()); + if (auto type = metaProperty.type()) { + if (type->internalName() == u"QUrl"_s) { + value = u"QUrl(%1)"_s.arg(value); + } + } + assignToProperty(metaProperty, value); break; } - case QQmlJSMetaPropertyBinding::RegExpLiteral: { + case QQmlSA::BindingType::RegExpLiteral: { const QString value = u"QRegularExpression(%1)"_s.arg(QQmlJSUtils::toLiteral(binding.regExpValue())); assignToProperty(metaProperty, value); break; } - case QQmlJSMetaPropertyBinding::Null: { + case QQmlSA::BindingType::Null: { // poor check: null bindings are only supported for var and objects Q_ASSERT(propertyType->isSameType(m_typeResolver->varType()) || propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference); @@ -1522,38 +1788,38 @@ void QmltcCompiler::compileBindingByType(QmltcType ¤t, assignToProperty(metaProperty, u"QVariant::fromValue(nullptr)"_s); break; } - case QQmlJSMetaPropertyBinding::Script: { + case QQmlSA::BindingType::Script: { QString bindingSymbolName = type->internalName() + u'_' + propertyName + u"_binding"; bindingSymbolName.replace(u'.', u'_'); // can happen with group properties compileScriptBinding(current, binding, bindingSymbolName, type, propertyName, propertyType, accessor); break; } - case QQmlJSMetaPropertyBinding::Object: { + case QQmlSA::BindingType::Object: { compileObjectBinding(current, binding, type, accessor); break; } - case QQmlJSMetaPropertyBinding::Interceptor: + case QQmlSA::BindingType::Interceptor: Q_FALLTHROUGH(); - case QQmlJSMetaPropertyBinding::ValueSource: { + case QQmlSA::BindingType::ValueSource: { compileValueSourceOrInterceptorBinding(current, binding, type, accessor); break; } - case QQmlJSMetaPropertyBinding::AttachedProperty: { + case QQmlSA::BindingType::AttachedProperty: { compileAttachedPropertyBinding(current, binding, type, accessor); break; } - case QQmlJSMetaPropertyBinding::GroupProperty: { + case QQmlSA::BindingType::GroupProperty: { compileGroupPropertyBinding(current, binding, type, accessor); break; } - case QQmlJSMetaPropertyBinding::TranslationById: - case QQmlJSMetaPropertyBinding::Translation: { + case QQmlSA::BindingType::TranslationById: + case QQmlSA::BindingType::Translation: { compileTranslationBinding(current, binding, type, accessor); break; } - case QQmlJSMetaPropertyBinding::Invalid: { + case QQmlSA::BindingType::Invalid: { recordError(binding.sourceLocation(), u"This binding is invalid"_s); break; } @@ -1617,8 +1883,11 @@ static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope // index is already added as p.index()) if (type->isSameType(owner)) return; - if (m == QQmlJSScope::ExtensionNamespace) // extension namespace properties are ignored + + // extension namespace and JavaScript properties are ignored + if (m == QQmlJSScope::ExtensionNamespace || m == QQmlJSScope::ExtensionJavaScript) return; + index += int(type->ownProperties().size()); }; QQmlJSUtils::traverseFollowingMetaObjectHierarchy(scope, owner, increment); @@ -1649,10 +1918,10 @@ void QmltcCompiler::compileScriptBinding(QmltcType ¤t, Q_ASSERT(!objectClassName_signal.isEmpty()); Q_ASSERT(!objectClassName_slot.isEmpty()); - const auto signalMethods = objectType->methods(name, QQmlJSMetaMethod::Signal); + const auto signalMethods = objectType->methods(name, QQmlJSMetaMethodType::Signal); Q_ASSERT(!signalMethods.isEmpty()); // an error somewhere else QQmlJSMetaMethod signal = signalMethods.at(0); - Q_ASSERT(signal.methodType() == QQmlJSMetaMethod::Signal); + Q_ASSERT(signal.methodType() == QQmlJSMetaMethodType::Signal); const QString signalName = signal.methodName(); const QString slotName = newSymbol(signalName + u"_slot"); @@ -1672,7 +1941,7 @@ void QmltcCompiler::compileScriptBinding(QmltcType ¤t, objectType->ownRuntimeFunctionIndex(binding.scriptIndex()), u"this"_s, // Note: because script bindings always use current QML object scope signalReturnType, slotParameters); - slotMethod.type = QQmlJSMetaMethod::Slot; + slotMethod.type = QQmlJSMetaMethodType::Slot; current.functions << std::move(slotMethod); current.setComplexBindings.body << u"QObject::connect(" + This_signal + u", " + u"&" @@ -1681,7 +1950,7 @@ void QmltcCompiler::compileScriptBinding(QmltcType ¤t, }; switch (binding.scriptKind()) { - case QQmlJSMetaPropertyBinding::Script_PropertyBinding: { + case QQmlSA::ScriptBindingKind::PropertyBinding: { if (!propertyType) { recordError(binding.sourceLocation(), u"Binding on property '" + propertyName + u"' of unknown type"); @@ -1723,19 +1992,20 @@ void QmltcCompiler::compileScriptBinding(QmltcType ¤t, property, valueTypeIndex, accessor.name); break; } - case QQmlJSMetaPropertyBinding::Script_SignalHandler: { - const auto name = QQmlJSUtils::signalName(propertyName); + case QQmlSA::ScriptBindingKind::SignalHandler: { + const auto name = QQmlSignalNames::handlerNameToSignalName(propertyName); Q_ASSERT(name.has_value()); compileScriptSignal(*name); break; } - case QQmlJSMetaPropertyBinding::Script_ChangeHandler: { + case QQmlSA ::ScriptBindingKind::ChangeHandler: { const QString objectClassName = objectType->internalName(); const QString bindingFunctorName = newSymbol(bindingSymbolName + u"Functor"); - const auto signalName = QQmlJSUtils::signalName(propertyName); + const auto signalName = QQmlSignalNames::handlerNameToSignalName(propertyName); Q_ASSERT(signalName.has_value()); // an error somewhere else - const auto actualProperty = QQmlJSUtils::changeHandlerProperty(objectType, *signalName); + const auto actualProperty = + QQmlJSUtils::propertyFromChangedHandler(objectType, propertyName); Q_ASSERT(actualProperty.has_value()); // an error somewhere else const auto actualPropertyType = actualProperty->type(); if (!actualPropertyType) { @@ -1759,9 +2029,12 @@ void QmltcCompiler::compileScriptBinding(QmltcType ¤t, current.children << compileScriptBindingPropertyChangeHandler( binding, objectType, m_urlMethodName, bindingFunctorName, objectClassName); + current.setComplexBindings.body << u"if (!%1.contains(\"%2\"))"_s.arg( + current.propertyInitializer.initializedCache.name, propertyName); + // TODO: this could be dropped if QQmlEngine::setContextForObject() is // done before currently generated C++ object is constructed - current.setComplexBindings.body << bindingSymbolName + u".reset(new QPropertyChangeHandler<" + current.setComplexBindings.body << u" "_s + bindingSymbolName + u".reset(new QPropertyChangeHandler<" + bindingFunctorName + u">(" + QmltcCodeGenerator::wrap_privateClass(accessor.name, *actualProperty) + u"->" + bindableString + u"().onValueChanged(" + bindingFunctorName + u"(" diff --git a/tools/qmltc/qmltccompiler.h b/tools/qmltc/qmltccompiler.h index d5f07aa3b0..3deab6d44e 100644 --- a/tools/qmltc/qmltccompiler.h +++ b/tools/qmltc/qmltccompiler.h @@ -25,6 +25,8 @@ struct QmltcCompilerInfo QString outputHFile; QString outputNamespace; QString resourcePath; + QString exportMacro; + QString exportInclude; }; class QmltcCompiler @@ -48,7 +50,7 @@ public: static bool isComplexBinding(const QQmlJSMetaPropertyBinding &binding) { // TODO: translation bindings (once supported) are also complex? - return binding.bindingType() == QQmlJSMetaPropertyBinding::Script; + return binding.bindingType() == QQmlSA::BindingType::Script; } private: @@ -185,14 +187,14 @@ private: bool hasErrors() const { return m_logger->hasErrors(); } void recordError(const QQmlJS::SourceLocation &location, const QString &message, - LoggerWarningId id = qmlCompiler) + QQmlJS::LoggerWarningId id = qmlCompiler) { // pretty much any compiler error is a critical error (we cannot // generate code - compilation fails) m_logger->log(message, id, location); } void recordError(const QV4::CompiledData::Location &location, const QString &message, - LoggerWarningId id = qmlCompiler) + QQmlJS::LoggerWarningId id = qmlCompiler) { recordError(QQmlJS::SourceLocation { 0, 0, location.line(), location.column() }, message, id); diff --git a/tools/qmltc/qmltccompilerpieces.cpp b/tools/qmltc/qmltccompilerpieces.cpp index 601cf1bbed..cd1735bc07 100644 --- a/tools/qmltc/qmltccompilerpieces.cpp +++ b/tools/qmltc/qmltccompilerpieces.cpp @@ -15,8 +15,8 @@ static QString scopeName(const QQmlJSScope::ConstPtr &scope) { Q_ASSERT(scope->isFullyResolved()); const auto scopeType = scope->scopeType(); - if (scopeType == QQmlJSScope::GroupedPropertyScope - || scopeType == QQmlJSScope::AttachedPropertyScope) { + if (scopeType == QQmlSA::ScopeType::GroupedPropertyScope + || scopeType == QQmlSA::ScopeType::AttachedPropertyScope) { return scope->baseType()->internalName(); } return scope->internalName(); @@ -237,15 +237,18 @@ void QmltcCodeGenerator::generate_createBindingOnProperty( } *block += prologue; - *block << value + u"->" + bindable + u"().setBinding(" + createBindingForBindable + u");"; + *block << u"if (!initializedCache.contains(\"%1\"))"_s.arg(p.propertyName()); + *block << u" "_s + value + u"->" + bindable + u"().setBinding(" + createBindingForBindable + u");"; *block += epilogue; } else { QString createBindingForNonBindable = - u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::createBindingForNonBindable(" + unitVarName + u" "_s + + u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::createBindingForNonBindable(" + unitVarName + u", " + scope + u", " + QString::number(functionIndex) + u", " + target + u", " + QString::number(propertyIndex) + u", " + QString::number(valueTypeIndex) + u", " + propName + u")"; // Note: in this version, the binding is set implicitly + *block << u"if (!initializedCache.contains(\"%1\"))"_s.arg(p.propertyName()); *block << createBindingForNonBindable + u";"; } } @@ -293,8 +296,8 @@ void QmltcCodeGenerator::generate_createTranslationBindingOnProperty( QmltcCodeGenerator::PreparedValue QmltcCodeGenerator::wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p, QString value) { - auto isDerivedFromBuiltin = [](QQmlJSScope::ConstPtr t, const QString &builtin) { - for (; t; t = t->baseType()) { + auto isDerivedFromBuiltin = [](const QQmlJSScope::ConstPtr &derived, const QString &builtin) { + for (QQmlJSScope::ConstPtr t = derived; t; t = t->baseType()) { if (t->internalName() == builtin) return true; } @@ -302,7 +305,7 @@ QmltcCodeGenerator::wrap_mismatchingTypeConversion(const QQmlJSMetaProperty &p, }; QStringList prologue; QStringList epilogue; - auto propType = p.type(); + const QQmlJSScope::ConstPtr propType = p.type(); if (isDerivedFromBuiltin(propType, u"QVariant"_s)) { const QString variantName = u"var_" + p.propertyName(); prologue << u"{ // accepts QVariant"_s; diff --git a/tools/qmltc/qmltccompilerpieces.h b/tools/qmltc/qmltccompilerpieces.h index bfd22e1b79..3252f19e86 100644 --- a/tools/qmltc/qmltccompilerpieces.h +++ b/tools/qmltc/qmltccompilerpieces.h @@ -152,14 +152,17 @@ struct QmltcCodeGenerator /*! \internal - Generates \a{current.init}'s code. The init method sets up a QQmlContext for - the object and (in case \a type is a document root) calls other object - creation methods in a well-defined order: + Generates \a{current.init}'s code. The init method sets up a + QQmlContext for the object and (in case \a type is a document + root) calls other object creation methods, and a user-provided + initialization callback, in a well-defined order: 1. current.beginClass 2. current.endInit - 3. current.completeComponent - 4. current.finalizeComponent - 5. current.handleOnCompleted + 3. user-provided initialization function + 4. current.setComplexBindings + 5. current.completeComponent + 6. current.finalizeComponent + 7. current.handleOnCompleted This function returns a QScopeGuard with the final instructions that have to be generated at a later point, once everything else is compiled. @@ -260,7 +263,7 @@ inline decltype(auto) QmltcCodeGenerator::generate_initCode(QmltcType ¤t, if (int id = visitor->runtimeId(type); id >= 0) { current.init.body << u"// 3. set id since it is provided"_s; - QString idString = visitor->addressableScopes().id(type); + QString idString = visitor->addressableScopes().id(type, type); if (idString.isEmpty()) idString = u"<unknown>"_s; QmltcCodeGenerator::generate_setIdValue(¤t.init.body, u"context"_s, id, u"this"_s, @@ -294,8 +297,14 @@ inline decltype(auto) QmltcCodeGenerator::generate_initCode(QmltcType ¤t, .arg(current.beginClass.name); current.init.body << QStringLiteral(" %1(creator, engine);") .arg(current.endInit.name); - current.init.body << QStringLiteral(" %1(creator, engine);") - .arg(current.setComplexBindings.name); + + current.init.body << QStringLiteral(" {"); + current.init.body << QStringLiteral(" PropertyInitializer propertyInitializer(*this);"); + current.init.body << QStringLiteral(" initializer(propertyInitializer);"); + current.init.body << QStringLiteral(" %1(creator, engine, propertyInitializer.initializedCache);").arg(current.setComplexBindings.name); + current.init.body << QStringLiteral(" }"); + + current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);") .arg(current.completeComponent.name); current.init.body << QStringLiteral(" %1(creator, /* finalize */ true);") diff --git a/tools/qmltc/qmltcoutputir.h b/tools/qmltc/qmltcoutputir.h index 8884ddc3a3..de531f718d 100644 --- a/tools/qmltc/qmltcoutputir.h +++ b/tools/qmltc/qmltcoutputir.h @@ -75,7 +75,7 @@ struct QmltcMethodBase struct QmltcMethod : QmltcMethodBase { QString returnType; // C++ return type - QQmlJSMetaMethod::Type type = QQmlJSMetaMethod::Method; // Qt function type + QQmlJSMetaMethodType type = QQmlJSMetaMethodType::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 @@ -92,6 +92,42 @@ struct QmltcDtor : QmltcMethodBase { }; +// Represents a generated class that knows how to set the public, +// writable properties of a compiled QML -> C++ type. +// This is generally intended to be available for the root of the +// document to allow the user to set the initial values for +// properties, when creating a component, with support for strong +// typing. +struct QmltcPropertyInitializer { + QString name; + + QmltcCtor constructor; + + // A member containing a reference to the object for which the + // properties should be set. + QmltcVariable component; + + // A member containing a cache of properties that were actually + // set that can be referenced later.. + QmltcVariable initializedCache; + + // Setter methods for each property. + QList<QmltcMethod> propertySetters; +}; + +// Represents a generated class that contains a bundle of values to +// initialize the required properties of a type. +// +// This is generally intended to be available for the root component +// of the document, where it will be used as a constructor argument to +// force the user to provide initial values for the required +// properties of the constructed type. +struct QmltcRequiredPropertiesBundle { + QString name; + + QList<QmltcVariable> members; +}; + // Represents QML -> C++ compiled type struct QmltcType { @@ -131,6 +167,12 @@ struct QmltcType // needed for singletons std::optional<QmltcMethod> staticCreate{}; + + // A proxy class that provides a restricted interface that only + // allows setting the properties of the type. + QmltcPropertyInitializer propertyInitializer{}; + + std::optional<QmltcRequiredPropertiesBundle> requiredPropertiesBundle{}; }; // Represents whole QML program, compiled to C++ @@ -140,6 +182,8 @@ struct QmltcProgram QString cppPath; // C++ output .cpp path QString hPath; // C++ output .h path QString outNamespace; + QString exportMacro; // if not empty, the macro that should be used to export the generated + // classes QSet<QString> includes; // non-default C++ include files QmltcMethod urlMethod; // returns QUrl of the QML document diff --git a/tools/qmltc/qmltcpropertyutils.h b/tools/qmltc/qmltcpropertyutils.h index db26c498dd..8a69e5ef09 100644 --- a/tools/qmltc/qmltcpropertyutils.h +++ b/tools/qmltc/qmltcpropertyutils.h @@ -6,6 +6,7 @@ #include <private/qqmljsmetatypes_p.h> #include <private/qqmljsscope_p.h> +#include <QtQml/private/qqmlsignalnames_p.h> QT_BEGIN_NAMESPACE @@ -35,13 +36,11 @@ struct QmltcPropertyData QmltcPropertyData(const QString &propertyName) { - const QString nameWithUppercase = propertyName[0].toUpper() + propertyName.sliced(1); - read = propertyName; - write = u"set" + nameWithUppercase; - bindable = u"bindable" + nameWithUppercase; - notify = propertyName + u"Changed"; - reset = u"reset" + nameWithUppercase; + write = QQmlSignalNames::addPrefixToPropertyName(u"set", propertyName); + bindable = QQmlSignalNames::addPrefixToPropertyName(u"bindable", propertyName); + notify = QQmlSignalNames::propertyNameToChangedSignalName(propertyName); + reset = QQmlSignalNames::addPrefixToPropertyName(u"reset", propertyName); } QString read; diff --git a/tools/qmltc/qmltctyperesolver.cpp b/tools/qmltc/qmltctyperesolver.cpp index 045f3af088..9c53686736 100644 --- a/tools/qmltc/qmltctyperesolver.cpp +++ b/tools/qmltc/qmltctyperesolver.cpp @@ -12,15 +12,12 @@ #include <QtCore/qfileinfo.h> #include <QtCore/qdiriterator.h> -Q_LOGGING_CATEGORY(lcTypeResolver2, "qml.qmltc.typeresolver", QtInfoMsg); +Q_STATIC_LOGGING_CATEGORY(lcTypeResolver2, "qml.qmltc.typeresolver", QtInfoMsg); void QmltcTypeResolver::init(QmltcVisitor *visitor, QQmlJS::AST::Node *program) { QQmlJSTypeResolver::init(visitor, program); - QQmlJSLiteralBindingCheck literalCheck; - literalCheck.run(visitor, this); - m_root = visitor->result(); QQueue<QQmlJSScope::Ptr> objects; diff --git a/tools/qmltc/qmltcvisitor.cpp b/tools/qmltc/qmltcvisitor.cpp index 236ad76467..a6ec1f8661 100644 --- a/tools/qmltc/qmltcvisitor.cpp +++ b/tools/qmltc/qmltcvisitor.cpp @@ -8,6 +8,7 @@ #include <QtCore/qstack.h> #include <QtCore/qdir.h> #include <QtCore/qloggingcategory.h> +#include <QtQml/private/qqmlsignalnames_p.h> #include <private/qqmljsutils_p.h> @@ -101,6 +102,9 @@ void QmltcVisitor::findCppIncludes() // look in type addCppInclude(type); + if (type->isListProperty()) + addCppInclude(type->valueType()); + // look in type's base type auto base = type->baseType(); if (!base && type->isComposite()) @@ -133,8 +137,9 @@ void QmltcVisitor::findCppIncludes() Q_ASSERT(type); const auto scopeType = type->scopeType(); - if (scopeType != QQmlJSScope::QMLScope && scopeType != QQmlJSScope::GroupedPropertyScope - && scopeType != QQmlJSScope::AttachedPropertyScope) { + if (scopeType != QQmlSA::ScopeType::QMLScope + && scopeType != QQmlSA::ScopeType::GroupedPropertyScope + && scopeType != QQmlSA::ScopeType::AttachedPropertyScope) { continue; } @@ -174,7 +179,7 @@ void QmltcVisitor::findCppIncludes() static void addCleanQmlTypeName(QStringList *names, const QQmlJSScope::ConstPtr &scope) { - Q_ASSERT(scope->scopeType() == QQmlJSScope::QMLScope); + Q_ASSERT(scope->scopeType() == QQmlSA::ScopeType::QMLScope); Q_ASSERT(!scope->isArrayScope()); Q_ASSERT(!scope->baseTypeName().isEmpty()); // the scope is guaranteed to be a new QML type, so any prefixes (separated @@ -197,7 +202,7 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiObjectDefinition *object) } // we're not interested in non-QML scopes - if (m_currentScope->scopeType() != QQmlJSScope::QMLScope) + if (m_currentScope->scopeType() != QQmlSA::ScopeType::QMLScope) return true; if (m_currentScope->isInlineComponent()) { @@ -216,7 +221,7 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiObjectDefinition *object) void QmltcVisitor::endVisit(QQmlJS::AST::UiObjectDefinition *object) { - if (m_currentScope->scopeType() == QQmlJSScope::QMLScope) + if (m_currentScope->scopeType() == QQmlSA::ScopeType::QMLScope) m_qmlTypeNames.removeLast(); QQmlJSImportVisitor::endVisit(object); } @@ -268,7 +273,7 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiPublicMember *publicMember) owner->addOwnProperty(property); } - const QString notifyName = name + u"Changed"_s; + const QString notifyName = QQmlSignalNames::propertyNameToChangedSignalName(name); // also check that notify is already a method of the scope { auto owningScope = m_savedBindingOuterScope ? m_savedBindingOuterScope : m_currentScope; @@ -280,7 +285,7 @@ bool QmltcVisitor::visit(QQmlJS::AST::UiPublicMember *publicMember) u"internal error: %1 found for property '%2'"_s.arg(errorString, name), qmlCompiler, publicMember->identifierToken); return false; - } else if (methods[0].methodType() != QQmlJSMetaMethod::Signal) { + } else if (methods[0].methodType() != QQmlJSMetaMethodType::Signal) { m_logger->log(u"internal error: method %1 of property %2 must be a signal"_s.arg( notifyName, name), qmlCompiler, publicMember->identifierToken); @@ -336,17 +341,17 @@ void QmltcVisitor::endVisit(QQmlJS::AST::UiProgram *program) for (const QList<QQmlJSScope::ConstPtr> &qmlTypes : m_pureQmlTypes) for (const QQmlJSScope::ConstPtr &type : qmlTypes) - checkForNamingCollisionsWithCpp(type); + checkNamesAndTypes(type); } QQmlJSScope::ConstPtr fetchType(const QQmlJSMetaPropertyBinding &binding) { switch (binding.bindingType()) { - case QQmlJSMetaPropertyBinding::Object: + case QQmlSA::BindingType::Object: return binding.objectType(); - case QQmlJSMetaPropertyBinding::Interceptor: + case QQmlSA::BindingType::Interceptor: return binding.interceptorType(); - case QQmlJSMetaPropertyBinding::ValueSource: + case QQmlSA::BindingType::ValueSource: return binding.valueSourceType(); // TODO: AttachedProperty and GroupProperty are not supported yet, // but have to also be acknowledged here @@ -435,7 +440,7 @@ void QmltcVisitor::postVisitResolve( // match scopes to indices of QmlIR::Object from QmlIR::Document qsizetype count = 0; const auto setIndex = [&](const QQmlJSScope::Ptr ¤t) { - if (current->scopeType() != QQmlJSScope::QMLScope || current->isArrayScope()) + if (current->scopeType() != QQmlSA::ScopeType::QMLScope || current->isArrayScope()) return; Q_ASSERT(!m_qmlIrObjectIndices.contains(current)); m_qmlIrObjectIndices[current] = count; @@ -547,9 +552,9 @@ void QmltcVisitor::postVisitResolve( if (scope->isArrayScope()) // special kind of QQmlJSScope::QMLScope return; switch (scope->scopeType()) { - case QQmlJSScope::QMLScope: - case QQmlJSScope::GroupedPropertyScope: - case QQmlJSScope::AttachedPropertyScope: { + case QQmlSA::ScopeType::QMLScope: + case QQmlSA::ScopeType::GroupedPropertyScope: + case QQmlSA::ScopeType::AttachedPropertyScope: { ++qmlScopeCount[scope->enclosingInlineComponentName()]; break; } @@ -653,7 +658,7 @@ void QmltcVisitor::setupAliases() } } -void QmltcVisitor::checkForNamingCollisionsWithCpp(const QQmlJSScope::ConstPtr &type) +void QmltcVisitor::checkNamesAndTypes(const QQmlJSScope::ConstPtr &type) { static const QString cppKeywords[] = { u"alignas"_s, @@ -672,20 +677,21 @@ void QmltcVisitor::checkForNamingCollisionsWithCpp(const QQmlJSScope::ConstPtr & u"case"_s, u"catch"_s, u"char"_s, - u"char8_t"_s, u"char16_t"_s, u"char32_t"_s, + u"char8_t"_s, u"class"_s, + u"co_await"_s, + u"co_return"_s, + u"co_yield"_s, u"compl"_s, u"concept"_s, u"const"_s, + u"const_cast"_s, u"consteval"_s, u"constexpr"_s, - u"const_cast"_s, + u"constinit"_s, u"continue"_s, - u"co_await"_s, - u"co_return"_s, - u"co_yield"_s, u"decltype"_s, u"default"_s, u"delete"_s, @@ -753,6 +759,7 @@ void QmltcVisitor::checkForNamingCollisionsWithCpp(const QQmlJSScope::ConstPtr & u"xor"_s, u"xor_eq"_s, }; + Q_ASSERT(std::is_sorted(std::begin(cppKeywords), std::end(cppKeywords))); const auto isReserved = [&](QStringView word) { if (word.startsWith(QChar(u'_')) && word.size() >= 2 @@ -769,6 +776,23 @@ void QmltcVisitor::checkForNamingCollisionsWithCpp(const QQmlJSScope::ConstPtr & qmlCompiler, type->sourceLocation()); }; + const auto validateType = [&type, this](const QQmlJSScope::ConstPtr &typeToCheck, + QStringView name, QStringView errorPrefix) { + if (type->moduleName().isEmpty() || typeToCheck.isNull()) + return; + + if (typeToCheck->isComposite() && typeToCheck->moduleName() != type->moduleName()) { + m_logger->log( + QStringLiteral( + "Can't compile the %1 type \"%2\" to C++ because it " + "lives in \"%3\" instead of the current file's \"%4\" QML module.") + .arg(errorPrefix, name, typeToCheck->moduleName(), type->moduleName()), + qmlCompiler, type->sourceLocation()); + } + }; + + validateType(type->baseType(), type->baseTypeName(), u"QML base"); + const auto enums = type->ownEnumerations(); for (auto it = enums.cbegin(); it != enums.cend(); ++it) { const QQmlJSMetaEnum e = it.value(); @@ -783,16 +807,23 @@ void QmltcVisitor::checkForNamingCollisionsWithCpp(const QQmlJSScope::ConstPtr & for (auto it = properties.cbegin(); it != properties.cend(); ++it) { const QQmlJSMetaProperty &p = it.value(); validate(p.propertyName(), u"Property"); + + if (!p.isAlias() && !p.typeName().isEmpty()) + validateType(p.type(), p.typeName(), u"QML property"); } const auto methods = type->ownMethods(); for (auto it = methods.cbegin(); it != methods.cend(); ++it) { const QQmlJSMetaMethod &m = it.value(); validate(m.methodName(), u"Method"); + if (!m.returnTypeName().isEmpty()) + validateType(m.returnType(), m.returnTypeName(), u"QML method return"); - const auto parameterNames = m.parameterNames(); - for (const auto &name : parameterNames) - validate(name, u"Method '%1' parameter"_s.arg(m.methodName())); + for (const auto ¶meter : m.parameters()) { + validate(parameter.name(), u"Method '%1' parameter"_s.arg(m.methodName())); + if (!parameter.typeName().isEmpty()) + validateType(parameter.type(), parameter.typeName(), u"QML parameter"); + } } // TODO: one could also test signal handlers' parameters but we do not store diff --git a/tools/qmltc/qmltcvisitor.h b/tools/qmltc/qmltcvisitor.h index 0ec9349527..111df0e885 100644 --- a/tools/qmltc/qmltcvisitor.h +++ b/tools/qmltc/qmltcvisitor.h @@ -22,7 +22,7 @@ class QmltcVisitor : public QQmlJSImportVisitor void postVisitResolve(const QHash<QQmlJSScope::ConstPtr, QList<QQmlJSMetaPropertyBinding>> &qmlIrOrderedBindings); void setupAliases(); - void checkForNamingCollisionsWithCpp(const QQmlJSScope::ConstPtr &type); + void checkNamesAndTypes(const QQmlJSScope::ConstPtr &type); void setRootFilePath(); QString sourceDirectoryPath(const QString &path); @@ -57,7 +57,7 @@ public: qsizetype creationIndex(const QQmlJSScope::ConstPtr &type) const { - Q_ASSERT(type->scopeType() == QQmlJSScope::QMLScope); + Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope); return m_creationIndices.value(type, -1); } @@ -68,13 +68,13 @@ public: qsizetype qmlComponentIndex(const QQmlJSScope::ConstPtr &type) const { - Q_ASSERT(type->scopeType() == QQmlJSScope::QMLScope); + Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope); return m_syntheticTypeIndices.value(type, -1); } qsizetype qmlIrObjectIndex(const QQmlJSScope::ConstPtr &type) const { - Q_ASSERT(type->scopeType() == QQmlJSScope::QMLScope); + Q_ASSERT(type->scopeType() == QQmlSA::ScopeType::QMLScope); Q_ASSERT(m_qmlIrObjectIndices.contains(type)); return m_qmlIrObjectIndices.value(type, -1); } |