aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qmltc
diff options
context:
space:
mode:
authorAndrei Golubev <andrei.golubev@qt.io>2021-02-16 12:52:41 +0100
committerQt Cherry-pick Bot <cherrypick_bot@qt-project.org>2021-12-17 14:39:09 +0000
commitdbc134e207943b23edb90552a89d02b8f43812af (patch)
treedec2b4319ca6f2b9499d1ca77ee241f35c792866 /tools/qmltc
parent39433310aa3a25e2711ce426d456a8e78e3cd325 (diff)
Use qmltc compiler prototype as a fallback implementation
Task-number: QTBUG-91927 Task-number: QTBUG-96041 Task-number: QTBUG-84368 Change-Id: I47320b5f3ed8efff6fb234778df5fae5be5b64f2 Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org> Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io> (cherry picked from commit e7ce5abf24f04d1b071343f07ca28b6a5d9ad4b9) Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
Diffstat (limited to 'tools/qmltc')
-rw-r--r--tools/qmltc/CMakeLists.txt10
-rw-r--r--tools/qmltc/main.cpp32
-rw-r--r--tools/qmltc/prototype/codegenerator.cpp1857
-rw-r--r--tools/qmltc/prototype/codegenerator.h180
-rw-r--r--tools/qmltc/prototype/codegeneratorutil.cpp238
-rw-r--r--tools/qmltc/prototype/codegeneratorutil.h126
-rw-r--r--tools/qmltc/prototype/codegeneratorwriter.cpp438
-rw-r--r--tools/qmltc/prototype/codegeneratorwriter.h57
-rw-r--r--tools/qmltc/prototype/generatedcodeprimitives.h126
-rw-r--r--tools/qmltc/prototype/qml2cppcontext.h108
-rw-r--r--tools/qmltc/prototype/qml2cppdefaultpasses.cpp941
-rw-r--r--tools/qmltc/prototype/qml2cppdefaultpasses.h75
-rw-r--r--tools/qmltc/prototype/qml2cpppropertyutils.h73
-rw-r--r--tools/qmltc/prototype/qmlcompiler.h177
-rw-r--r--tools/qmltc/prototype/typeresolver.cpp148
-rw-r--r--tools/qmltc/prototype/typeresolver.h87
-rw-r--r--tools/qmltc/prototype/visitor.cpp62
-rw-r--r--tools/qmltc/prototype/visitor.h62
18 files changed, 4780 insertions, 17 deletions
diff --git a/tools/qmltc/CMakeLists.txt b/tools/qmltc/CMakeLists.txt
index cda6eef7e8..b5ed382e3e 100644
--- a/tools/qmltc/CMakeLists.txt
+++ b/tools/qmltc/CMakeLists.txt
@@ -14,6 +14,16 @@ qt_internal_add_tool(${target_name}
qmltccompilerpieces.h
qmltccompilerutils.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 dd2dfb97ba..8ae320c79e 100644
--- a/tools/qmltc/main.cpp
+++ b/tools/qmltc/main.cpp
@@ -27,8 +27,9 @@
****************************************************************************/
#include "qmltccommandlineutils.h"
-#include "qmltccompiler.h"
-#include "qmltcvisitor.h"
+#include "prototype/codegenerator.h"
+#include "prototype/visitor.h"
+#include "prototype/typeresolver.h"
#include <QtQml/private/qqmlirbuilder_p.h>
#include <private/qqmljscompiler_p.h>
@@ -42,7 +43,7 @@
# include <QtCore/qcommandlineparser.h>
#endif
-#include <cstdio>
+#include <cstdlib> // EXIT_SUCCESS, EXIT_FAILURE
void setupLogger(QQmlJSLogger &logger) // prepare logger to work with compiler
{
@@ -163,31 +164,28 @@ int main(int argc, char **argv)
return EXIT_FAILURE;
}
- QmltcCompilerInfo info;
- info.outputCppFile = outputCppFile;
- info.outputHFile = outputHFile;
- info.outputNamespace = parser.value(namespaceOption);
- info.resourcePath = parser.value(resourcePathOption);
+ Options options;
+ options.outputCppFile = parser.value(outputCppOption);
+ options.outputHFile = parser.value(outputHOption);
+ options.resourcePath = parser.value(resourcePathOption);
+ options.outNamespace = parser.value(namespaceOption);
QQmlJSImporter importer { importPaths, /* resource file mapper */ nullptr };
QQmlJSLogger logger;
logger.setFileName(url);
logger.setCode(sourceCode);
setupLogger(logger);
- QmltcVisitor visitor(&importer, &logger, implicitImportDirectory, qmldirFiles);
- QmltcTypeResolver typeResolver { &importer };
- typeResolver.init(&visitor, document.program);
+
+ Qmltc::Visitor visitor(&importer, &logger, implicitImportDirectory, qmldirFiles);
+ Qmltc::TypeResolver typeResolver { &importer };
+ typeResolver.init(visitor, document.program);
if (logger.hasWarnings() || logger.hasErrors())
return EXIT_FAILURE;
- if (logger.hasWarnings() || logger.hasErrors()) {
- // TODO: how do we print errors/warnings/etc.?
- return EXIT_FAILURE;
- }
+ CodeGenerator generator(url, &logger, &document, &typeResolver);
+ generator.generate(options);
- QmltcCompiler compiler(url, &typeResolver, &visitor, &logger);
- compiler.compile(info);
if (logger.hasWarnings() || logger.hasErrors())
return EXIT_FAILURE;
diff --git a/tools/qmltc/prototype/codegenerator.cpp b/tools/qmltc/prototype/codegenerator.cpp
new file mode 100644
index 0000000000..d02c98f04b
--- /dev/null
+++ b/tools/qmltc/prototype/codegenerator.cpp
@@ -0,0 +1,1857 @@
+/****************************************************************************
+**
+** 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/codegenerator.h"
+#include "prototype/qml2cppdefaultpasses.h"
+#include "prototype/qml2cpppropertyutils.h"
+#include "prototype/codegeneratorutil.h"
+#include "prototype/codegeneratorwriter.h"
+
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qhash.h>
+#include <QtCore/qset.h>
+#include <QtCore/qregularexpression.h>
+
+#include <QtCore/qloggingcategory.h>
+
+#include <optional>
+#include <utility>
+#include <numeric>
+
+static constexpr char newLineLatin1[] =
+#ifdef Q_OS_WIN32
+ "\r\n";
+#else
+ "\n";
+#endif
+
+Q_LOGGING_CATEGORY(lcCodeGenerator, "qml.qmltc.compiler", QtWarningMsg);
+
+static void writeToFile(const QString &path, const QByteArray &data)
+{
+ // When not using dependency files, changing a single qml invalidates all
+ // qml files and would force the recompilation of everything. To avoid that,
+ // we check if the data is equal to the existing file, if yes, don't touch
+ // it so the build system will not recompile unnecessary things.
+ //
+ // If the build system use dependency file, we should anyway touch the file
+ // so qmlcompiler is not re-run
+ QFileInfo fi(path);
+ if (fi.exists() && fi.size() == data.size()) {
+ QFile oldFile(path);
+ oldFile.open(QIODevice::ReadOnly);
+ if (oldFile.readAll() == data)
+ return;
+ }
+ QFile file(path);
+ file.open(QIODevice::WriteOnly);
+ file.write(data);
+}
+
+static QString figureReturnType(const QQmlJSMetaMethod &m)
+{
+ const bool isVoidMethod =
+ m.returnTypeName() == u"void" || m.methodType() == QQmlJSMetaMethod::Signal;
+ Q_ASSERT(isVoidMethod || m.returnType());
+ // TODO: should really be m.returnTypeName(). for now, avoid inconsistencies
+ // due to QML/C++ name collisions and unique C++ name generation
+ QString type;
+ if (isVoidMethod) {
+ type = u"void"_qs;
+ } else {
+ type = CodeGeneratorUtility::getInternalNameAwareOfAccessSemantics(m.returnType());
+ }
+ return type;
+}
+
+static QList<QQmlJSAotVariable>
+compileMethodParameters(const QStringList &names,
+ const QList<QSharedPointer<const QQmlJSScope>> &types,
+ bool allowUnnamed = false)
+{
+ QList<QQmlJSAotVariable> paramList;
+ const auto size = names.size();
+ paramList.reserve(size);
+ for (qsizetype i = 0; i < size; ++i) {
+ Q_ASSERT(types[i]); // assume verified
+ QString name = names[i];
+ Q_ASSERT(allowUnnamed || !name.isEmpty()); // assume verified
+ if (name.isEmpty() && allowUnnamed)
+ name = u"unnamed_" + QString::number(i);
+ paramList.emplaceBack(QQmlJSAotVariable {
+ CodeGeneratorUtility::getInternalNameAwareOfAccessSemantics(types[i]), names[i],
+ QString() });
+ }
+ return paramList;
+}
+
+// this version just returns runtimeFunctionIndices[relative]. used in
+// property assignments like `property var p: function() {}`
+static qsizetype relativeToAbsoluteRuntimeIndex(const QmlIR::Object *irObject, qsizetype relative)
+{
+ // TODO: for some reason, this function is necessary. do we really need it?
+ // why does it have to be a special case?
+ return irObject->runtimeFunctionIndices.at(relative);
+}
+
+// this version expects that \a relative points to a to-be-executed function.
+// for this, it needs to detect whether a case like `onSignal: function() {}` is
+// present and return nested function index instead of the
+// runtimeFunctionIndices[relative]
+static qsizetype relativeToAbsoluteRuntimeIndex(const QmlIR::Document *doc,
+ const QmlIR::Object *irObject, qsizetype relative)
+{
+ int absoluteIndex = irObject->runtimeFunctionIndices.at(relative);
+ Q_ASSERT(absoluteIndex >= 0);
+ Q_ASSERT(doc->javaScriptCompilationUnit.unitData());
+ const QV4::CompiledData::Function *f =
+ doc->javaScriptCompilationUnit.unitData()->functionAt(absoluteIndex);
+ Q_ASSERT(f);
+ if (f->nestedFunctionIndex != std::numeric_limits<uint32_t>::max())
+ return f->nestedFunctionIndex;
+ return absoluteIndex;
+}
+
+// finds property for given scope and returns it together with the absolute
+// property index in the property array of the corresponding QMetaObject.
+static std::pair<QQmlJSMetaProperty, int> getMetaPropertyIndex(const QQmlJSScope::ConstPtr &scope,
+ const QString &propertyName)
+{
+ auto owner = QQmlJSScope::ownerOfProperty(scope, propertyName);
+ Q_ASSERT(owner);
+ auto p = owner->ownProperty(propertyName);
+ if (!p.isValid())
+ return { p, -1 };
+ int index = p.index();
+ if (index < 0) // this property doesn't have index - comes from QML
+ return { p, -1 };
+ // owner is not included in absolute index
+ for (QQmlJSScope::ConstPtr base = owner->baseType(); base; base = base->baseType())
+ index += int(base->ownProperties().size());
+ return { p, index };
+}
+
+struct QmlIrBindingCompare
+{
+private:
+ using T = QmlIR::Binding;
+ using I = typename QmlIR::PoolList<T>::Iterator;
+ static QHash<uint, qsizetype> orderTable;
+
+public:
+ bool operator()(const I &x, const I &y) const
+ {
+ return orderTable[x->type] < orderTable[y->type];
+ }
+ bool operator()(const T &x, const T &y) const
+ {
+ return orderTable[x.type] < orderTable[y.type];
+ }
+};
+
+QHash<uint, qsizetype> QmlIrBindingCompare::orderTable = {
+ { QmlIR::Binding::Type_Invalid, 100 },
+ // value assignments (and object bindings) are "equal"
+ { QmlIR::Binding::Type_Boolean, 0 },
+ { QmlIR::Binding::Type_Number, 0 },
+ { QmlIR::Binding::Type_String, 0 },
+ { QmlIR::Binding::Type_Object, 0 },
+ // translations are also "equal" between themselves, but come after
+ // assignments
+ { QmlIR::Binding::Type_Translation, 1 },
+ { QmlIR::Binding::Type_TranslationById, 1 },
+ // attached properties and group properties might contain assignments
+ // inside, so come next
+ { QmlIR::Binding::Type_AttachedProperty, 2 },
+ { QmlIR::Binding::Type_GroupProperty, 2 },
+ // JS bindings come last because they can use values from other categories
+ { QmlIR::Binding::Type_Script, 3 },
+ // { QmlIR::Binding::Type_Null, 100 }, // TODO: what is this used for?
+};
+
+static QList<typename QmlIR::PoolList<QmlIR::Binding>::Iterator>
+toOrderedSequence(typename QmlIR::PoolList<QmlIR::Binding>::Iterator first,
+ typename QmlIR::PoolList<QmlIR::Binding>::Iterator last, qsizetype n)
+{
+ // bindings actually have to sorted so that e.g. value assignments come
+ // before script bindings. this is important for the code generator as it
+ // would just emit instructions one by one, regardless of the ordering
+ using I = typename QmlIR::PoolList<QmlIR::Binding>::Iterator;
+ QList<I> sorted;
+ sorted.reserve(n);
+ for (auto it = first; it != last; ++it)
+ sorted << it;
+ // NB: use stable sort for bindings, because this matters: relative order of
+ // bindings must be preserved - this might affect UI
+ std::stable_sort(sorted.begin(), sorted.end(), QmlIrBindingCompare {});
+ return sorted;
+}
+
+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)))
+{
+}
+
+void CodeGenerator::constructObjects(QSet<QString> &requiredCppIncludes)
+{
+ const auto &objects = m_doc->objects;
+ m_objects.reserve(objects.size());
+
+ m_typeToObjectIndex.reserve(objects.size());
+
+ for (qsizetype objectIndex = 0; objectIndex != objects.size(); ++objectIndex) {
+ QmlIR::Object *irObject = objects[objectIndex];
+ if (!irObject) {
+ recordError(QQmlJS::SourceLocation {},
+ u"Internal compiler error: IR object is null"_qs);
+ return;
+ }
+ QQmlJSScope::Ptr object = m_localTypeResolver->scopeForLocation(irObject->location);
+ if (!object) {
+ recordError(irObject->location, u"Object of unknown type"_qs);
+ return;
+ }
+ m_typeToObjectIndex.insert(object, objectIndex);
+ m_objects.emplaceBack(CodeGenObject { irObject, object });
+ }
+
+ // objects are constructed, now we can run compiler passes to make sure they
+ // are in good state
+ Qml2CppCompilerPassExecutor executor(m_doc, m_localTypeResolver, m_url, m_objects,
+ m_typeToObjectIndex);
+ executor.addPass(&verifyTypes);
+ executor.addPass(&checkForNamingCollisionsWithCpp);
+ executor.addPass([this](const Qml2CppContext &context, QList<Qml2CppObject> &objects) {
+ m_typeCounts = makeUniqueCppNames(context, objects);
+ });
+ const auto setupQmlBaseTypes = [&](const Qml2CppContext &context,
+ QList<Qml2CppObject> &objects) {
+ m_qmlCompiledBaseTypes = setupQmlCppTypes(context, objects);
+ };
+ executor.addPass(setupQmlBaseTypes);
+ const auto resolveAliases = [&](const Qml2CppContext &context, QList<Qml2CppObject> &objects) {
+ m_aliasesToIds = deferredResolveValidateAliases(context, objects);
+ };
+ executor.addPass(resolveAliases);
+ const auto populateCppIncludes = [&](const Qml2CppContext &context,
+ QList<Qml2CppObject> &objects) {
+ requiredCppIncludes = findCppIncludes(context, objects);
+ };
+ executor.addPass(populateCppIncludes);
+ const auto resolveImplicitComponents = [&](const Qml2CppContext &context,
+ QList<Qml2CppObject> &objects) {
+ m_implicitComponentMapping = findAndResolveImplicitComponents(context, objects);
+ };
+ executor.addPass(resolveImplicitComponents);
+ executor.addPass(&setObjectIds);
+ // run all passes:
+ executor.run(m_logger);
+}
+
+void CodeGenerator::generate(const Options &options)
+{
+ 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;
+ m_isAnonymous = rootClassName.at(0).isLower();
+ const QString url = QFileInfo(m_url).fileName();
+
+ // generate url method straight away to be able to use it everywhere
+ compileUrlMethod();
+
+ 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())
+ return;
+
+ const auto isWithinSubComponent = [&](QQmlJSScope::ConstPtr type) {
+ for (; type; type = type->parentScope()) {
+ qsizetype index = m_typeToObjectIndex.value(type, -1);
+ if (index < 0)
+ return false;
+ const QmlIR::Object *irObject = m_objects[index].irObject;
+ if (irObject->flags & QV4::CompiledData::Object::IsComponent)
+ return true;
+ }
+ return false;
+ };
+
+ // compile everything
+ QList<QQmlJSAotObject> compiledObjects;
+ compiledObjects.reserve(m_objects.size());
+ for (const auto &object : m_objects) {
+ if (object.type->scopeType() != QQmlJSScope::QMLScope) {
+ qCDebug(lcCodeGenerator) << u"Scope '" + object.type->internalName()
+ + u"' is not QMLScope, so it is skipped.";
+ continue;
+ }
+ if (isWithinSubComponent(object.type)) {
+ // e.g. when creating a view delegate
+ qCDebug(lcCodeGenerator) << u"Scope '" + object.type->internalName()
+ + u"' is a QQmlComponent sub-component. It won't be compiled to C++.";
+ continue;
+ }
+ compiledObjects.emplaceBack(); // create new object
+ compileObject(compiledObjects.back(), object);
+ }
+ // no point in generating anything if there are errors
+ if (m_logger->hasErrors() || m_logger->hasWarnings())
+ return;
+
+ QQmlJSProgram program { compiledObjects, m_urlMethod, url, hPath, cppPath,
+ options.outNamespace, requiredCppIncludes };
+
+ // write everything
+ GeneratedCodeUtils codeUtils(code);
+ CodeGeneratorWriter::write(codeUtils, program);
+
+ writeToFile(hPath, code.header.toUtf8());
+ writeToFile(cppPath, code.implementation.toUtf8());
+}
+
+QString buildCallSpecialMethodValue(bool documentRoot, const QString &outerFlagName,
+ bool overridesInterface)
+{
+ const QString callInBase = overridesInterface ? u"false"_qs : u"true"_qs;
+ if (documentRoot) {
+ return outerFlagName + u" && " + callInBase;
+ } else {
+ return callInBase;
+ }
+}
+
+void CodeGenerator::compileObject(QQmlJSAotObject &compiled, const CodeGenObject &object)
+{
+ compiled.cppType = object.type->internalName();
+ const QString baseClass = object.type->baseType()->internalName();
+
+ const bool baseTypeIsCompiledQml = m_qmlCompiledBaseTypes.contains(object.type->baseTypeName());
+ const qsizetype objectIndex = m_typeToObjectIndex[object.type];
+ const bool documentRoot = objectIndex == 0;
+ const bool hasParserStatusInterface = object.type->hasInterface(u"QQmlParserStatus"_qs);
+ const bool hasFinalizerHookInterface = object.type->hasInterface(u"QQmlFinalizerHook"_qs);
+
+ compiled.baseClasses = { baseClass };
+
+ // add ctors code
+ compiled.baselineCtor.access = QQmlJSMetaMethod::Protected;
+ compiled.externalCtor.access = QQmlJSMetaMethod::Public;
+ compiled.init.access = QQmlJSMetaMethod::Protected;
+ // TODO: all below could actually be hidden? (but need to befriend the
+ // document root, etc.)
+ compiled.endInit.access = QQmlJSMetaMethod::Public;
+ compiled.completeComponent.access = QQmlJSMetaMethod::Public;
+ compiled.finalizeComponent.access = QQmlJSMetaMethod::Public;
+ compiled.handleOnCompleted.access = QQmlJSMetaMethod::Public;
+
+ compiled.baselineCtor.name = compiled.cppType;
+ compiled.externalCtor.name = compiled.cppType;
+ compiled.init.name = u"QML_init"_qs;
+ compiled.init.returnType = u"QQmlRefPointer<QQmlContextData>"_qs;
+ compiled.endInit.name = u"QML_endInit"_qs;
+ compiled.endInit.returnType = u"void"_qs;
+ compiled.completeComponent.name = u"QML_completeComponent"_qs;
+ compiled.completeComponent.returnType = u"void"_qs;
+ compiled.finalizeComponent.name = u"QML_finalizeComponent"_qs;
+ compiled.finalizeComponent.returnType = u"void"_qs;
+ 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);
+ 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());
+ if (documentRoot) {
+ compiled.init.parameterList = { engine, ctxtdata, finalizeFlag, callSpecialMethodFlag };
+ compiled.endInit.parameterList = { engine, finalizeFlag };
+ compiled.completeComponent.parameterList = { callSpecialMethodFlag };
+ compiled.finalizeComponent.parameterList = { callSpecialMethodFlag };
+ } else {
+ compiled.init.parameterList = { engine, ctxtdata };
+ compiled.endInit.parameterList = { engine, CodeGeneratorUtility::compilationUnitVariable };
+ }
+
+ if (baseTypeIsCompiledQml) {
+ // call baseline ctor of the QML-originated base class. it also takes
+ // care of QObject::setParent() call
+ compiled.baselineCtor.initializerList = { baseClass + u"(parent)" };
+ } else {
+ // default call to ctor is enough, but QQml_setParent_noEvent() - is
+ // needed (note, this is a faster version of QObject::setParent())
+ compiled.baselineCtor.body << u"QQml_setParent_noEvent(this, " + parent.name + u");";
+ }
+
+ compiled.externalCtor.initializerList = { compiled.baselineCtor.name + u"(parent)" };
+ if (documentRoot) {
+ compiled.externalCtor.body << u"// document root:"_qs;
+ compiled.endInit.body << u"auto " + CodeGeneratorUtility::compilationUnitVariable.name
+ + u" = " + u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl("
+ + m_urlMethod.name + u"());";
+
+ // call init method of the document root
+ compiled.externalCtor.body << compiled.init.name
+ + u"(engine, QQmlContextData::get(engine->rootContext()), /* finalize */ "
+ u"true, /* call special method */ true);";
+ } else {
+ compiled.externalCtor.body << u"// not document root:"_qs;
+ compiled.externalCtor.body
+ << compiled.init.name + u"(engine, QQmlData::get(parent)->outerContext);";
+ }
+
+ compiled.init.body << u"Q_UNUSED(engine);"_qs;
+ if (documentRoot) {
+ compiled.init.body << u"Q_UNUSED(" + callSpecialMethodFlag.name + u")";
+ compiled.completeComponent.body << u"Q_UNUSED(" + callSpecialMethodFlag.name + u")";
+ compiled.finalizeComponent.body << u"Q_UNUSED(" + callSpecialMethodFlag.name + u")";
+ }
+
+ // compiled.init.body << u"Q_UNUSED(" + finalizeFlag.name + u");";
+ compiled.init.body << u"auto context = parentContext;"_qs;
+ // TODO: context hierarchy is way-over-the-top complicated already
+
+ // -1. if the parent scope of this type has base type as compiled qml and
+ // this parent scope is not a root object, we have to go one level up in the
+ // context. in a nutshell:
+ // * parentScope->outerContext == parentContext of this type
+ // * parentScope->outerContext != context of this document
+ // * parentScope->outerContext is a child of context of this document
+ // > to ensure correct context, we must use context->parent() instead of
+ // > parentContext
+ if (QQmlJSScope::ConstPtr parent = object.type->parentScope(); parent
+ && m_qmlCompiledBaseTypes.contains(parent->baseTypeName())
+ && m_typeToObjectIndex[parent] != 0) {
+ compiled.init.body << u"// NB: context->parent() is the context of the root "_qs;
+ compiled.init.body << u"context = context->parent();"_qs;
+ }
+
+ // 0. call parent's init if necessary
+ if (baseTypeIsCompiledQml) {
+ compiled.init.body << u"// 0. call parent's init"_qs;
+ QString lhs;
+ if (documentRoot)
+ lhs = u"context = "_qs;
+ const QString callParserStatusSpecialMethod = buildCallSpecialMethodValue(
+ documentRoot, callSpecialMethodFlag.name, hasParserStatusInterface);
+ compiled.init.body << lhs + baseClass + u"::" + compiled.init.name
+ + u"(engine, context, /* finalize */ false, /* call special method */ "
+ + callParserStatusSpecialMethod + u");";
+
+ compiled.completeComponent.body << u"// call parent's completeComponent"_qs;
+ compiled.completeComponent.body << baseClass + u"::" + compiled.completeComponent.name
+ + u"(" + callParserStatusSpecialMethod + u");";
+
+ const QString callFinalizerHookSpecialMethod = buildCallSpecialMethodValue(
+ documentRoot, callSpecialMethodFlag.name, hasFinalizerHookInterface);
+ compiled.finalizeComponent.body << u"// call parent's finalizeComponent"_qs;
+ compiled.finalizeComponent.body << baseClass + u"::" + compiled.finalizeComponent.name
+ + u"(" + callFinalizerHookSpecialMethod + u");";
+
+ compiled.handleOnCompleted.body << u"// call parent's Component.onCompleted handler"_qs;
+ compiled.handleOnCompleted.body
+ << baseClass + u"::" + compiled.handleOnCompleted.name + u"();";
+ }
+ // 1. create new context through QQmlCppContextRegistrator
+ if (documentRoot) {
+ Q_ASSERT(objectIndex == 0);
+ compiled.init.body << u"// 1. create context for this type (root)"_qs;
+ compiled.init.body
+ << QStringLiteral(
+ "context = %1->createInternalContext(%1->compilationUnitFromUrl(%2()), "
+ "context, 0, true);")
+ .arg(u"QQmlEnginePrivate::get(engine)"_qs, m_urlMethod.name);
+ } else {
+ // non-root objects adopt parent context and use that one instead of
+ // creating own context
+ compiled.init.body << u"// 1. use current as context of this type (non-root)"_qs;
+ compiled.init.body << u"// context = context;"_qs;
+ }
+
+ // TODO: optimize step 2: do we need context = parentContext? simplify
+ // QQmlCppContextRegistrator::set also
+
+ // 2.
+ if (baseTypeIsCompiledQml && !documentRoot) {
+ } else { // !baseTypeIsCompiledQml || documentRoot
+ // set up current context
+ compiled.init.body << u"// 2. normal flow, set context for this object"_qs;
+ const QString enumValue = documentRoot ? u"QQmlContextData::DocumentRoot"_qs
+ : u"QQmlContextData::OrdinaryObject"_qs;
+ compiled.init.body << QStringLiteral(
+ "%1->setInternalContext(this, context, QQmlContextData::%2);")
+ .arg(u"QQmlEnginePrivate::get(engine)"_qs, enumValue);
+ if (documentRoot)
+ compiled.init.body << u"context->setContextObject(this);"_qs;
+ }
+ // 3. set id if it's present in the QML document
+ if (!m_doc->stringAt(object.irObject->idNameIndex).isEmpty()) {
+ Q_ASSERT(object.irObject->id >= 0);
+ compiled.init.body << u"// 3. set id since it exists"_qs;
+ compiled.init.body << u"context->setIdValue(" + QString::number(object.irObject->id)
+ + u", this);";
+ }
+
+ // TODO: we might want to optimize storage space when there are no object
+ // bindings, but this requires deep checking (e.g. basically go over all
+ // bindings and all bindings of attached/grouped properties)
+ compiled.init.body << u"// create objects for object bindings in advance:"_qs;
+ // TODO: support private and protected variables
+ compiled.variables.emplaceBack(CodeGeneratorUtility::childrenOffsetVariable);
+ compiled.init.body << CodeGeneratorUtility::childrenOffsetVariable.name
+ + u" = QObject::children().size();";
+
+ // magic step: if the type has a QQmlParserStatus interface, we should call
+ // it's method here
+ if (hasParserStatusInterface) {
+ const QString indent = documentRoot ? u" "_qs : QString();
+
+ compiled.init.body << u"// this type has QQmlParserStatus interface:"_qs;
+ compiled.init.body << u"Q_ASSERT(dynamic_cast<QQmlParserStatus *>(this) != nullptr);"_qs;
+ if (documentRoot)
+ compiled.init.body << u"if (" + callSpecialMethodFlag.name + u")";
+ compiled.init.body << indent + u"this->classBegin();";
+
+ compiled.completeComponent.lastLines << u"// this type has QQmlParserStatus interface:"_qs;
+ compiled.completeComponent.lastLines
+ << u"Q_ASSERT(dynamic_cast<QQmlParserStatus *>(this) != nullptr);"_qs;
+ if (documentRoot)
+ compiled.completeComponent.lastLines << u"if (" + callSpecialMethodFlag.name + u")";
+ compiled.completeComponent.lastLines << indent + u"this->componentComplete();";
+ }
+
+ // magic step: if the type has a QQmlFinalizerHook interface, we should call
+ // it's method here
+ if (hasFinalizerHookInterface) {
+ QString indent;
+ compiled.finalizeComponent.lastLines << u"// this type has QQmlFinalizerHook interface:"_qs;
+ compiled.finalizeComponent.lastLines
+ << u"Q_ASSERT(dynamic_cast<QQmlFinalizerHook *>(this) != nullptr);"_qs;
+ if (documentRoot) {
+ compiled.finalizeComponent.lastLines << u"if (" + callSpecialMethodFlag.name + u")";
+ indent = u" "_qs;
+ }
+ compiled.finalizeComponent.lastLines << indent + u"this->componentFinalized();"_qs;
+ }
+
+ // NB: step 4 - and everything below that - is done at the very end of QML
+ // init and so we use lastLines. we need to make sure that objects from
+ // object bindings are created (and initialized) before we start the
+ // finalization: we need fully-set context at the beginning of QML finalize.
+
+ // 4. finalize if necessary
+ if (documentRoot) {
+ compiled.init.lastLines << u"// 4. document root, call finalize"_qs;
+ compiled.init.lastLines << u"if (" + finalizeFlag.name + u") {";
+ compiled.init.lastLines << u" " + compiled.endInit.name
+ + u"(engine, /* finalize */ true);";
+ compiled.init.lastLines << u"}"_qs;
+ }
+ // return
+ compiled.init.lastLines << u"return context;"_qs;
+
+ // TODO: is property update group needed?
+ compiled.endInit.body << u"Q_UNUSED(engine);"_qs;
+ compiled.endInit.body << u"Q_UNUSED(" + CodeGeneratorUtility::compilationUnitVariable.name
+ + u")"_qs;
+ // compiled.endInit.body << u"Q_UNUSED(" + finalizeFlag.name + u");";
+ if (baseTypeIsCompiledQml) {
+ compiled.endInit.body << u"{ // call parent's finalize"_qs;
+ compiled.endInit.body << baseClass + u"::" + compiled.endInit.name
+ + u"(engine, /* finalize */ false);";
+ 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;
+
+ // add basic MOC stuff
+ compiled.mocCode = {
+ u"Q_OBJECT"_qs,
+ (m_isAnonymous ? u"QML_ANONYMOUS"_qs : u"QML_ELEMENT"_qs),
+ };
+
+ 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());
+ for (auto it = enums.cbegin(); it != enums.cend(); ++it)
+ compileEnum(compiled, it.value());
+
+ // compile properties in order
+ auto properties = object.type->ownProperties().values();
+ compiled.variables.reserve(properties.size());
+ std::sort(properties.begin(), properties.end(),
+ [](const QQmlJSMetaProperty &x, const QQmlJSMetaProperty &y) {
+ return x.index() < y.index();
+ });
+ for (const QQmlJSMetaProperty &p : properties) {
+ if (p.index() == -1) {
+ recordError(object.type->sourceLocation(),
+ u"Property '" + p.propertyName()
+ + u"' has incomplete information (internal error)");
+ continue;
+ }
+ if (p.isAlias()) {
+ compileAlias(compiled, p, object.type);
+ } else { // normal property
+ compileProperty(compiled, p, object.type);
+ }
+ }
+
+ // compile methods
+ QHash<QString, const QmlIR::Function *> irFunctionsByName;
+ std::for_each(object.irObject->functionsBegin(), object.irObject->functionsEnd(),
+ [&](const QmlIR::Function &function) {
+ irFunctionsByName.insert(m_doc->stringAt(function.nameIndex),
+ std::addressof(function));
+ });
+ const auto methods = object.type->ownMethods();
+ compiled.functions.reserve(methods.size());
+ for (auto it = methods.cbegin(); it != methods.cend(); ++it) {
+ const QmlIR::Function *irFunction = irFunctionsByName.value(it.key(), nullptr);
+ compileMethod(compiled, it.value(), irFunction, object);
+ }
+
+ // NB: just clearing is safe since we do not call this function recursively
+ m_localChildrenToEndInit.clear();
+ m_localChildrenToFinalize.clear();
+
+ // compile bindings
+ const auto sortedBindings =
+ toOrderedSequence(object.irObject->bindingsBegin(), object.irObject->bindingsEnd(),
+ object.irObject->bindingCount());
+
+ // for (auto it : sortedBindings)
+ // compileBinding(compiled, *it, object, u"this"_qs);
+
+ // NB: can't use lower_bound since it only accepts a value, not a unary
+ // predicate
+ auto scriptBindingsBegin =
+ std::find_if(sortedBindings.cbegin(), sortedBindings.cend(),
+ [](auto it) { return it->type == QmlIR::Binding::Type_Script; });
+ auto it = sortedBindings.cbegin();
+ for (; it != scriptBindingsBegin; ++it)
+ compileBinding(compiled, **it, object, { object.type, u"this"_qs, u""_qs, false });
+
+ // NB: finalize children before creating/setting script bindings for `this`
+ for (qsizetype i = 0; i < m_localChildrenToEndInit.size(); ++i) {
+ compiled.endInit.body << m_localChildrenToEndInit.at(i) + u"->" + compiled.endInit.name
+ + u"(engine, " + CodeGeneratorUtility::compilationUnitVariable.name + u");";
+ }
+
+ const auto buildChildAtString = [](const QQmlJSScope::ConstPtr &type,
+ const QString &i) -> QString {
+ return u"static_cast<" + type->internalName() + u"* >(QObject::children().at("
+ + CodeGeneratorUtility::childrenOffsetVariable.name + u" + " + i + u"))";
+ };
+ // TODO: there's exceptional redundancy (!) in this code generation part
+ for (qsizetype i = 0; i < m_localChildrenToFinalize.size(); ++i) {
+ QString index = QString::number(i);
+ const QQmlJSScope::ConstPtr &child = m_localChildrenToFinalize.at(i);
+ const QString childAt = buildChildAtString(child, index);
+ // NB: children are not document roots, so all special methods are argument-less
+ compiled.completeComponent.body
+ << childAt + u"->" + compiled.completeComponent.name + u"();";
+ compiled.finalizeComponent.body
+ << childAt + u"->" + compiled.finalizeComponent.name + u"();";
+ compiled.handleOnCompleted.body
+ << childAt + u"->" + compiled.handleOnCompleted.name + u"();";
+ }
+
+ for (; it != sortedBindings.cend(); ++it)
+ compileBinding(compiled, **it, object, { object.type, u"this"_qs, u""_qs, false });
+
+ // add finalization steps only to document root
+ if (documentRoot) {
+ compiled.endInit.body << u"if (" + finalizeFlag.name + u") {";
+
+ // at this point, all bindings must've been finished, thus, we need:
+ // 1. componentComplete()
+ // 2. finalize callbacks / componentFinalized()
+ // 3. Component.onCompleted()
+
+ // 1.
+ compiled.endInit.body << u" this->" + compiled.completeComponent.name
+ + u"(/* complete component */ true);";
+
+ // 2
+ compiled.endInit.body << u" this->" + compiled.finalizeComponent.name
+ + u"(/* finalize component */ true);";
+
+ // 3.
+ compiled.endInit.body << u" this->" + compiled.handleOnCompleted.name + u"();";
+
+ compiled.endInit.body << u"}"_qs;
+ }
+
+ // compiled.endInit.body << u"Qt::endPropertyUpdateGroup();"_qs;
+}
+void CodeGenerator::compileEnum(QQmlJSAotObject &current, const QQmlJSMetaEnum &e)
+{
+ const auto intValues = e.values();
+ QStringList values;
+ values.reserve(intValues.size());
+ std::transform(intValues.cbegin(), intValues.cend(), std::back_inserter(values),
+ [](int x) { return QString::number(x); });
+
+ // structure: (C++ type name, enum keys, enum values, MOC line)
+ current.enums.emplaceBack(e.name(), e.keys(), std::move(values),
+ u"Q_ENUM(%1)"_qs.arg(e.name()));
+}
+
+#if 1
+void CodeGenerator::compileProperty(QQmlJSAotObject &current, const QQmlJSMetaProperty &p,
+ const QQmlJSScope::ConstPtr &owner)
+{
+ Q_ASSERT(!p.isAlias()); // will be handled separately
+ Q_ASSERT(p.type());
+
+ const QString name = p.propertyName();
+ const QString variableName = u"m_" + name;
+ const QString underlyingType = getUnderlyingType(p);
+ // only check for isList() here as it needs some special arrangements.
+ // otherwise, getUnderlyingType() handles the specifics of a type in C++
+ if (p.isList()) {
+ const QString storageName = variableName + u"_storage";
+ current.variables.emplaceBack(u"QList<" + p.type()->internalName() + u" *>", storageName,
+ QString());
+ 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
+ // property in Qt/QML contexts
+ QStringList mocPieces;
+ mocPieces.reserve(10);
+ mocPieces << underlyingType << name;
+
+ Qml2CppPropertyData compilationData(p);
+
+ // 1. add setter and getter
+ if (p.isWritable()) {
+ QQmlJSAotMethod setter {};
+ setter.returnType = u"void"_qs;
+ setter.name = compilationData.write;
+ // QQmlJSAotVariable
+ setter.parameterList.emplaceBack(
+ CodeGeneratorUtility::wrapInConstRefIfNotAPointer(underlyingType), name + u"_",
+ u""_qs);
+ setter.body << variableName + u".setValue(" + name + u"_);";
+ setter.body << u"emit " + compilationData.notify + u"();";
+ current.functions.emplaceBack(setter);
+ mocPieces << u"WRITE"_qs << setter.name;
+ }
+
+ QQmlJSAotMethod getter {};
+ getter.returnType = underlyingType;
+ getter.name = compilationData.read;
+ getter.body << u"return " + variableName + u".value();";
+ current.functions.emplaceBack(getter);
+ mocPieces << u"READ"_qs << getter.name;
+
+ // 2. add bindable
+ QQmlJSAotMethod bindable {};
+ bindable.returnType = u"QBindable<" + underlyingType + u">";
+ bindable.name = compilationData.bindable;
+ bindable.body << u"return QBindable<" + underlyingType + u">(std::addressof(" + variableName
+ + u"));";
+ current.functions.emplaceBack(bindable);
+ mocPieces << u"BINDABLE"_qs << bindable.name;
+
+ // 3. add/check notify (actually, this is already done inside QmltcVisitor)
+
+ if (owner->isPropertyRequired(name))
+ mocPieces << u"REQUIRED"_qs;
+
+ // 4. add moc entry
+ // e.g. Q_PROPERTY(QString p READ getP WRITE setP BINDABLE bindableP)
+ current.mocCode << u"Q_PROPERTY(" + mocPieces.join(u" "_qs) + u")";
+
+ // 5. add extra moc entry if this property is marked default
+ if (name == owner->defaultPropertyName())
+ current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_qs.arg(name);
+
+ // structure: (C++ type name, name, C++ class name, C++ signal name)
+ current.properties.emplaceBack(underlyingType, variableName, current.cppType,
+ compilationData.notify);
+}
+#else
+void CodeGenerator::compileProperty(QQmlJSAotObject &current, const QQmlJSMetaProperty &p,
+ const QQmlJSScope::ConstPtr &owner)
+{
+ if (p.isAlias()) // ignore aliases here, they are processed separately
+ return;
+
+ Q_ASSERT(p.type());
+
+ const QString inputName = p.propertyName() + u"_";
+ const QString memberName = u"m_" + p.propertyName();
+ QString underlyingType = p.type()->internalName();
+ // NB: can be a pointer or a list, can't be both (list automatically assumes
+ // that it holds pointers though). check isList() first, as list<QtObject>
+ // would be both a list and a pointer (weird).
+ if (p.isList()) {
+ current.variables.emplaceBack(u"QList<" + underlyingType + u" *>", memberName + u"_storage",
+ QString());
+ current.baselineCtor.initializerList.emplaceBack(
+ memberName + u"(QQmlListProperty<" + underlyingType + u">(this, std::addressof("
+ + memberName + u"_storage)))");
+ underlyingType = u"QQmlListProperty<" + underlyingType + u">";
+ } else if (isPointer(p)) {
+ underlyingType += u'*';
+ }
+
+ Qml2CppPropertyData compilationData(p);
+ // structure:
+ // C++ type name
+ // name
+ // default value
+ current.variables.emplaceBack(u"QProperty<" + underlyingType + u">", memberName, QString());
+
+ // with property added, also add relevant moc code, so that we can use the
+ // property in QML contexts
+ QStringList mocLines;
+ mocLines.reserve(10);
+ mocLines << underlyingType << p.propertyName();
+
+ // 1. add setter and getter
+ QQmlJSAotMethod setter {};
+ setter.returnType = u"void"_qs;
+ setter.name = compilationData.write;
+ // QQmlJSAotVariable
+ setter.parameterList.emplaceBack(
+ CodeGeneratorUtility::wrapInConstRefIfNotAPointer(underlyingType), inputName, u""_qs);
+ setter.body << memberName + u".setValue(" + inputName + u"_);";
+ // TODO: Qt.binding() (and old bindings?) requires signal emission
+ setter.body << u"emit " + compilationData.notify + u"();";
+ current.functions.emplaceBack(setter);
+ mocLines << u"WRITE"_qs << setter.name;
+
+ QQmlJSAotMethod getter {};
+ getter.returnType = underlyingType;
+ getter.name = compilationData.read;
+ getter.body << u"return " + memberName + u".value();";
+ current.functions.emplaceBack(getter);
+ mocLines << u"READ"_qs << getter.name;
+
+ // 2. add bindable
+ QQmlJSAotMethod bindable {};
+ bindable.returnType = u"QBindable<" + underlyingType + u">";
+ bindable.name = compilationData.bindable;
+ bindable.body << u"return QBindable<" + underlyingType + u">(std::addressof(" + memberName
+ + u"));";
+ current.functions.emplaceBack(bindable);
+ mocLines << u"BINDABLE"_qs << bindable.name;
+
+ // 3. add notify
+ QQmlJSAotMethod notify {};
+ notify.returnType = u"void"_qs;
+ notify.name = compilationData.notify;
+ notify.type = QQmlJSMetaMethod::Signal;
+ current.functions.emplaceBack(notify);
+
+ // 4. add moc entry
+ // Q_PROPERTY(QString text READ text WRITE setText BINDABLE bindableText NOTIFY textChanged)
+ current.mocCode << u"Q_PROPERTY(" + mocLines.join(u" "_qs) + u")";
+
+ // 5. add extra moc entry if this property is default one
+ if (p.propertyName() == owner->defaultPropertyName()) {
+ // Q_CLASSINFO("DefaultProperty", propertyName)
+ current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_qs.arg(p.propertyName());
+ }
+}
+#endif
+
+void CodeGenerator::compileAlias(QQmlJSAotObject &current, const QQmlJSMetaProperty &alias,
+ const QQmlJSScope::ConstPtr &owner)
+{
+ const QString aliasName = alias.propertyName();
+ Q_ASSERT(!aliasName.isEmpty());
+
+ QStringList aliasExprBits = alias.aliasExpression().split(u'.');
+ Q_ASSERT(!aliasExprBits.isEmpty());
+ QString idString = aliasExprBits.front();
+ Q_ASSERT(!idString.isEmpty());
+ QQmlJSScope::ConstPtr type = m_localTypeResolver->scopeForId(idString, owner);
+ Q_ASSERT(type);
+ const bool aliasToProperty = aliasExprBits.size() > 1; // and not an object
+
+ QString latestAccessor = u"this"_qs;
+ QString latestAccessorNonPrivate; // TODO: crutch for notify
+ QStringList prologue;
+ QStringList writeSpecificEpilogue;
+
+ if (owner != type) { // cannot start at `this`, need to fetch object through context at run time
+ Q_ASSERT(m_typeToObjectIndex.contains(type)
+ && m_typeToObjectIndex[type] <= m_objects.size());
+ const int id = m_objects[m_typeToObjectIndex[type]].irObject->id;
+ Q_ASSERT(id >= 0); // since the type is found by id, it must have an id
+
+ // TODO: the context fetching must be done once in the notify code
+ // (across multiple alias properties). now there's a huge duplication
+ prologue << u"auto context = QQmlData::get(this)->outerContext;"_qs;
+ // there's a special case: when `this` type has compiled QML type as a base
+ // type and it is not a root, it has a non-root first context, so we need to
+ // step one level up
+ if (m_qmlCompiledBaseTypes.contains(owner->baseTypeName())
+ && m_typeToObjectIndex[owner] != 0) {
+ Q_ASSERT(!owner->baseTypeName().isEmpty());
+ prologue << u"// `this` is special: not a root and its base type is compiled"_qs;
+ prologue << u"context = context->parent().data();"_qs;
+ }
+ // doing the above allows us to lookup id object by integer, which is fast
+ latestAccessor = u"alias_objectById_" + idString; // unique enough
+ if (aliasToProperty) {
+ prologue << u"auto " + latestAccessor + u" = static_cast<" + type->internalName()
+ + u"*>(context->idValue(" + QString::number(id) + u"));";
+ } else {
+ // kind of a crutch for object aliases: expect that the user knows
+ // what to do and so accept QObject*
+ prologue << u"QObject *" + latestAccessor + u" = context->idValue("
+ + QString::number(id) + u");";
+ }
+ prologue << u"Q_ASSERT(" + latestAccessor + u");";
+ }
+
+ struct AliasData
+ {
+ QString underlyingType;
+ QString readLine;
+ QString writeLine;
+ QString bindableLine;
+ } info; // note, we only really need this crutch to cover the alias to object case
+ QQmlJSMetaProperty resultingProperty;
+
+ if (aliasToProperty) {
+ // the following code goes to prologue section. the idea of that is to
+ // construct the necessary code to fetch the resulting property
+ for (qsizetype i = 1; i < aliasExprBits.size() - 1; ++i) {
+ const QString &bit = qAsConst(aliasExprBits)[i];
+ Q_ASSERT(!bit.isEmpty());
+ Q_ASSERT(type);
+ QQmlJSMetaProperty p = type->property(bit);
+
+ QString currAccessor =
+ CodeGeneratorUtility::generate_getPrivateClass(latestAccessor, p);
+ if (p.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Value) {
+ // we need to read the property to a local variable and then
+ // write the updated value once the actual operation is done
+ QString localVariableName = u"alias_" + bit; // should be fairly unique
+ prologue << u"auto " + localVariableName + u" = " + currAccessor + u"->" + p.read()
+ + u"();";
+ writeSpecificEpilogue
+ << currAccessor + u"->" + p.write() + u"(" + localVariableName + u");";
+ // NB: since accessor becomes a value type, wrap it into an
+ // addressof operator so that we could access it as a pointer
+ currAccessor = CodeGeneratorUtility::generate_addressof(localVariableName); // reset
+ } else {
+ currAccessor += u"->" + p.read() + u"()"; // amend
+ }
+
+ type = p.type();
+ latestAccessor = currAccessor;
+ }
+
+ resultingProperty = type->property(aliasExprBits.back());
+ latestAccessorNonPrivate = latestAccessor;
+ latestAccessor =
+ CodeGeneratorUtility::generate_getPrivateClass(latestAccessor, resultingProperty);
+ info.underlyingType = resultingProperty.type()->internalName();
+ if (resultingProperty.isList()) {
+ info.underlyingType = u"QQmlListProperty<" + info.underlyingType + u">";
+ } else if (isPointer(resultingProperty)) {
+ info.underlyingType += u"*"_qs;
+ }
+ // reset to generic type when having alias to id:
+ if (m_aliasesToIds.contains(resultingProperty))
+ info.underlyingType = u"QObject*"_qs;
+
+ // READ must always be present
+ info.readLine = latestAccessor + u"->" + resultingProperty.read() + u"()";
+ if (QString setName = resultingProperty.write(); !setName.isEmpty())
+ info.writeLine = latestAccessor + u"->" + setName + u"(%1)";
+ if (QString bindableName = resultingProperty.bindable(); !bindableName.isEmpty())
+ info.bindableLine = latestAccessor + u"->" + bindableName + u"()";
+ } else {
+ info.underlyingType = u"QObject*"_qs;
+ info.readLine = latestAccessor;
+ // NB: id-pointing aliases are read-only, also alias to object is not
+ // bindable since it's not a QProperty itself
+ }
+
+ QStringList mocLines;
+ mocLines.reserve(10);
+ mocLines << info.underlyingType << aliasName;
+
+ Qml2CppPropertyData compilationData(aliasName);
+ // 1. add setter and getter
+ if (!info.readLine.isEmpty()) {
+ QQmlJSAotMethod getter {};
+ getter.returnType = info.underlyingType;
+ getter.name = compilationData.read;
+ getter.body += prologue;
+ getter.body << u"return " + info.readLine + u";";
+ // getter.body += writeSpecificEpilogue;
+ current.functions.emplaceBack(getter);
+ mocLines << u"READ"_qs << getter.name;
+ } // else always an error?
+
+ if (!info.writeLine.isEmpty()) {
+ QQmlJSAotMethod setter {};
+ setter.returnType = u"void"_qs;
+ setter.name = compilationData.write;
+
+ QList<QQmlJSMetaMethod> methods = type->methods(resultingProperty.write());
+ if (methods.isEmpty()) {
+ // QQmlJSAotVariable
+ setter.parameterList.emplaceBack(
+ CodeGeneratorUtility::wrapInConstRefIfNotAPointer(info.underlyingType),
+ aliasName + u"_", u""_qs);
+ } else {
+ setter.parameterList = compileMethodParameters(methods.at(0).parameterNames(),
+ methods.at(0).parameterTypes(), true);
+ }
+
+ setter.body += prologue;
+ if (aliasToProperty) {
+ QStringList parameterNames;
+ parameterNames.reserve(setter.parameterList.size());
+ std::transform(setter.parameterList.cbegin(), setter.parameterList.cend(),
+ std::back_inserter(parameterNames),
+ [](const QQmlJSAotVariable &x) { return x.name; });
+ QString commaSeparatedParameterNames = parameterNames.join(u", "_qs);
+ setter.body << info.writeLine.arg(commaSeparatedParameterNames) + u";";
+ } else {
+ setter.body << info.writeLine + u";";
+ }
+ setter.body += writeSpecificEpilogue;
+ current.functions.emplaceBack(setter);
+ mocLines << u"WRITE"_qs << setter.name;
+ }
+
+ // 2. add bindable
+ if (!info.bindableLine.isEmpty()) {
+ QQmlJSAotMethod bindable {};
+ bindable.returnType = u"QBindable<" + info.underlyingType + u">";
+ bindable.name = compilationData.bindable;
+ bindable.body += prologue;
+ bindable.body << u"return " + info.bindableLine + u";";
+ current.functions.emplaceBack(bindable);
+ mocLines << u"BINDABLE"_qs << bindable.name;
+ }
+ // 3. add notify - which is pretty special
+ if (QString notifyName = resultingProperty.notify(); !notifyName.isEmpty()) {
+ // notify is very special (even more than reasonable)
+ current.endInit.body << u"{ // alias notify connection:"_qs;
+ // TODO: part of the prologue (e.g. QQmlData::get(this)->outerContext)
+ // must be shared across different notifies (to speed up finalize)
+ current.endInit.body += prologue;
+ // TODO: use non-private accessor since signals must exist on the public
+ // type, not on the private one -- otherwise, they can't be used from
+ // C++ which is not what's wanted (this is a mess)
+ current.endInit.body << u"QObject::connect(" + latestAccessorNonPrivate + u", &"
+ + type->internalName() + u"::" + notifyName + u", this, &" + current.cppType
+ + u"::" + compilationData.notify + u");";
+ current.endInit.body << u"}"_qs;
+ }
+
+ // 4. add moc entry
+ // Q_PROPERTY(QString text READ text WRITE setText BINDABLE bindableText NOTIFY textChanged)
+ current.mocCode << u"Q_PROPERTY(" + mocLines.join(u" "_qs) + u")";
+
+ // 5. add extra moc entry if this alias is default one
+ if (aliasName == owner->defaultPropertyName()) {
+ // Q_CLASSINFO("DefaultProperty", propertyName)
+ current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_qs.arg(aliasName);
+ }
+}
+
+void CodeGenerator::compileMethod(QQmlJSAotObject &current, const QQmlJSMetaMethod &m,
+ const QmlIR::Function *f, const CodeGenObject &object)
+{
+ Q_UNUSED(object);
+ const QString returnType = figureReturnType(m);
+
+ const auto paramNames = m.parameterNames();
+ const auto paramTypes = m.parameterTypes();
+ Q_ASSERT(paramNames.size() == paramTypes.size());
+ const QList<QQmlJSAotVariable> paramList = compileMethodParameters(paramNames, paramTypes);
+
+ const auto methodType = QQmlJSMetaMethod::Type(m.methodType());
+
+ QStringList code;
+ // the function body code only makes sense if the method is not a signal -
+ // and there is a corresponding QmlIR::Function
+ if (f) {
+ Q_ASSERT(methodType != QQmlJSMetaMethod::Signal);
+ code += CodeGeneratorUtility::generate_callExecuteRuntimeFunction(
+ m_urlMethod.name + u"()",
+ relativeToAbsoluteRuntimeIndex(m_doc, object.irObject, f->index), u"this"_qs,
+ returnType, paramList);
+ }
+
+ QQmlJSAotMethod compiled {};
+ compiled.returnType = returnType;
+ compiled.name = m.methodName();
+ compiled.parameterList = std::move(paramList);
+ compiled.body = std::move(code);
+ 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?
+ current.functions.emplaceBack(compiled);
+}
+
+template<typename Iterator>
+static QString getPropertyOrAliasNameFromIr(const QmlIR::Document *doc, Iterator first, int pos)
+{
+ std::advance(first, pos); // assume that first is correct - due to earlier check
+ return doc->stringAt(first->nameIndex);
+}
+
+void CodeGenerator::compileBinding(QQmlJSAotObject &current, const QmlIR::Binding &binding,
+ const CodeGenObject &object,
+ const CodeGenerator::AccessorData &accessor)
+{
+ // 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()) {
+ // if empty, try default property
+ for (QQmlJSScope::ConstPtr t = object.type->baseType(); t && propertyName.isEmpty();
+ t = t->baseType())
+ propertyName = t->defaultPropertyName();
+ }
+ Q_ASSERT(!propertyName.isEmpty());
+ QQmlJSMetaProperty p = object.type->property(propertyName);
+ QQmlJSScope::ConstPtr propertyType = p.type();
+ // Q_ASSERT(propertyType); // TODO: doesn't work with signals
+
+ const auto addPropertyLine = [&](const QString &propertyName, const QQmlJSMetaProperty &p,
+ const QString &value, bool constructQVariant = false) {
+ // TODO: there mustn't be this special case. instead, alias resolution
+ // must be done in QQmlJSImportVisitor subclass, that would handle this
+ // mess (see resolveValidateOrSkipAlias() in qml2cppdefaultpasses.cpp)
+ if (p.isAlias() && m_qmlCompiledBaseTypes.contains(object.type->baseTypeName())) {
+ qCDebug(lcCodeGenerator) << u"Property '" + propertyName + u"' is an alias on type '"
+ + object.type->internalName()
+ + u"' which is a QML type compiled to C++. The assignment is special "
+ u"in this case";
+ current.endInit.body += CodeGeneratorUtility::generate_assignToSpecialAlias(
+ object.type, propertyName, p, value, accessor.name, constructQVariant);
+ } else {
+ current.endInit.body += CodeGeneratorUtility::generate_assignToProperty(
+ object.type, propertyName, p, value, accessor.name, constructQVariant);
+ }
+ };
+
+ switch (binding.type) {
+ case QmlIR::Binding::Type_Boolean: {
+ addPropertyLine(propertyName, p, binding.value.b ? u"true"_qs : u"false"_qs);
+ break;
+ }
+ case QmlIR::Binding::Type_Number: {
+ QString value = m_doc->javaScriptCompilationUnit.bindingValueAsString(&binding);
+ addPropertyLine(propertyName, p, value);
+ break;
+ }
+ case QmlIR::Binding::Type_String: {
+ const QString str = m_doc->stringAt(binding.stringIndex);
+ addPropertyLine(propertyName, p, CodeGeneratorUtility::createStringLiteral(str));
+ break;
+ }
+ case QmlIR::Binding::Type_TranslationById: { // TODO: add test
+ const QV4::CompiledData::TranslationData &translation =
+ m_doc->javaScriptCompilationUnit.unitData()
+ ->translations()[binding.value.translationDataIndex];
+ const QString id = m_doc->stringAt(binding.stringIndex);
+ addPropertyLine(propertyName, p,
+ u"qsTrId(\"" + id + u"\", " + QString::number(translation.number) + u")");
+ break;
+ }
+ case QmlIR::Binding::Type_Translation: { // TODO: add test
+ const QV4::CompiledData::TranslationData &translation =
+ m_doc->javaScriptCompilationUnit.unitData()
+ ->translations()[binding.value.translationDataIndex];
+ int lastSlash = m_url.lastIndexOf(QChar(u'/'));
+ const QStringView context = (lastSlash > -1)
+ ? QStringView { m_url }.mid(lastSlash + 1, m_url.length() - lastSlash - 5)
+ : QStringView();
+ const QString comment = m_doc->stringAt(translation.commentIndex);
+ const QString text = m_doc->stringAt(translation.stringIndex);
+
+ addPropertyLine(propertyName, p,
+ u"QCoreApplication::translate(\"" + context + u"\", \"" + text + u"\", \""
+ + comment + u"\", " + QString::number(translation.number) + u")");
+ break;
+ }
+ case QmlIR::Binding::Type_Script: {
+ QString bindingSymbolName = object.type->internalName() + u'_' + propertyName + u"_binding";
+ bindingSymbolName.replace(u'.', u'_'); // can happen with group properties
+ compileScriptBinding(current, binding, bindingSymbolName, object, propertyName,
+ propertyType, accessor);
+ break;
+ }
+ case QmlIR::Binding::Type_Object: {
+ // NB: object is compiled with compileObject, here just need to use it
+ const CodeGenObject &bindingObject = m_objects.at(binding.value.objectIndex);
+
+ // Note: despite a binding being set for `accessor`, we use "this" as a
+ // parent of a created object. Both attached and grouped properties are
+ // parented by "this", so lifetime-wise we should be fine. If not, we're
+ // in a fairly deep trouble, actually, since it's essential to use
+ // `this` (at least at present it seems to be so) - e.g. the whole logic
+ // of separating object binding into object creation and object binding
+ // setup relies on the fact that we use `this`
+ const QString qobjectParent = u"this"_qs;
+ const QString objectAddr = accessor.name;
+
+ if (binding.flags & QmlIR::Binding::IsOnAssignment) {
+ const QString onAssignmentName = u"onAssign_" + propertyName;
+ const auto uniqueId = UniqueStringId(current, onAssignmentName);
+ if (!m_onAssignmentObjectsCreated.contains(uniqueId)) {
+ m_onAssignmentObjectsCreated.insert(uniqueId);
+ current.init.body << u"new " + bindingObject.type->internalName() + u"(engine, "
+ + qobjectParent + u");";
+
+ // static_cast is fine, because we (must) know the exact type
+ current.endInit.body << u"auto " + onAssignmentName + u" = static_cast<"
+ + bindingObject.type->internalName()
+ + u" *>(QObject::children().at("
+ + CodeGeneratorUtility::childrenOffsetVariable.name + u" + "
+ + QString::number(m_localChildrenToEndInit.size()) + u"));";
+
+ m_localChildrenToEndInit.append(onAssignmentName);
+ m_localChildrenToFinalize.append(bindingObject.type);
+ }
+
+ // NB: we expect one "on" assignment per property, so creating
+ // QQmlProperty each time should be fine (unlike QQmlListReference)
+ current.endInit.body << u"{"_qs;
+ current.endInit.body << u"QQmlProperty qmlprop(" + objectAddr + u", QStringLiteral(\""
+ + propertyName + u"\"));";
+ current.endInit.body << u"QT_PREPEND_NAMESPACE(QQmlCppOnAssignmentHelper)::set("
+ + onAssignmentName + u", qmlprop);";
+ current.endInit.body << u"}"_qs;
+ break;
+ }
+
+ if (!propertyType) {
+ recordError(binding.location,
+ u"Binding on property '" + propertyName + u"' of unknown type");
+ return;
+ }
+
+ const auto setObjectBinding = [&](const QString &value) {
+ if (p.isList() || (binding.flags & QmlIR::Binding::IsListItem)) {
+ const QString refName = u"listref_" + propertyName;
+ const auto uniqueId = UniqueStringId(current, refName);
+ if (!m_listReferencesCreated.contains(uniqueId)) {
+ m_listReferencesCreated.insert(uniqueId);
+ // TODO: figure if Unicode support is needed here
+ current.endInit.body << u"QQmlListReference " + refName + u"(" + objectAddr
+ + u", QByteArrayLiteral(\"" + propertyName + u"\"));";
+ current.endInit.body << u"Q_ASSERT(" + refName + u".canAppend());";
+ }
+ current.endInit.body << refName + u".append(" + value + u");";
+ } else {
+ addPropertyLine(propertyName, p, value, /* through QVariant = */ true);
+ }
+ };
+
+ if (bindingObject.irObject->flags & QV4::CompiledData::Object::IsComponent) {
+ // NB: this one is special - and probably becomes a view delegate.
+ // object binding separation also does not apply here
+ const QString objectName = makeGensym(u"sc"_qs);
+ Q_ASSERT(m_typeToObjectIndex.contains(bindingObject.type));
+ Q_ASSERT(m_implicitComponentMapping.contains(
+ int(m_typeToObjectIndex[bindingObject.type])));
+ const int index =
+ m_implicitComponentMapping[int(m_typeToObjectIndex[bindingObject.type])];
+ current.endInit.body << u"{"_qs;
+ current.endInit.body << QStringLiteral(
+ "auto thisContext = QQmlData::get(%1)->outerContext;")
+ .arg(qobjectParent);
+ current.endInit.body << QStringLiteral(
+ "auto %1 = QQmlObjectCreator::createComponent(engine, "
+ "QQmlEnginePrivate::get(engine)->"
+ "compilationUnitFromUrl(%2()), %3, %4, thisContext);")
+ .arg(objectName, m_urlMethod.name,
+ QString::number(index), qobjectParent);
+ current.endInit.body << QStringLiteral("thisContext->installContext(QQmlData::get(%1), "
+ "QQmlContextData::OrdinaryObject);")
+ .arg(objectName);
+ setObjectBinding(objectName);
+ current.endInit.body << u"}"_qs;
+ break;
+ }
+
+ current.init.body << u"new " + bindingObject.type->internalName() + u"(engine, "
+ + qobjectParent + u");";
+
+ const QString objectName = makeGensym(u"o"_qs);
+
+ // static_cast is fine, because we (must) know the exact type
+ current.endInit.body << u"auto " + objectName + u" = static_cast<"
+ + bindingObject.type->internalName() + u" *>(QObject::children().at("
+ + CodeGeneratorUtility::childrenOffsetVariable.name + u" + "
+ + QString::number(m_localChildrenToEndInit.size()) + u"));";
+ setObjectBinding(objectName);
+
+ m_localChildrenToEndInit.append(objectName);
+ m_localChildrenToFinalize.append(bindingObject.type);
+ break;
+ }
+ case QmlIR::Binding::Type_AttachedProperty: {
+ Q_ASSERT(accessor.name == u"this"_qs); // TODO: doesn't have to hold, in fact
+ const auto attachedObject = m_objects.at(binding.value.objectIndex);
+ const auto &irObject = attachedObject.irObject;
+ const auto &type = attachedObject.type;
+ Q_ASSERT(irObject && type);
+ if (propertyName == u"Component"_qs) {
+ // TODO: there's a special QQmlComponentAttached, which has to be
+ // called? c.f. qqmlobjectcreator.cpp's finalize()
+ const auto compileComponent = [&](const QmlIR::Binding &b) {
+ Q_ASSERT(b.type == QmlIR::Binding::Type_Script);
+ compileScriptBindingOfComponent(current, irObject, type, b,
+ m_doc->stringAt(b.propertyNameIndex));
+ };
+ std::for_each(irObject->bindingsBegin(), irObject->bindingsEnd(), compileComponent);
+ } else {
+ const QString attachingTypeName = propertyName; // acts as an identifier
+ QString attachedTypeName = type->attachedTypeName(); // TODO: check if == internalName?
+ if (attachedTypeName.isEmpty()) // TODO: shouldn't happen ideally
+ attachedTypeName = type->baseTypeName();
+ const QString attachedMemberName = u"m_" + attachingTypeName;
+ const auto uniqueId = UniqueStringId(current, attachedMemberName);
+ // add attached type as a member variable to allow noop lookup
+ if (!m_attachedTypesAlreadyRegistered.contains(uniqueId)) {
+ m_attachedTypesAlreadyRegistered.insert(uniqueId);
+ current.variables.emplaceBack(attachedTypeName + u" *", attachedMemberName,
+ u"nullptr"_qs);
+ // Note: getting attached property is fairly expensive
+ const QString getAttachedPropertyLine = u"qobject_cast<" + attachedTypeName
+ + u" *>(qmlAttachedPropertiesObject<" + attachedTypeName
+ + u">(this, /* create = */ true))";
+ current.endInit.body
+ << attachedMemberName + u" = " + getAttachedPropertyLine + u";";
+ }
+
+ // compile bindings of the attached property
+ auto sortedBindings = toOrderedSequence(
+ irObject->bindingsBegin(), irObject->bindingsEnd(), irObject->bindingCount());
+ for (auto it : qAsConst(sortedBindings))
+ compileBinding(current, *it, attachedObject,
+ { object.type, attachedMemberName, propertyName, false });
+ }
+ break;
+ }
+ case QmlIR::Binding::Type_GroupProperty: {
+ Q_ASSERT(accessor.name == u"this"_qs); // TODO: doesn't have to hold, in fact
+ if (p.read().isEmpty()) {
+ recordError(binding.location,
+ u"READ function of group property '" + propertyName + u"' is unknown");
+ return;
+ }
+
+ const auto groupedObject = m_objects.at(binding.value.objectIndex);
+ const auto &irObject = groupedObject.irObject;
+ const auto &type = groupedObject.type;
+ Q_ASSERT(irObject && type);
+
+ const bool isValueType =
+ propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Value;
+ if (!isValueType
+ && propertyType->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
+ recordError(binding.location,
+ u"Group property '" + propertyName + u"' has unsupported access semantics");
+ return;
+ }
+
+ QString groupAccessor = CodeGeneratorUtility::generate_getPrivateClass(accessor.name, p)
+ + u"->" + p.read() + u"()";
+ // NB: used when isValueType == true
+ QString groupPropertyVarName = accessor.name + u"_group_" + propertyName;
+
+ const auto isGroupAffectingBinding = [](const QmlIR::Binding &b) {
+ // script bindings do not require value type group property variable
+ // to actually be present.
+ return b.type != QmlIR::Binding::Type_Invalid && b.type != QmlIR::Binding::Type_Script;
+ };
+ const bool generateValueTypeCode = isValueType
+ && std::any_of(irObject->bindingsBegin(), irObject->bindingsEnd(),
+ isGroupAffectingBinding);
+
+ // value types are special
+ if (generateValueTypeCode) {
+ if (p.write().isEmpty()) { // just reject this
+ recordError(binding.location,
+ u"Group property '" + propertyName
+ + u"' is a value type without a setter");
+ return;
+ }
+
+ current.endInit.body << u"auto " + groupPropertyVarName + u" = " + groupAccessor + u";";
+ // TODO: addressof operator is a crutch to make the binding logic
+ // work, which expects that `accessor.name` is a pointer type.
+ groupAccessor = CodeGeneratorUtility::generate_addressof(groupPropertyVarName);
+ }
+
+ // compile bindings of the grouped property
+ auto sortedBindings = toOrderedSequence(irObject->bindingsBegin(), irObject->bindingsEnd(),
+ irObject->bindingCount());
+ const auto compile = [&](typename QmlIR::PoolList<QmlIR::Binding>::Iterator it) {
+ compileBinding(current, *it, groupedObject,
+ { object.type, groupAccessor, propertyName, isValueType });
+ };
+
+ // NB: can't use lower_bound since it only accepts a value, not a unary
+ // predicate
+ auto scriptBindingsBegin =
+ std::find_if(sortedBindings.cbegin(), sortedBindings.cend(),
+ [](auto it) { return it->type == QmlIR::Binding::Type_Script; });
+ auto it = sortedBindings.cbegin();
+ for (; it != scriptBindingsBegin; ++it) {
+ Q_ASSERT((*it)->type != QmlIR::Binding::Type_Script);
+ compile(*it);
+ }
+
+ // NB: script bindings are special on group properties. if our group is
+ // a value type, the binding would be installed on the *object* that
+ // holds the value type and not on the value type itself. this may cause
+ // subtle side issues (esp. when script binding is actually a simple
+ // enum value assignment - which is not recognized specially):
+ //
+ // auto valueTypeGroupProperty = getCopy();
+ // installBinding(valueTypeGroupProperty, "subproperty1"); // changes subproperty1 value
+ // setCopy(valueTypeGroupProperty); // oops, subproperty1 value changed to old again
+ if (generateValueTypeCode) { // write the value type back
+ current.endInit.body << CodeGeneratorUtility::generate_getPrivateClass(accessor.name, p)
+ + u"->" + p.write() + u"(" + groupPropertyVarName + u");";
+ }
+
+ // once the value is written back, process the script bindings
+ for (; it != sortedBindings.cend(); ++it) {
+ Q_ASSERT((*it)->type == QmlIR::Binding::Type_Script);
+ compile(*it);
+ }
+ break;
+ }
+ case QmlIR::Binding::Type_Null: {
+ // poor check: null bindings are only supported for var and objects
+ if (propertyType != m_localTypeResolver->varType()
+ && propertyType->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
+ // TODO: this should really be done before the compiler here
+ recordError(binding.location, u"Cannot assign null to incompatible property"_qs);
+ } else if (propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) {
+ addPropertyLine(p.propertyName(), p, u"nullptr"_qs);
+ } else {
+ addPropertyLine(p.propertyName(), p, u"QVariant::fromValue(nullptr)"_qs);
+ }
+ break;
+ }
+ case QmlIR::Binding::Type_Invalid: {
+ recordError(binding.valueLocation,
+ u"Binding on property '" + propertyName + u"' is invalid or unsupported");
+ break;
+ }
+ }
+}
+
+QString CodeGenerator::makeGensym(const QString &base)
+{
+ QString gensym = base;
+ gensym.replace(QLatin1String("."), QLatin1String("_"));
+ while (gensym.startsWith(QLatin1Char('_')) && gensym.size() >= 2
+ && (gensym[1].isUpper() || gensym[1] == QLatin1Char('_'))) {
+ gensym.remove(0, 1);
+ }
+
+ if (!m_typeCounts.contains(gensym)) {
+ m_typeCounts.insert(gensym, 1);
+ } else {
+ gensym += u"_" + QString::number(m_typeCounts[gensym]++);
+ }
+
+ return gensym;
+}
+
+// returns compiled script binding for "property changed" handler in a form of object type
+static QQmlJSAotObject 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)
+{
+ QQmlJSAotObject 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 });
+ bindingFunctor.baselineCtor.name = functorCppType;
+ bindingFunctor.baselineCtor.parameterList.emplaceBack(
+ QQmlJSAotVariable { pointerToObject, u"self"_qs, QString() });
+ bindingFunctor.baselineCtor.initializerList.emplaceBack(u"m_self(self)"_qs);
+
+ // call operator:
+ QQmlJSAotMethod callOperator {};
+ callOperator.returnType = u"void"_qs;
+ callOperator.name = u"operator()"_qs;
+ callOperator.parameterList = slotParameters;
+ callOperator.modifiers << u"const"_qs;
+ callOperator.body += CodeGeneratorUtility::generate_callExecuteRuntimeFunction(
+ urlMethod.name + u"()",
+ relativeToAbsoluteRuntimeIndex(doc, irObject, binding.value.compiledScriptIndex),
+ u"m_self"_qs, u"void"_qs, slotParameters);
+
+ bindingFunctor.functions.emplaceBack(std::move(callOperator));
+
+ return bindingFunctor;
+}
+
+void CodeGenerator::compileScriptBinding(QQmlJSAotObject &current, const QmlIR::Binding &binding,
+ const QString &bindingSymbolName,
+ const CodeGenObject &object, const QString &propertyName,
+ const QQmlJSScope::ConstPtr &propertyType,
+ const CodeGenerator::AccessorData &accessor)
+{
+ auto objectType = object.type;
+
+ // returns signal by name. signal exists if std::optional<> has value
+ const auto signalByName = [&](const QString &name) -> std::optional<QQmlJSMetaMethod> {
+ const QList<QQmlJSMetaMethod> signalMethods = objectType->methods(name);
+ if (signalMethods.isEmpty())
+ return {};
+ // TODO: no clue how to handle redefinition, so just record an error
+ if (signalMethods.size() != 1) {
+ recordError(binding.location, u"Binding on redefined signal '" + name + u"'");
+ return {};
+ }
+ QQmlJSMetaMethod s = signalMethods.at(0);
+ if (s.methodType() != QQmlJSMetaMethod::Signal)
+ return {};
+ return s;
+ };
+
+ // TODO: add Invalid case which would reject garbage handlers
+ enum BindingKind {
+ JustProperty, // is a binding on property
+ SignalHandler, // is a slot related to custom signal
+ PropertyChangeHandler, // is a slot related to property's "changed" signal
+ };
+
+ // these only make sense when binding is on signal handler
+ QList<QQmlJSAotVariable> slotParameters;
+ QString signalName;
+ QString signalReturnType;
+
+ const BindingKind kind = [&]() -> BindingKind {
+ if (!QmlIR::IRBuilder::isSignalPropertyName(propertyName))
+ return BindingKind::JustProperty;
+
+ const auto offset = static_cast<uint>(strlen("on"));
+ signalName = propertyName[offset].toLower() + propertyName.mid(offset + 1);
+
+ std::optional<QQmlJSMetaMethod> possibleSignal = signalByName(signalName);
+ if (possibleSignal) { // signal with signalName exists
+ QQmlJSMetaMethod s = possibleSignal.value();
+ const auto paramNames = s.parameterNames();
+ const auto paramTypes = s.parameterTypes();
+ Q_ASSERT(paramNames.size() == paramTypes.size());
+ slotParameters = compileMethodParameters(paramNames, paramTypes);
+ signalReturnType = figureReturnType(s);
+ return BindingKind::SignalHandler;
+ } else if (propertyName.endsWith(u"Changed"_qs)) {
+ return BindingKind::PropertyChangeHandler;
+ }
+ return BindingKind::JustProperty;
+ }();
+
+ switch (kind) {
+ case BindingKind::JustProperty: {
+ if (!propertyType) {
+ recordError(binding.location,
+ u"Binding on property '" + propertyName + u"' of unknown type");
+ return;
+ }
+
+ auto [property, absoluteIndex] = getMetaPropertyIndex(object.type, propertyName);
+ if (absoluteIndex < 0) {
+ recordError(binding.location, u"Binding on unknown property '" + propertyName + u"'");
+ return;
+ }
+
+ QString bindingTarget = accessor.name;
+
+ int valueTypeIndex = -1;
+ if (accessor.isValueType) {
+ Q_ASSERT(accessor.scope != object.type);
+ bindingTarget = u"this"_qs; // TODO: not necessarily "this"?
+ auto [groupProperty, groupPropertyIndex] =
+ getMetaPropertyIndex(accessor.scope, accessor.propertyName);
+ if (groupPropertyIndex < 0) {
+ recordError(binding.location,
+ u"Binding on group property '" + accessor.propertyName
+ + u"' of unknown type");
+ return;
+ }
+ valueTypeIndex = absoluteIndex;
+ absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name
+ }
+
+ current.endInit.body += CodeGeneratorUtility::generate_createBindingOnProperty(
+ CodeGeneratorUtility::compilationUnitVariable.name,
+ u"this"_qs, // NB: always using enclosing object as a scope for the binding
+ relativeToAbsoluteRuntimeIndex(object.irObject, binding.value.compiledScriptIndex),
+ bindingTarget, // binding target
+ absoluteIndex, property, valueTypeIndex, accessor.name);
+ break;
+ }
+ case BindingKind::SignalHandler: {
+ QString This_signal = u"this"_qs;
+ QString This_slot = u"this"_qs;
+ QString objectClassName_signal = objectType->internalName();
+ QString objectClassName_slot = objectType->internalName();
+ // TODO: ugly crutch to make stuff work
+ if (accessor.name != u"this"_qs) { // e.g. if attached property
+ This_signal = accessor.name;
+ This_slot = u"this"_qs; // still
+ objectClassName_signal = objectType->baseTypeName();
+ objectClassName_slot = current.cppType; // real base class where slot would go
+ }
+ Q_ASSERT(!objectClassName_signal.isEmpty());
+ 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 {};
+ slotMethod.returnType = signalReturnType;
+ slotMethod.name = slotName;
+ slotMethod.parameterList = slotParameters;
+
+ slotMethod.body += CodeGeneratorUtility::generate_callExecuteRuntimeFunction(
+ m_urlMethod.name + u"()",
+ relativeToAbsoluteRuntimeIndex(m_doc, object.irObject,
+ binding.value.compiledScriptIndex),
+ u"this"_qs, // Note: because script bindings always use current QML object scope
+ signalReturnType, slotParameters);
+ slotMethod.type = QQmlJSMetaMethod::Slot;
+
+ current.functions << std::move(slotMethod);
+ current.endInit.body << u"QObject::connect(" + This_signal + u", "
+ + CodeGeneratorUtility::generate_qOverload(slotParameters,
+ u"&" + objectClassName_signal
+ + u"::" + signalName)
+ + u", " + This_slot + u", &" + objectClassName_slot + u"::" + slotName
+ + u");";
+ break;
+ }
+ // TODO: fix code generation for property change handlers - it is broken for
+ // many cases. see QTBUG-91956
+ case BindingKind::PropertyChangeHandler: {
+ const QString objectClassName = objectType->internalName();
+ const QString bindingFunctorName = makeGensym(bindingSymbolName + u"Functor");
+
+ QString actualPropertyName;
+ QRegularExpression onChangedRegExp(u"on(\\w+)Changed"_qs);
+ QRegularExpressionMatch match = onChangedRegExp.match(propertyName);
+ if (match.hasMatch()) {
+ actualPropertyName = match.captured(1);
+ actualPropertyName[0] = actualPropertyName.at(0).toLower();
+ } else {
+ recordError(binding.location, u"Missing parameter name in on*Changed handler"_qs);
+ return;
+ }
+ if (!objectType->hasProperty(actualPropertyName)) {
+ recordError(binding.location, u"No property named " + actualPropertyName);
+ return;
+ }
+ QQmlJSMetaProperty actualProperty = objectType->property(actualPropertyName);
+ const auto actualPropertyType = actualProperty.type();
+ if (!actualPropertyType) {
+ recordError(binding.location,
+ u"Binding on property '" + actualPropertyName + u"' of unknown type");
+ return;
+ }
+ QString typeOfQmlBinding =
+ u"std::unique_ptr<QPropertyChangeHandler<" + bindingFunctorName + u">>";
+
+ current.children << compileScriptBindingPropertyChangeHandler(
+ m_doc, binding, object.irObject, m_urlMethod, bindingFunctorName, objectClassName,
+ slotParameters);
+
+ QString bindableString = actualProperty.bindable();
+ if (bindableString.isEmpty()) { // TODO: always should come from prop.bindalbe()
+ recordError(binding.location,
+ u"Property '" + actualPropertyName
+ + u"' is non-bindable. Change handlers for such properties are not "
+ u"supported");
+ break;
+ }
+ // TODO: the finalize.lastLines has to be used here -- somehow if property gets a binding,
+ // the change handler is not updated?!
+
+ // TODO: this could be dropped if QQmlEngine::setContextForObject() is
+ // done before currently generated C++ object is constructed
+ current.endInit.body << bindingSymbolName + u".reset(new QPropertyChangeHandler<"
+ + bindingFunctorName + u">("
+ + CodeGeneratorUtility::generate_getPrivateClass(accessor.name,
+ actualProperty)
+ + u"->" + bindableString + u"().onValueChanged(" + bindingFunctorName + u"("
+ + accessor.name + u"))));";
+
+ current.variables.emplaceBack(
+ QQmlJSAotVariable { typeOfQmlBinding, bindingSymbolName, QString() });
+ // current.ctor.initializerList << bindingSymbolName + u"()";
+ break;
+ }
+ }
+}
+
+// TODO: should use "compileScriptBinding" instead of custom code
+void CodeGenerator::compileScriptBindingOfComponent(QQmlJSAotObject &current,
+ const QmlIR::Object *irObject,
+ const QQmlJSScope::ConstPtr objectType,
+ const QmlIR::Binding &binding,
+ const QString &propertyName)
+{
+ // returns signal by name. signal exists if std::optional<> has value
+ const auto signalByName = [&](const QString &name) -> std::optional<QQmlJSMetaMethod> {
+ const QList<QQmlJSMetaMethod> signalMethods = objectType->methods(name);
+ // TODO: no clue how to handle redefinition, so just record an error
+ if (signalMethods.size() != 1) {
+ recordError(binding.location, u"Binding on redefined signal '" + name + u"'");
+ return {};
+ }
+ QQmlJSMetaMethod s = signalMethods.at(0);
+ if (s.methodType() != QQmlJSMetaMethod::Signal)
+ return {};
+ return s;
+ };
+
+ QString signalName;
+ QString signalReturnType;
+
+ Q_ASSERT(QmlIR::IRBuilder::isSignalPropertyName(propertyName));
+ const auto offset = static_cast<uint>(strlen("on"));
+ signalName = propertyName[offset].toLower() + propertyName.mid(offset + 1);
+
+ std::optional<QQmlJSMetaMethod> possibleSignal = signalByName(signalName);
+ if (!possibleSignal) {
+ recordError(binding.location, u"Component signal '" + signalName + u"' is not recognized");
+ return;
+ }
+ Q_ASSERT(possibleSignal); // Component's script binding is always a signal handler
+ QQmlJSMetaMethod s = possibleSignal.value();
+ Q_ASSERT(s.parameterNames().isEmpty()); // Component signals do not have parameters
+ signalReturnType = figureReturnType(s);
+
+ const QString slotName = makeGensym(signalName + u"_slot");
+
+ // SignalHander specific:
+ QQmlJSAotMethod slotMethod {};
+ slotMethod.returnType = signalReturnType;
+ slotMethod.name = slotName;
+
+ // Component is special:
+ int runtimeIndex =
+ relativeToAbsoluteRuntimeIndex(m_doc, irObject, binding.value.compiledScriptIndex);
+ slotMethod.body += CodeGeneratorUtility::generate_callExecuteRuntimeFunction(
+ m_urlMethod.name + u"()", runtimeIndex, u"this"_qs, signalReturnType);
+ slotMethod.type = QQmlJSMetaMethod::Slot;
+
+ // TODO: there's actually an attached type, which has completed/destruction
+ // signals that are typically emitted -- do we care enough about supporting
+ // that? see QQmlComponentAttached
+ if (signalName == u"completed"_qs) {
+ current.handleOnCompleted.body << slotName + u"();";
+ } else if (signalName == u"destruction"_qs) {
+ if (!current.dtor) {
+ current.dtor = QQmlJSAotSpecialMethod {};
+ current.dtor->name = u"~" + current.cppType;
+ }
+ current.dtor->firstLines << slotName + u"();";
+ }
+ current.functions << std::move(slotMethod);
+}
+
+void CodeGenerator::compileUrlMethod()
+{
+ m_urlMethod.returnType = u"const QUrl &"_qs;
+ m_urlMethod.name = u"url"_qs;
+ m_urlMethod.body << u"static QUrl docUrl = %1;"_qs.arg(
+ CodeGeneratorUtility::toResourcePath(m_options.resourcePath));
+ m_urlMethod.body << u"return docUrl;"_qs;
+ m_urlMethod.declPreambles << 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);
+}
+
+void CodeGenerator::recordError(const QV4::CompiledData::Location &location, const QString &message)
+{
+ recordError(QQmlJS::SourceLocation { 0, 0, location.line, location.column }, message);
+}
diff --git a/tools/qmltc/prototype/codegenerator.h b/tools/qmltc/prototype/codegenerator.h
new file mode 100644
index 0000000000..e4a1bb4705
--- /dev/null
+++ b/tools/qmltc/prototype/codegenerator.h
@@ -0,0 +1,180 @@
+/****************************************************************************
+**
+** 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 CODEGENERATOR_H
+#define CODEGENERATOR_H
+
+#include "prototype/typeresolver.h"
+#include "prototype/qmlcompiler.h"
+#include "prototype/generatedcodeprimitives.h"
+#include "prototype/qml2cppcontext.h"
+
+#include <QtCore/qlist.h>
+#include <QtCore/qqueue.h>
+#include <QtCore/qhash.h>
+
+#include <QtQml/private/qqmlirbuilder_p.h>
+#include <private/qqmljscompiler_p.h>
+#include <private/qqmljstyperesolver_p.h>
+
+#include <variant>
+#include <utility>
+
+class CodeGenerator
+{
+public:
+ CodeGenerator(const QString &url, QQmlJSLogger *logger, QmlIR::Document *doc,
+ const Qmltc::TypeResolver *localResolver);
+
+ // main function: given compilation options, generates C++ code (implicitly)
+ void generate(const Options &options);
+
+ // TODO: this should really be just QQmlJSScope::ConstPtr (and maybe C++
+ // class name), but bindings are currently not represented in QQmlJSScope,
+ // so have to keep track of QmlIR::Object and consequently some extra stuff
+ using CodeGenObject = Qml2CppObject;
+
+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
+
+ Options m_options = {}; // compilation options
+
+ // convenient object abstraction, laid out as QmlIR::Document.objects
+ QList<CodeGenObject> m_objects;
+ // mapping from type to m_objects index
+ QHash<QQmlJSScope::ConstPtr, qsizetype> m_typeToObjectIndex; // TODO: remove this
+ // mapping from to-be-wrapped object to the wrapper's object pseudo-index
+ QHash<int, int> m_implicitComponentMapping;
+
+ QQmlJSAotMethod m_urlMethod;
+
+ // helper struct used for unique string generation
+ struct UniqueStringId
+ {
+ QString combined;
+ UniqueStringId(const QQmlJSAotObject &compiled, const QString &value)
+ : combined(compiled.cppType + u"_" + value)
+ {
+ Q_ASSERT(!compiled.cppType.isEmpty());
+ }
+ operator QString() { return combined; }
+
+ friend bool operator==(const UniqueStringId &x, const UniqueStringId &y)
+ {
+ return x.combined == y.combined;
+ }
+ friend bool operator!=(const UniqueStringId &x, const UniqueStringId &y)
+ {
+ return !(x == y);
+ }
+ friend bool operator<(const UniqueStringId &x, const UniqueStringId &y)
+ {
+ return x.combined < y.combined;
+ }
+ friend size_t qHash(const UniqueStringId &x) { return qHash(x.combined); }
+ };
+
+ // QML attached types that are already added as member variables (faster lookup)
+ QSet<UniqueStringId> m_attachedTypesAlreadyRegistered;
+
+ // machinery for unique names generation
+ QHash<QString, qsizetype> m_typeCounts;
+ QString makeGensym(const QString &base);
+
+ // crutch to remember QQmlListReference names, the unique naming convention
+ // is used for these so there's never a conflict
+ QSet<UniqueStringId> m_listReferencesCreated;
+
+ // native QML base type names of the to-be-compiled objects which happen to
+ // also be generated (but separately)
+ QSet<QString> m_qmlCompiledBaseTypes;
+
+ // a set of objects that participate in "on" assignments
+ QSet<UniqueStringId> m_onAssignmentObjectsCreated;
+
+ // a vector of names of children that need to be end-initialized within type
+ QList<QString> m_localChildrenToEndInit;
+ // a vector of children that need to be finalized within type
+ QList<QQmlJSScope::ConstPtr> m_localChildrenToFinalize;
+
+ // a crutch (?) to enforce special generated code for aliases to ids, for
+ // example: `property alias p: root`
+ QSet<QQmlJSMetaProperty> m_aliasesToIds;
+
+ // init function that constructs m_objects
+ void constructObjects(QSet<QString> &requiredCppIncludes);
+
+ bool m_isAnonymous = false; // crutch to distinguish QML_ELEMENT from QML_ANONYMOUS
+
+ // code compilation functions that produce "compiled" entities
+ void compileObject(QQmlJSAotObject &current, const CodeGenObject &object);
+ void compileEnum(QQmlJSAotObject &current, const QQmlJSMetaEnum &e);
+ void compileProperty(QQmlJSAotObject &current, const QQmlJSMetaProperty &p,
+ const QQmlJSScope::ConstPtr &owner);
+ void compileAlias(QQmlJSAotObject &current, const QQmlJSMetaProperty &alias,
+ const QQmlJSScope::ConstPtr &owner);
+ void compileMethod(QQmlJSAotObject &current, const QQmlJSMetaMethod &m,
+ const QmlIR::Function *f, const CodeGenObject &object);
+ void compileUrlMethod(); // special case
+
+ // helper structure that holds the information necessary for most bindings,
+ // such as accessor name, which is used to reference the properties like:
+ // (accessor.name)->(propertyName): this->myProperty. it is also used in
+ // more advanced scenarios by attached and grouped properties
+ struct AccessorData
+ {
+ QQmlJSScope::ConstPtr scope; // usually object.type
+ QString name; // usually "this"
+ QString propertyName; // usually empty
+ bool isValueType = false; // usually false
+ };
+ void compileBinding(QQmlJSAotObject &current, const QmlIR::Binding &binding,
+ const CodeGenObject &object, const AccessorData &accessor);
+ // special case (for simplicity)
+ void compileScriptBinding(QQmlJSAotObject &current, 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 &current, const QmlIR::Object *object,
+ const QQmlJSScope::ConstPtr objectType,
+ const QmlIR::Binding &binding,
+ const QString &propertyName);
+
+ // helper methods:
+ void recordError(const QQmlJS::SourceLocation &location, const QString &message);
+ void recordError(const QV4::CompiledData::Location &location, const QString &message);
+};
+
+#endif // CODEGENERATOR_H
diff --git a/tools/qmltc/prototype/codegeneratorutil.cpp b/tools/qmltc/prototype/codegeneratorutil.cpp
new file mode 100644
index 0000000000..248a40eebf
--- /dev/null
+++ b/tools/qmltc/prototype/codegeneratorutil.cpp
@@ -0,0 +1,238 @@
+/****************************************************************************
+**
+** 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/codegeneratorutil.h"
+#include "prototype/qml2cpppropertyutils.h"
+
+#include <QtCore/qstringbuilder.h>
+
+#include <tuple>
+
+// 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 QQmlJSAotVariable CodeGeneratorUtility::compilationUnitVariable = {
+ u"QV4::ExecutableCompilationUnit *"_qs, u"QML_cu"_qs, QString()
+};
+
+static std::tuple<QStringList, QString, QStringList>
+wrapIfMismatchingType(const QQmlJSMetaProperty &p, QString value)
+{
+ auto isDerivedFromBuiltin = [](QQmlJSScope::ConstPtr t, const QString &builtin) {
+ for (; t; t = t->baseType()) {
+ if (t->internalName() == builtin)
+ return true;
+ }
+ return false;
+ };
+ QStringList prologue;
+ QStringList epilogue;
+ auto propType = p.type();
+ if (isDerivedFromBuiltin(propType, u"QVariant"_qs)) {
+ const QString variantName = u"var_" + p.propertyName();
+ prologue << u"{ // accepts QVariant"_qs;
+ prologue << u"QVariant " + variantName + u";";
+ prologue << variantName + u".setValue(" + value + u");";
+ epilogue << u"}"_qs;
+ value = u"std::move(" + variantName + u")";
+ }
+ /*else if (isDerivedFromBuiltin(propType, u"QQmlComponent"_qs)) {
+ // assume value can always be converted to "QObject *"
+ prologue << u"// accepts QQmlComponent"_qs;
+ value = u"new QQmlComponent(" + value + u")";
+ }*/
+ return { prologue, value, epilogue };
+}
+
+// TODO: when assigning to a property, if property.type() != value.type(), have
+// to wrap it (actually, wrap unconditionally if property.type() is QVariant or
+// QQmlComponent -- other cases out of scope for now)
+QStringList CodeGeneratorUtility::generate_assignToProperty(
+ const QQmlJSScope::ConstPtr &type, const QString &propertyName, const QQmlJSMetaProperty &p,
+ const QString &value, const QString &accessor, bool constructQVariant)
+{
+ Q_ASSERT(p.isValid());
+ Q_ASSERT(!p.isList()); // NB: this code does not handle list properties
+
+ QStringList code;
+ code.reserve(6); // should always be enough
+
+ if (type->hasOwnProperty(propertyName)) {
+ Q_ASSERT(!p.isPrivate());
+ // this object is compiled, so just assignment should work fine
+ code << accessor + u"->m_" + propertyName + u" = " + value + u";";
+ } else if (QString propertySetter = p.write(); !propertySetter.isEmpty()) {
+ // there's a WRITE function
+ auto [prologue, wrappedValue, epilogue] = wrapIfMismatchingType(p, value);
+ code += prologue;
+ code << CodeGeneratorUtility::generate_getPrivateClass(accessor, p) + u"->" + propertySetter
+ + u"(" + wrappedValue + u");";
+ code += epilogue;
+ } else {
+ // this property is weird, fallback to `setProperty`
+ code << u"{ // couldn't find property setter, so using QObject::setProperty()"_qs;
+ QString val = value;
+ if (constructQVariant) {
+ const QString variantName = u"var_" + propertyName;
+ code << u"QVariant " + variantName + u";";
+ code << variantName + u".setValue(" + val + u");";
+ val = u"std::move(" + variantName + u")";
+ }
+ // NB: setProperty() would handle private properties
+ code << accessor + u"->setProperty(\"" + propertyName + u"\", " + val + u");";
+ code << u"}"_qs;
+ }
+
+ return code;
+}
+
+QStringList CodeGeneratorUtility::generate_assignToSpecialAlias(
+ const QQmlJSScope::ConstPtr &type, const QString &propertyName, const QQmlJSMetaProperty &p,
+ const QString &value, const QString &accessor, bool constructQVariant)
+{
+ Q_UNUSED(type);
+ Q_UNUSED(propertyName);
+ Q_UNUSED(constructQVariant);
+
+ Q_ASSERT(p.isValid());
+ Q_ASSERT(!p.isList()); // NB: this code does not handle list properties
+ Q_ASSERT(p.isAlias());
+ Q_ASSERT(p.type());
+
+ QStringList code;
+ code.reserve(6); // should always be enough
+ // pretend there's a WRITE function
+ Qml2CppPropertyData data(p);
+ auto [prologue, wrappedValue, epilogue] = wrapIfMismatchingType(p, value);
+ code += prologue;
+ code << CodeGeneratorUtility::generate_getPrivateClass(accessor, p) + u"->" + data.write + u"("
+ + wrappedValue + u");";
+ code += epilogue;
+ return code;
+}
+
+QStringList CodeGeneratorUtility::generate_callExecuteRuntimeFunction(
+ const QString &url, qsizetype index, const QString &accessor, const QString &returnType,
+ const QList<QQmlJSAotVariable> &parameters)
+{
+ QStringList code;
+ code.reserve(12); // should always be enough
+
+ code << u"QQmlEnginePrivate *e = QQmlEnginePrivate::get(qmlEngine(" + accessor + u"));";
+
+ const QString returnValueName = u"_ret"_qs;
+ QStringList args;
+ args.reserve(parameters.size() + 1);
+ QStringList types;
+ types.reserve(parameters.size() + 1);
+ if (returnType == u"void"_qs) {
+ args << u"nullptr"_qs;
+ types << u"QMetaType::fromType<void>()"_qs;
+ } else {
+ code << returnType + u" " + returnValueName + u"{};"; // TYPE _ret{};
+ args << u"const_cast<void *>(reinterpret_cast<const void *>(std::addressof("
+ + returnValueName + u")))";
+ types << u"QMetaType::fromType<std::decay_t<" + returnType + u">>()";
+ }
+
+ for (const QQmlJSAotVariable &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">>()";
+ }
+
+ code << u"void *_a[] = { " + args.join(u", "_qs) + u" };";
+ code << u"QMetaType _t[] = { " + types.join(u", "_qs) + u" };";
+ code << u"e->executeRuntimeFunction(" + url + u", " + QString::number(index) + u", " + accessor
+ + u", " + QString::number(parameters.size()) + u", _a, _t);";
+ if (returnType != u"void"_qs)
+ code << u"return " + returnValueName + u";";
+
+ return code;
+}
+
+QStringList CodeGeneratorUtility::generate_createBindingOnProperty(
+ const QString &unitVarName, const QString &scope, qsizetype functionIndex,
+ const QString &target, int propertyIndex, const QQmlJSMetaProperty &p, int valueTypeIndex,
+ const QString &subTarget)
+{
+ QStringList code;
+ code.reserve(1);
+
+ const QString propName = u"QStringLiteral(\"" + p.propertyName() + u"\")";
+ if (QString bindable = p.bindable(); !bindable.isEmpty()) {
+ // TODO: test that private properties are bindable
+ QString createBindingForBindable = u"QT_PREPEND_NAMESPACE(QQmlCppBinding)::"
+ u"createBindingForBindable("
+ + unitVarName + u", " + scope + u", " + QString::number(functionIndex) + u", "
+ + target + u", " + QString::number(propertyIndex) + u", "
+ + QString::number(valueTypeIndex) + u", " + propName + u")";
+ const QString accessor = (valueTypeIndex == -1) ? target : subTarget;
+ code << CodeGeneratorUtility::generate_getPrivateClass(accessor, p) + u"->" + bindable
+ + u"().setBinding(" + createBindingForBindable + u");";
+ } else {
+ // TODO: test that bindings on private properties also work
+ QString createBindingForNonBindable =
+ 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
+ code << createBindingForNonBindable + u";";
+ }
+
+ return code;
+}
+
+QString CodeGeneratorUtility::generate_qOverload(const QList<QQmlJSAotVariable> &params,
+ const QString &overloaded)
+{
+ QStringList types;
+ types.reserve(params.size());
+ for (const QQmlJSAotVariable &p : params)
+ types.emplaceBack(p.cppType);
+ return u"qOverload<" + types.join(u", "_qs) + u">(" + overloaded + u")";
+}
+
+QString CodeGeneratorUtility::generate_addressof(const QString &addressed)
+{
+ return u"(&" + addressed + u")";
+}
+
+QString CodeGeneratorUtility::generate_getPrivateClass(const QString &accessor,
+ const QQmlJSMetaProperty &p)
+{
+ if (!p.isPrivate())
+ return accessor;
+
+ const QString privateType = p.privateClass();
+ return u"static_cast<" + privateType + u" *>(QObjectPrivate::get(" + accessor + u"))";
+}
diff --git a/tools/qmltc/prototype/codegeneratorutil.h b/tools/qmltc/prototype/codegeneratorutil.h
new file mode 100644
index 0000000000..74529f6853
--- /dev/null
+++ b/tools/qmltc/prototype/codegeneratorutil.h
@@ -0,0 +1,126 @@
+/****************************************************************************
+**
+** 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 CODEGENERATORUTIL_H
+#define CODEGENERATORUTIL_H
+
+#include "prototype/qmlcompiler.h"
+
+#include <private/qqmljsscope_p.h>
+#include <private/qqmljsmetatypes_p.h>
+
+#include <QtCore/qlist.h>
+#include <QtCore/qstring.h>
+
+#include <utility>
+
+struct CodeGeneratorUtility
+{
+ // magic variable, necessary for correct handling of object bindings: since
+ // object creation and binding setup are separated across functions, we
+ // fetch a subobject by: QObject::children().at(offset + localIndex)
+ // ^
+ // childrenOffsetVariable
+ // this variable would often be equal to 0, but there's no guarantee. and it
+ // is required due to non-trivial aliases dependencies: aliases can
+ // 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;
+
+ // represents QV4::ExecutableCompilationUnit
+ static const QQmlJSAotVariable compilationUnitVariable;
+
+ // helper functions:
+ static QString toResourcePath(const QString &s)
+ {
+ return u"QUrl(QStringLiteral(\"qrc:" + s + u"\"))";
+ }
+
+ static QString escapeString(QString s)
+ {
+ return s.replace(u"\\"_qs, u"\\\\"_qs)
+ .replace(u"\""_qs, u"\\\""_qs)
+ .replace(u"\n"_qs, u"\\n"_qs);
+ }
+
+ static QString createStringLiteral(const QString &s)
+ {
+ return u"QStringLiteral(\"" + escapeString(s) + u"\")";
+ }
+
+ static QString getInternalNameAwareOfAccessSemantics(const QQmlJSScope::ConstPtr &s)
+ {
+ // TODO: sequence is not supported yet
+ Q_ASSERT(s->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence);
+ const QString suffix = (s->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
+ ? u" *"_qs
+ : u""_qs;
+ return s->internalName() + suffix;
+ }
+
+ static QString wrapInConstRefIfNotAPointer(QString s)
+ {
+ if (!s.endsWith(u'*'))
+ s = u"const " + s + u"&";
+ return s;
+ }
+
+ static QString metaPropertyName(const QString &propertyVariableName)
+ {
+ return propertyVariableName + u"_meta";
+ }
+
+ // generate functions:
+ static QStringList generate_assignToProperty(const QQmlJSScope::ConstPtr &type,
+ const QString &propertyName,
+ const QQmlJSMetaProperty &p, const QString &value,
+ const QString &accessor,
+ bool constructQVariant = false);
+ static QStringList generate_assignToSpecialAlias(const QQmlJSScope::ConstPtr &type,
+ const QString &propertyName,
+ const QQmlJSMetaProperty &p,
+ const QString &value, const QString &accessor,
+ bool constructQVariant = false);
+ // TODO: 3 separate versions: bindable QML, bindable C++, non-bindable C++
+ static QStringList
+ generate_callExecuteRuntimeFunction(const QString &url, qsizetype index,
+ const QString &accessor, const QString &returnType,
+ const QList<QQmlJSAotVariable> &parameters = {});
+ 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> &parameters,
+ const QString &overloaded);
+ static QString generate_addressof(const QString &addressed);
+ static QString generate_getPrivateClass(const QString &accessor, const QQmlJSMetaProperty &p);
+};
+
+#endif // CODEGENERATORUTIL_H
diff --git a/tools/qmltc/prototype/codegeneratorwriter.cpp b/tools/qmltc/prototype/codegeneratorwriter.cpp
new file mode 100644
index 0000000000..5ccd698c63
--- /dev/null
+++ b/tools/qmltc/prototype/codegeneratorwriter.cpp
@@ -0,0 +1,438 @@
+/****************************************************************************
+**
+** 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>
+
+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"");
+ 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;
+}
+
+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);
+ }
+
+ GeneratedCodeUtils::MemberNamespaceScope thisObjectScope(code, compiled.cppType);
+ Q_UNUSED(thisObjectScope);
+ {
+ GeneratedCodeUtils::HeaderIndentationScope headerIndentScope(code);
+ Q_UNUSED(headerIndentScope);
+
+ // generate ctor
+ if (compiled.ignoreInit) { // TODO: this branch should be eliminated
+ // NB: here the ctor should be public
+ code.appendToHeader(getFunctionCategory(compiled.baselineCtor) + u":", -1);
+ CodeGeneratorWriter::write(code, compiled.baselineCtor);
+ } else {
+ Q_ASSERT(compiled.baselineCtor.access == compiled.init.access);
+ code.appendToHeader(getFunctionCategory(compiled.init) + u":", -1);
+ CodeGeneratorWriter::write(code, compiled.baselineCtor);
+ CodeGeneratorWriter::write(code, compiled.init);
+
+ // NB: when singleton, this ctor won't be public
+ code.appendToHeader(getFunctionCategory(compiled.externalCtor) + u":", -1);
+ CodeGeneratorWriter::write(code, compiled.externalCtor);
+ // TODO: actually should figure how to make this one protected
+ code.appendToHeader(u"public:", -1);
+ CodeGeneratorWriter::write(code, compiled.endInit);
+ CodeGeneratorWriter::write(code, compiled.completeComponent);
+ CodeGeneratorWriter::write(code, compiled.finalizeComponent);
+ CodeGeneratorWriter::write(code, compiled.handleOnCompleted);
+ }
+
+ // generate dtor
+ if (compiled.dtor)
+ CodeGeneratorWriter::write(code, *compiled.dtor);
+
+ // generate enums
+ code.appendToHeader(u"// BEGIN(enumerations)");
+ for (const auto &enumeration : qAsConst(compiled.enums))
+ CodeGeneratorWriter::write(code, enumeration);
+ code.appendToHeader(u"// END(enumerations)");
+
+ // 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(functions)");
+ // functions are special as they are grouped by access and kind
+ QHash<QString, QList<const QQmlJSAotMethod *>> functionsByCategory;
+ for (const auto &function : qAsConst(compiled.functions))
+ functionsByCategory[getFunctionCategory(function)].append(std::addressof(function));
+
+ for (auto it = functionsByCategory.cbegin(); it != functionsByCategory.cend(); ++it) {
+ code.appendToHeader(it.key() + u":", -1);
+ for (const QQmlJSAotMethod *function : qAsConst(it.value()))
+ CodeGeneratorWriter::write(code, *function);
+ }
+ code.appendToHeader(u"// END(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> &parameterList = 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
new file mode 100644
index 0000000000..e03a2123ac
--- /dev/null
+++ b/tools/qmltc/prototype/codegeneratorwriter.h
@@ -0,0 +1,57 @@
+/****************************************************************************
+**
+** 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
new file mode 100644
index 0000000000..46d7b1461f
--- /dev/null
+++ b/tools/qmltc/prototype/generatedcodeprimitives.h
@@ -0,0 +1,126 @@
+/****************************************************************************
+**
+** 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
new file mode 100644
index 0000000000..56bb5a12db
--- /dev/null
+++ b/tools/qmltc/prototype/qml2cppcontext.h
@@ -0,0 +1,108 @@
+/****************************************************************************
+**
+** 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 QML2CPPCONTEXT_H
+#define QML2CPPCONTEXT_H
+
+#include "prototype/qmlcompiler.h"
+#include "prototype/typeresolver.h"
+
+#include <private/qqmljsdiagnosticmessage_p.h>
+#include <QtQml/private/qqmlirbuilder_p.h>
+#include <private/qqmljstyperesolver_p.h>
+
+#include <QtCore/QList>
+
+#include <variant>
+#include <functional>
+
+struct Qml2CppContext
+{
+ const QmlIR::Document *document = nullptr;
+ const Qmltc::TypeResolver *typeResolver = nullptr;
+ QString documentUrl;
+ QQmlJSLogger *logger = nullptr;
+ const QHash<QQmlJSScope::ConstPtr, qsizetype> *typeIndices = nullptr; // TODO: remove this?
+
+ void recordError(const QQmlJS::SourceLocation &location, const QString &message) const
+ {
+ Q_ASSERT(logger);
+ logger->logCritical(message, Log_Compiler, location);
+ }
+
+ void recordError(const QV4::CompiledData::Location &location, const QString &message) const
+ {
+ recordError(QQmlJS::SourceLocation { 0, 0, location.line, location.column }, message);
+ }
+};
+
+struct Qml2CppObject
+{
+ QmlIR::Object *irObject = nullptr; // raw IR object representation
+ QQmlJSScope::Ptr type; // Qml/JS type representation for IR object
+};
+
+// the corner stone of the compiler: compiler pass function prototype. the main
+// idea is that, given "context" as input and object hierarchy as input/output,
+// you run an arbitrary function (e.g. to verify that QQmlJSScope::ConstPtr
+// types are valid).
+using Qml2CppCompilerPass = std::function<void(/* IN */ const Qml2CppContext &,
+ /* INOUT */ QList<Qml2CppObject> &)>;
+
+class Qml2CppCompilerPassExecutor
+{
+ Qml2CppContext m_context;
+ QList<Qml2CppObject> &m_objects;
+ QList<Qml2CppCompilerPass> m_passes;
+
+public:
+ Qml2CppCompilerPassExecutor(const QmlIR::Document *doc, const Qmltc::TypeResolver *resolver,
+ const QString &url, QList<Qml2CppObject> &objects,
+ const QHash<QQmlJSScope::ConstPtr, qsizetype> &typeIndices)
+ : m_context { doc, resolver, url, nullptr, &typeIndices }, m_objects { objects }
+ {
+ }
+
+ // add new pass to the executor. first pass is typically the one that
+ // populates the Qml2CppObject list
+ void addPass(Qml2CppCompilerPass pass) { m_passes.append(pass); }
+
+ // runs passes in the order of their insertion, populating \a logger on
+ // errors. right now always stops if new errors are found
+ void run(QQmlJSLogger *logger)
+ {
+ m_context.logger = logger;
+ for (const auto &pass : m_passes) {
+ pass(m_context, m_objects);
+ if (m_context.logger->hasErrors())
+ return;
+ }
+ }
+};
+
+#endif // QML2CPPCONTEXT_H
diff --git a/tools/qmltc/prototype/qml2cppdefaultpasses.cpp b/tools/qmltc/prototype/qml2cppdefaultpasses.cpp
new file mode 100644
index 0000000000..b8560a39af
--- /dev/null
+++ b/tools/qmltc/prototype/qml2cppdefaultpasses.cpp
@@ -0,0 +1,941 @@
+/****************************************************************************
+**
+** 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 "qml2cppdefaultpasses.h"
+#include "qml2cpppropertyutils.h"
+
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qqueue.h>
+#include <QtCore/qloggingcategory.h>
+
+static QString const cppKeywords[] = {
+ u"alignas"_qs,
+ u"alignof"_qs,
+ u"and"_qs,
+ u"and_eq"_qs,
+ u"asm"_qs,
+ u"atomic_cancel"_qs,
+ u"atomic_commit"_qs,
+ u"atomic_noexcept"_qs,
+ u"auto"_qs,
+ u"bitand"_qs,
+ u"bitor"_qs,
+ u"bool"_qs,
+ u"break"_qs,
+ u"case"_qs,
+ u"catch"_qs,
+ u"char"_qs,
+ u"char8_t"_qs,
+ u"char16_t"_qs,
+ u"char32_t"_qs,
+ u"class"_qs,
+ u"compl"_qs,
+ u"concept"_qs,
+ u"const"_qs,
+ u"consteval"_qs,
+ u"constexpr"_qs,
+ u"const_cast"_qs,
+ u"continue"_qs,
+ u"co_await"_qs,
+ u"co_return"_qs,
+ u"co_yield"_qs,
+ u"decltype"_qs,
+ u"default"_qs,
+ u"delete"_qs,
+ u"do"_qs,
+ u"double"_qs,
+ u"dynamic_cast"_qs,
+ u"else"_qs,
+ u"enum"_qs,
+ u"explicit"_qs,
+ u"export"_qs,
+ u"extern"_qs,
+ u"false"_qs,
+ u"float"_qs,
+ u"for"_qs,
+ u"friend"_qs,
+ u"goto"_qs,
+ u"if"_qs,
+ u"inline"_qs,
+ u"int"_qs,
+ u"long"_qs,
+ u"mutable"_qs,
+ u"namespace"_qs,
+ u"new"_qs,
+ u"noexcept"_qs,
+ u"not"_qs,
+ u"not_eq"_qs,
+ u"nullptr"_qs,
+ u"operator"_qs,
+ u"or"_qs,
+ u"or_eq"_qs,
+ u"private"_qs,
+ u"protected"_qs,
+ u"public"_qs,
+ u"reflexpr"_qs,
+ u"register"_qs,
+ u"reinterpret_cast"_qs,
+ u"requires"_qs,
+ u"return"_qs,
+ u"short"_qs,
+ u"signed"_qs,
+ u"sizeof"_qs,
+ u"static"_qs,
+ u"static_assert"_qs,
+ u"static_cast"_qs,
+ u"struct"_qs,
+ u"switch"_qs,
+ u"synchronized"_qs,
+ u"template"_qs,
+ u"this"_qs,
+ u"thread_local"_qs,
+ u"throw"_qs,
+ u"true"_qs,
+ u"try"_qs,
+ u"typedef"_qs,
+ u"typeid"_qs,
+ u"typename"_qs,
+ u"union"_qs,
+ u"unsigned"_qs,
+ u"using"_qs,
+ u"virtual"_qs,
+ u"void"_qs,
+ u"volatile"_qs,
+ u"wchar_t"_qs,
+ u"while"_qs,
+ u"xor"_qs,
+ u"xor_eq"_qs,
+};
+
+static bool isReservedWord(QStringView word)
+{
+ if (word.startsWith(QChar(u'_')) && word.size() >= 2
+ && (word[1].isUpper() || word[1] == QChar(u'_'))) {
+ return true; // Indentifiers starting with underscore and uppercase are reserved in C++
+ }
+ return std::binary_search(std::begin(cppKeywords), std::end(cppKeywords), word);
+}
+
+template<typename InputIterator>
+static decltype(auto) findIrElement(const QmlIR::Document *doc, InputIterator first,
+ InputIterator last, QStringView name)
+{
+ auto it = std::find_if(first, last, [&](const auto &candidate) {
+ return name == doc->stringAt(candidate.nameIndex);
+ });
+ Q_ASSERT(it != last); // must be satisfied by the caller
+ return *it;
+}
+
+// convenience wrapper
+template<typename InputIterator>
+static decltype(auto) findIrLocation(const QmlIR::Document *doc, InputIterator first,
+ InputIterator last, QStringView name)
+{
+ return findIrElement(doc, first, last, name).location;
+}
+
+Q_LOGGING_CATEGORY(lcDefaultPasses, "qml.qmltc.compilerpasses", QtWarningMsg);
+
+void verifyTypes(const Qml2CppContext &context, QList<Qml2CppObject> &objects)
+{
+ const auto verifyProperty = [&](const QQmlJSMetaProperty &property,
+ const QmlIR::Object *irObject) {
+ // TODO: this whole verify function is a mess
+ Q_ASSERT(!property.isAlias());
+ if (property.propertyName().isEmpty())
+ context.recordError(irObject->location, u"Property with unknown name found"_qs);
+
+ const auto type = property.type();
+ if (type)
+ return;
+
+ // search irObject's properties and try to find matching one
+ const QStringView name = property.propertyName();
+ auto loc = findIrLocation(context.document, irObject->propertiesBegin(),
+ irObject->propertiesEnd(), name);
+ context.recordError(loc, u"Property '" + name + u"' of unknown type");
+ if (property.isList() && !isPointer(property)) {
+ context.recordError(loc,
+ u"Property '" + name
+ + u"' is a list type that contains non-pointer types");
+ }
+ };
+
+ // Note: this is an intentionally incomplete check
+ const auto verifyAlias = [&](const QQmlJSMetaProperty &alias, const QmlIR::Object *irObject,
+ const QQmlJSScope::ConstPtr &objectType) {
+ Q_ASSERT(alias.isAlias());
+ if (alias.propertyName().isEmpty()) {
+ context.recordError(irObject->location, u"Property alias with unknown name found"_qs);
+ return;
+ }
+
+ auto loc = findIrLocation(context.document, irObject->aliasesBegin(),
+ irObject->aliasesEnd(), alias.propertyName());
+ QStringList aliasExprBits = alias.aliasExpression().split(u'.');
+ if (aliasExprBits.isEmpty()) {
+ context.recordError(loc, u"Alias expression is invalid"_qs);
+ return;
+ }
+
+ QQmlJSScope::ConstPtr type = context.typeResolver->scopeForId(aliasExprBits[0], objectType);
+ if (!type) {
+ context.recordError(loc, u"Alias references an invalid id '" + aliasExprBits[0] + u"'");
+ return;
+ }
+ aliasExprBits.removeFirst();
+ for (const QString &bit : qAsConst(aliasExprBits)) {
+ if (bit.isEmpty()) {
+ context.recordError(loc, u"Alias expression contains empty piece"_qs);
+ break;
+ }
+ // TODO: we might need some better location, but this is not
+ // absolutely essential
+ QQmlJSMetaProperty p = type->property(bit);
+ if (!p.isValid()) {
+ Q_ASSERT(!type->hasProperty(bit)); // we're in deep trouble otherwise
+ context.recordError(loc,
+ u"Property '" + bit + u"' in '" + alias.aliasExpression()
+ + u"' does not exist");
+ break;
+ }
+ if (!p.type()) {
+ context.recordError(loc,
+ u"Property '" + bit + u"' in '" + alias.aliasExpression()
+ + u"' of unknown type");
+ break;
+ }
+ type = p.type();
+ // NB: the rest is checked at a later point when we set up
+ // to-be-compiled types properly
+ }
+ };
+
+ const auto verifyBinding = [&](const QmlIR::Binding &binding,
+ const QQmlJSScope::ConstPtr &type) {
+ QString propName = context.document->stringAt(binding.propertyNameIndex);
+ if (propName.isEmpty()) {
+ Q_ASSERT(type);
+ if (propName.isEmpty()) {
+ // if empty, try default property
+ for (QQmlJSScope::ConstPtr t = type->baseType(); t && propName.isEmpty();
+ t = t->baseType()) {
+ propName = t->defaultPropertyName();
+ }
+ }
+ // otherwise, it's an error
+ if (propName.isEmpty()) {
+ context.recordError(binding.location,
+ u"Cannot assign to non-existent default property"_qs);
+ return;
+ }
+ }
+
+ // ignore signal properties
+ if (QmlIR::IRBuilder::isSignalPropertyName(propName))
+ return;
+
+ // attached property is special
+ if (binding.type == QmlIR::Binding::Type_AttachedProperty) {
+ const auto [attachedObject, attachedType] = objects.at(binding.value.objectIndex);
+ if (!attachedObject || !attachedType) {
+ context.recordError(binding.location,
+ u"Binding on attached object '" + propName
+ + u"' of unknown type");
+ }
+ // Note: since attached object is part of objects, it will be
+ // verified at a later point anyway, so nothing has to be done here
+ return;
+ }
+
+ QQmlJSMetaProperty p = type->property(propName);
+ if (!p.isValid()) {
+ context.recordError(binding.location,
+ u"Binding on unknown property '" + propName + u"'");
+ return; // nothing to do here anyway
+ }
+ if (!p.type()) { // Note: aliases also have valid type
+ context.recordError(binding.location,
+ u"Binding on property '" + propName + u"' of unknown type");
+ }
+
+ // TODO: why isList() needed here?
+ if (!p.isWritable() && !p.isList()
+ && !(binding.flags & QmlIR::Binding::InitializerForReadOnlyDeclaration)
+ && binding.type != QmlIR::Binding::Type_GroupProperty) {
+ context.recordError(binding.location,
+ u"Binding on read-only property '" + propName + u"'");
+ }
+ };
+
+ for (const auto &object : objects) {
+ const auto [irObject, type] = object;
+ Q_ASSERT(irObject && type); // assume verified
+ if (!type->baseType()) { // base class exists
+ context.recordError(type->sourceLocation(), u"QML type has undefined base type"_qs);
+ // not critical for type verification, so do not break the iteration
+ }
+
+ if (type->isInCustomParserParent()) { // has QML_CUSTOMPARSER
+ // TODO: we might end up supporting it later, but this needs certain
+ // modifications to QML_CUSTOMPARSER macro at the very least to
+ // support parser flags - see e.g.
+ // QQmlCustomParser::AcceptsAttachedProperties
+
+#if 0
+ context.recordError(type->sourceLocation(),
+ u"QML type of this kind is not supported (QML_CUSTOMPARSER)"_qs);
+ continue;
+#else
+ // do silent error only because QQmlListModel is affected as well
+ qCDebug(lcDefaultPasses) << "QML type" << type->internalName()
+ << "of this kind is not supported (QML_CUSTOMPARSER)";
+#endif
+ }
+
+ // verify own properties
+ const auto properties = object.type->ownProperties();
+ for (auto it = properties.cbegin(); it != properties.cend(); ++it) {
+ const auto &prop = *it;
+ if (prop.isAlias())
+ verifyAlias(prop, irObject, object.type);
+ else
+ verifyProperty(prop, irObject);
+ }
+
+ // verify bindings
+ for (auto it = irObject->bindingsBegin(); it != irObject->bindingsEnd(); ++it)
+ verifyBinding(*it, type);
+ }
+}
+
+// verify that object's strings are not reserved C++ words:
+// * enumeration name (because enum name stays "as is") and keys
+// * own property names
+// * own method names and method parameter names
+// * TODO: bindings are ignored
+//
+// additionally, verify that no redefinition happens (e.g. no two properties
+// named the same way, etc.)
+static void checkObjectStringsForCollisions(const Qml2CppContext &context,
+ const Qml2CppObject &object)
+{
+ const auto isValidName = [&](QStringView name, auto getLocation, const QString &errorPrefix,
+ QSet<QStringView> &seenSymbols) {
+ // check name (e.g. property name) for correctness
+ const bool isReserved = isReservedWord(name);
+ const bool isSeenBefore = seenSymbols.contains(name);
+ // getLocation() might be slow, so only use it once if there is an error
+ decltype(getLocation()) location {}; // stub value
+ if (isReserved || isSeenBefore)
+ location = getLocation();
+
+ if (isReserved) {
+ context.recordError(location,
+ errorPrefix + u" '" + name
+ + u"' is a reserved C++ word, consider renaming");
+ }
+ if (isSeenBefore) {
+ context.recordError(location, errorPrefix + u" with this name already exists");
+ } else {
+ seenSymbols.insert(name);
+ }
+ };
+
+ const auto irObject = object.irObject;
+ const auto &type = object.type;
+
+ QSet<QStringView> uniqueSymbols;
+
+ const auto enums = type->ownEnumerations();
+ for (auto it = enums.cbegin(); it != enums.cend(); ++it) {
+ const QQmlJSMetaEnum e = it.value();
+ QStringView name = e.name();
+ QmlIR::Enum irEnum {}; // reuse for enumeration keys
+ const auto getEnumLocation = [&]() {
+ irEnum = findIrElement(context.document, irObject->enumsBegin(), irObject->enumsEnd(),
+ name);
+ return irEnum.location;
+ };
+ isValidName(name, getEnumLocation, u"Enumeration"_qs, uniqueSymbols);
+
+ const auto enumKeys = e.keys();
+ for (const auto &key : enumKeys) {
+ const auto getEnumKeyLocation = [&]() {
+ return findIrLocation(context.document, irEnum.enumValuesBegin(),
+ irEnum.enumValuesEnd(), key);
+ };
+ // no support for enum classes: each key is visible outside of enum
+ isValidName(key, getEnumKeyLocation, u"Enumeration key"_qs, uniqueSymbols);
+ }
+ }
+
+ const auto properties = type->ownProperties();
+ for (auto it = properties.cbegin(); it != properties.cend(); ++it) {
+ const QQmlJSMetaProperty &p = it.value();
+ QStringView name = p.propertyName();
+ const auto getPropertyLocation = [&]() { return p.type()->sourceLocation(); };
+ isValidName(name, getPropertyLocation, u"Property"_qs, uniqueSymbols);
+ }
+
+ const auto methods = type->ownMethods();
+ for (auto it = methods.cbegin(); it != methods.cend(); ++it) {
+ const QQmlJSMetaMethod &m = it.value();
+ QStringView name = m.methodName();
+ const auto getMethodLocation = [&]() { return m.returnType()->sourceLocation(); };
+ isValidName(name, getMethodLocation, u"Method"_qs, uniqueSymbols);
+
+ const auto parameterNames = m.parameterNames();
+ QSet<QStringView> uniqueParameters; // parameters can shadow
+ for (qsizetype i = 0; i < parameterNames.size(); ++i) {
+ QStringView paramName = parameterNames.at(i);
+ const auto getParamLocation = [&]() {
+ static auto paramTypes = m.parameterTypes();
+ return paramTypes.at(i)->sourceLocation();
+ };
+ isValidName(paramName, getParamLocation, u"Parameter"_qs, uniqueParameters);
+ }
+ }
+}
+
+void checkForNamingCollisionsWithCpp(const Qml2CppContext &context, QList<Qml2CppObject> &objects)
+{
+ for (const auto &object : objects)
+ checkObjectStringsForCollisions(context, object);
+}
+
+static void updateInternalName(QQmlJSScope::Ptr root, QString prefix,
+ QHash<QString, qsizetype> &typeCounts)
+{
+ if (!root)
+ return;
+
+ // gives unique C++ class name for a basic name based on type occurrence,
+ // additionally updating the type occurrence
+ const auto uniqueCppClassName = [&](QString basicName) -> QString {
+ if (!typeCounts.contains(basicName)) {
+ typeCounts.insert(basicName, 1);
+ return basicName;
+ }
+ return basicName + u"_" + QString::number(typeCounts[basicName]++);
+ };
+
+ switch (root->scopeType()) {
+ case QQmlJSScope::AttachedPropertyScope: {
+ // special case
+ Q_ASSERT(!root->baseTypeName().isEmpty());
+ root->setInternalName(root->baseTypeName());
+ break;
+ }
+ // case QQmlJSScope::JSFunctionScope:
+ // case QQmlJSScope::JSLexicalScope:
+ // case QQmlJSScope::GroupedPropertyScope:
+ // case QQmlJSScope::QMLScope:
+ // case QQmlJSScope::EnumScope:
+ default: {
+ root->setInternalName(prefix);
+ break;
+ }
+ }
+
+ const QList<QQmlJSScope::Ptr> children = root->childScopes();
+ for (QQmlJSScope::Ptr child : children) {
+ updateInternalName(child,
+ uniqueCppClassName(root->internalName() + u"_" + child->baseTypeName()),
+ typeCounts);
+ }
+}
+
+QHash<QString, qsizetype> makeUniqueCppNames(const Qml2CppContext &context,
+ QList<Qml2CppObject> &objects)
+{
+ // TODO: fix return type names of the methods as well
+ Q_UNUSED(objects);
+
+ QHash<QString, qsizetype> typeCounts;
+ for (const QString &str : cppKeywords)
+ typeCounts.insert(str, 1);
+ const auto knownCppNames = context.typeResolver->gatherKnownCppClassNames();
+ for (const QString &str : knownCppNames)
+ typeCounts.insert(str, 1);
+
+ // root is special:
+ QQmlJSScope::Ptr root = context.typeResolver->root();
+ QFileInfo fi(context.documentUrl);
+ auto cppName = fi.baseName();
+ // TODO: root is special and with implicit import directory cppName might be
+ // in typeCounts.
+#if 0
+ if (typeCounts.contains(cppName)) {
+ context.recordError(root->sourceLocation(),
+ u"Root object name '" + cppName + u"' is reserved");
+ return typeCounts;
+ }
+#endif
+ if (cppName.isEmpty()) {
+ context.recordError(root->sourceLocation(), u"Root object's name is empty"_qs);
+ return typeCounts;
+ }
+ typeCounts.insert(cppName, 1);
+
+ updateInternalName(root, cppName, typeCounts);
+ return typeCounts;
+}
+
+static void setupQmlCppType(const Qml2CppContext &context, const QQmlJSScope::Ptr &type,
+ QString filePath)
+{
+ Q_ASSERT(type);
+ if (filePath.isEmpty()) {
+ 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
+ return;
+ if (!filePath.endsWith(u".qml"_qs)) {
+ context.recordError(type->sourceLocation(),
+ u"QML type has non-QML origin (internal error)"_qs);
+ return;
+ }
+
+ // TODO: in reality, the file path could be something else instead of
+ // "<qml_file_name>_qmltc.h", but this is not supported right now
+ filePath = QFileInfo(filePath).baseName().toLower() + u".h"_qs;
+ type->setFileName(filePath); // this file name will be discovered during findCppIncludes
+
+ const auto properties = type->ownProperties();
+ for (auto it = properties.cbegin(); it != properties.cend(); ++it) {
+ QQmlJSMetaProperty p = it.value();
+ Q_ASSERT(it.key() == p.propertyName());
+
+ if (p.isAlias()) // we'll process aliases separately
+ continue;
+
+ Qml2CppPropertyData compiledData(p);
+ if (p.read().isEmpty())
+ p.setRead(compiledData.read);
+ if (p.write().isEmpty() && p.isWritable())
+ p.setWrite(compiledData.write);
+ if (p.bindable().isEmpty())
+ p.setBindable(compiledData.bindable);
+ // TODO: p.setNotify(compiledData.notify); - ?
+ type->addOwnProperty(p);
+ }
+}
+
+QSet<QString> setupQmlCppTypes(const Qml2CppContext &context, QList<Qml2CppObject> &objects)
+{
+ QSet<QString> qmlBaseTypes;
+ QHash<QString, std::pair<QString, QQmlJSScope::Ptr>> qmlTypes =
+ context.typeResolver->gatherCompiledQmlTypes();
+ for (const auto &object : objects) {
+ // 1. set up object itself
+ setupQmlCppType(context, object.type, context.documentUrl);
+
+ // 2. set up the base type if it is also QML originated
+ auto [url, potentialQmlType] = qmlTypes.value(object.type->baseTypeName());
+ if (potentialQmlType) { // it is an actual QML type
+ setupQmlCppType(context, potentialQmlType, url);
+ qmlBaseTypes.insert(object.type->baseTypeName());
+ }
+ }
+ return qmlBaseTypes;
+}
+
+#if 0
+// TODO: somewhat of a necessary crutch (or can we just not ignore it - and move
+// to qmllint)
+template<typename It, typename UnaryPredicate>
+static bool traverseAlias(It first, It last, QQmlJSMetaProperty p, UnaryPredicate pred)
+{
+ QQmlJSScope::ConstPtr type = p.type();
+ for (; first != last; ++first) {
+ p = type->property(*first);
+ if (pred(p))
+ return true;
+ type = p.type();
+ }
+ return false;
+}
+#endif
+
+// TODO: this should really become a part of the visitor. otherwise, the
+// to-be-compiled types from different documents do not get resolved aliases
+// a.k.a. we have to use QObject::setProperty() instead.
+static void resolveValidateOrSkipAlias(const Qml2CppContext &context,
+ const Qml2CppObject &aliasOwner, QQmlJSMetaProperty alias,
+ QSet<QQmlJSMetaProperty> &unresolved)
+{
+ Q_ASSERT(alias.isAlias());
+ Q_ASSERT(!alias.propertyName().isEmpty());
+ // TODO: we might need some better location, but this is not
+ // absolutely essential
+ auto loc = findIrLocation(context.document, aliasOwner.irObject->aliasesBegin(),
+ aliasOwner.irObject->aliasesEnd(), alias.propertyName());
+ QStringList aliasExprBits = alias.aliasExpression().split(u'.');
+ Q_ASSERT(aliasExprBits.size() > 1);
+
+ bool canHaveWrite = true;
+
+ QQmlJSMetaProperty p;
+ QQmlJSScope::ConstPtr type =
+ context.typeResolver->scopeForId(aliasExprBits[0], aliasOwner.type);
+ Q_ASSERT(type);
+ aliasExprBits.removeFirst();
+ for (qsizetype i = 0; i < aliasExprBits.size(); ++i) {
+ const QString &bit = qAsConst(aliasExprBits)[i];
+ p = type->property(bit);
+ Q_ASSERT(p.isValid() && p.type());
+
+ const bool isYetToBeResolvedAlias = unresolved.contains(p);
+ if (isYetToBeResolvedAlias) {
+ // we can do nothing with `alias` at the moment, so just skip it for
+ // now. the caller must then re-call this function, after `p` is
+ // processed. we assume no cycles exist at this stage, so the
+ // caller's code must terminate at some point.
+ return;
+ }
+
+ struct Remover
+ {
+ QSet<QQmlJSMetaProperty> &unresolved;
+ const QQmlJSMetaProperty &alias;
+ bool needUpdate = true;
+ ~Remover()
+ {
+ if (needUpdate)
+ unresolved.remove(alias);
+ }
+ } scopedRemover { unresolved, alias };
+
+ // validate and resolve the alias:
+ if (p.read().isEmpty()) { // we won't be able to read this property -- always an error
+ context.recordError(loc,
+ u"Property '" + bit + u"' of '" + type->internalName()
+ + u"' does not have READ method");
+ return;
+ }
+ // NB: last iteration is special, don't check for WRITE as it might
+ // not exist even for value property (in this case alias would only
+ // have a READ method, which is fine)
+ if (i == aliasExprBits.size() - 1)
+ continue; // NB: this will trigger scopedRemover, which is good
+
+ if (!p.isWritable()) {
+ canHaveWrite = false;
+ // check whether value type property has no WRITE method while it's
+ // subproperties have WRITE - if so, this is likely at least a
+ // warning/info
+
+#if 0 // TODO: something here is broken in release build
+ // NB: expensive in general case, pretty cheap otherwise, but still
+ // only run this in debug
+ const auto hasWrite = [](const QQmlJSMetaProperty &p) { return p.isWritable(); };
+ if (p.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Value
+ && traverseAlias(aliasExprBits.cbegin() + i, aliasExprBits.cend(), p, hasWrite)) {
+ qCDebug(lcDefaultPasses).noquote().nospace()
+ << context.documentUrl << ":" << loc.line << ":" << loc.column << ":"
+ << "Value type property '" << bit << u"' of '" << type->internalName()
+ << u"' does not have WRITE method while it probably should";
+ }
+#endif
+ }
+ type = p.type();
+ scopedRemover.needUpdate = false;
+ }
+
+ Q_ASSERT(!unresolved.contains(alias));
+
+ // here, `p` is the actually aliased property, `type` is the owner of this
+ // property
+
+ Qml2CppPropertyData compiledData(alias);
+ if (alias.read().isEmpty())
+ alias.setRead(compiledData.read);
+ // TODO: how to handle redefinition/overload?
+ if (QString setName = p.write(); !setName.isEmpty()) {
+ QList<QQmlJSMetaMethod> methods = type->methods(setName);
+ if (methods.size() > 1) {
+ context.recordError(loc,
+ u"WRITE method of aliased property '" + p.propertyName() + u"' of '"
+ + type->internalName() + u"' is ambiguous");
+ }
+ if (canHaveWrite && alias.write().isEmpty())
+ alias.setWrite(compiledData.write);
+ }
+ if (!p.bindable().isEmpty() && alias.bindable().isEmpty())
+ alias.setBindable(compiledData.bindable);
+ if (QString notifyName = p.notify(); !notifyName.isEmpty()) {
+ QList<QQmlJSMetaMethod> methods = type->methods(notifyName);
+ if (methods.size() > 1) {
+ context.recordError(loc,
+ u"NOTIFY method of aliased property '" + p.propertyName()
+ + u"' of '" + type->internalName() + u"' is ambiguous");
+ }
+ if (alias.notify().isEmpty())
+ alias.setNotify(compiledData.notify);
+ }
+ aliasOwner.type->addOwnProperty(alias);
+}
+
+QSet<QQmlJSMetaProperty> deferredResolveValidateAliases(const Qml2CppContext &context,
+ QList<Qml2CppObject> &objects)
+{
+ QSet<QQmlJSMetaProperty> aliasesToId;
+
+ QSet<QQmlJSMetaProperty> unresolved;
+ for (const auto &object : objects) {
+ const auto [irObject, type] = object;
+ Q_ASSERT(irObject && type); // assume verified
+
+ const auto properties = object.type->ownProperties();
+ for (QQmlJSMetaProperty p : properties) {
+ if (!p.isAlias())
+ continue;
+ QStringList aliasExprBits = p.aliasExpression().split(u'.');
+ Q_ASSERT(!aliasExprBits.isEmpty());
+ if (aliasExprBits.size() == 1) { // special case
+ Q_ASSERT(context.typeResolver->scopeForId(aliasExprBits.at(0), object.type));
+ // since it points to an object, set only the essential stuff
+ // and continue - it is already resolved after this
+ Qml2CppPropertyData compiledData(p);
+ if (p.read().isEmpty())
+ p.setRead(compiledData.read);
+ // NB: id-pointing aliases are read-only
+ type->addOwnProperty(p);
+ aliasesToId.insert(p);
+ continue;
+ }
+ unresolved.insert(p);
+ }
+ }
+
+ // NB: assume no cycles at this stage - see
+ // QQmlJSImportVisitor::resolveAliases()
+ while (!unresolved.isEmpty()) {
+ for (const auto &object : objects) {
+ const auto properties = object.type->ownProperties();
+ for (QQmlJSMetaProperty p : properties) {
+ if (!unresolved.contains(p)) // only contains aliases, so p.isAlias() is redundant
+ continue;
+ resolveValidateOrSkipAlias(context, object, p, unresolved);
+ }
+ }
+ }
+
+ return aliasesToId;
+}
+
+static void addFirstCppIncludeFromType(QSet<QString> &cppIncludes,
+ const QQmlJSScope::ConstPtr &type)
+{
+ auto t = QQmlJSScope::nonCompositeBaseType(type);
+ if (!t)
+ return;
+ if (QString includeFile = t->fileName(); !includeFile.isEmpty())
+ cppIncludes.insert(includeFile);
+}
+
+static void populateCppIncludes(QSet<QString> &cppIncludes, const QQmlJSScope::ConstPtr &type)
+{
+ const auto constructPrivateInclude = [](QStringView publicInclude) -> QString {
+ if (publicInclude.isEmpty())
+ return QString();
+ Q_ASSERT(publicInclude.endsWith(u".h"_qs) || publicInclude.endsWith(u".hpp"_qs));
+ const qsizetype dotLocation = publicInclude.endsWith(u".h"_qs) ? publicInclude.size() - 2
+ : publicInclude.size() - 4;
+ QStringView extension = publicInclude.sliced(dotLocation);
+ QStringView includeWithoutExtension = publicInclude.first(dotLocation);
+ // check if the "public" include is actually private
+ if (includeWithoutExtension.startsWith(u"private"))
+ return includeWithoutExtension.toString() + u"_p" + extension.toString();
+ return u"private/" + includeWithoutExtension.toString() + u"_p" + extension.toString();
+ };
+
+ // TODO: this pass is VERY slow - we have to do exhaustive search, however,
+ // because some classes could do forward declarations
+
+ // look in type itself
+ // addFirstCppIncludeFromType(cppIncludes, type);
+
+ // look in type hierarchy
+ 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())
+ cppIncludes.insert(includeFile);
+
+ // look in property types
+ const auto properties = t->ownProperties();
+ for (const QQmlJSMetaProperty &p : properties) {
+ addFirstCppIncludeFromType(cppIncludes, p.type());
+
+ if (p.isPrivate()) {
+ const QString ownersInclude = QQmlJSScope::nonCompositeBaseType(t)->fileName();
+ QString privateInclude = constructPrivateInclude(ownersInclude);
+ if (!privateInclude.isEmpty())
+ cppIncludes.insert(std::move(privateInclude));
+ }
+ }
+
+ // look in method types
+ const auto methods = t->ownMethods();
+ for (const QQmlJSMetaMethod &m : methods) {
+ addFirstCppIncludeFromType(cppIncludes, m.returnType());
+
+ const auto parameters = m.parameterTypes();
+ for (const auto &p : parameters)
+ addFirstCppIncludeFromType(cppIncludes, p);
+ }
+ }
+}
+
+QSet<QString> findCppIncludes(const Qml2CppContext &context, QList<Qml2CppObject> &objects)
+{
+ Q_UNUSED(objects);
+ QSet<QString> cppIncludes;
+
+ QQueue<QQmlJSScope::ConstPtr> objectsQueue;
+ objectsQueue.enqueue(context.typeResolver->root());
+
+ while (!objectsQueue.isEmpty()) {
+ QQmlJSScope::ConstPtr current = objectsQueue.dequeue();
+ Q_ASSERT(current); // assume verified
+
+ populateCppIncludes(cppIncludes, current);
+
+ const auto children = current->childScopes();
+ for (auto child : children)
+ objectsQueue.enqueue(child);
+ }
+
+ return cppIncludes;
+}
+
+template<typename Update>
+static void updateImplicitComponents(const Qml2CppContext &context, Qml2CppObject &object,
+ QList<Qml2CppObject> &objects, Update update)
+{
+ const auto checkAndUpdate = [&](const QmlIR::Binding &binding) {
+ if (binding.type != QmlIR::Binding::Type_Object)
+ return;
+ if (object.irObject->flags & QV4::CompiledData::Object::IsComponent) // already set
+ return;
+
+ QString propName = context.document->stringAt(binding.propertyNameIndex);
+ if (propName.isEmpty()) {
+ Q_ASSERT(object.type);
+ // if empty, try default property
+ for (QQmlJSScope::ConstPtr t = object.type->baseType(); t && propName.isEmpty();
+ t = t->baseType())
+ propName = t->defaultPropertyName();
+ }
+ Q_ASSERT(!propName.isEmpty()); // assume verified
+ QQmlJSMetaProperty p = object.type->property(propName);
+ Q_ASSERT(p.isValid()); // assume verified
+ Q_ASSERT(p.type()); // assume verified
+
+ // NB: in case property is a QQmlComponent, we need to handle it
+ // specially
+ if (p.type()->internalName() == u"QQmlComponent"_qs) {
+ // if it's an implicit component, call update function
+ Q_ASSERT(binding.value.objectIndex < objects.size());
+ update(objects[binding.value.objectIndex], binding.value.objectIndex);
+ }
+ };
+
+ std::for_each(object.irObject->bindingsBegin(), object.irObject->bindingsEnd(), checkAndUpdate);
+}
+
+QHash<int, int> findAndResolveImplicitComponents(const Qml2CppContext &context,
+ QList<Qml2CppObject> &objects)
+{
+ // TODO: this pass is incomplete. Other cases include component definitions:
+ // `Component { Item {} }` and maybe something else
+ int syntheticComponentCount = 0;
+ QHash<int, int> indexMapping;
+ const auto setQQmlComponentFlag = [&](Qml2CppObject &object, int objectIndex) {
+ object.irObject->flags |= QV4::CompiledData::Object::IsComponent;
+ Q_ASSERT(!indexMapping.contains(objectIndex));
+ // TODO: the mapping construction is very ad-hoc, it could be that the
+ // logic is more complicated. This is to compensate the lack of
+ // QQmlComponentAndAliasResolver::findAndRegisterImplicitComponents()
+ // that the QQmlTypeCompiler does
+ indexMapping[objectIndex] = int(objects.size()) + syntheticComponentCount++;
+ };
+
+ for (Qml2CppObject &o : objects)
+ updateImplicitComponents(context, o, objects, setQQmlComponentFlag);
+
+ return indexMapping;
+}
+
+static void setObjectId(const Qml2CppContext &context, int objectIndex,
+ QHash<int, int> &idToObjectIndex)
+{
+ // TODO: this method is basically a copy-paste of
+ // QQmlComponentAndAliasResolver::collectIdsAndAliases()
+
+ QmlIR::Object *irObject = context.document->objectAt(objectIndex);
+ Q_ASSERT(irObject); // assume verified
+
+ if (irObject->idNameIndex != 0) {
+ if (idToObjectIndex.contains(irObject->idNameIndex)) {
+ context.recordError(irObject->location, u"Object id is not unique"_qs);
+ return;
+ }
+ irObject->id = int(idToObjectIndex.size());
+ idToObjectIndex.insert(irObject->idNameIndex, objectIndex);
+ }
+
+ // NB: rejecting IsComponent only *after* the id is potentially set. this is
+ // aligned with QQmlTypeCompiler logic
+ if (irObject->flags & QV4::CompiledData::Object::IsComponent && objectIndex != 0)
+ return;
+
+ std::for_each(irObject->bindingsBegin(), irObject->bindingsEnd(),
+ [&](const QmlIR::Binding &binding) {
+ if (binding.type != QV4::CompiledData::Binding::Type_Object
+ && binding.type != QV4::CompiledData::Binding::Type_AttachedProperty
+ && binding.type != QV4::CompiledData::Binding::Type_GroupProperty) {
+ return;
+ }
+ setObjectId(context, binding.value.objectIndex, idToObjectIndex);
+ });
+}
+
+void setObjectIds(const Qml2CppContext &context, QList<Qml2CppObject> &objects)
+{
+ Q_UNUSED(objects);
+ QHash<int, int> idToObjectIndex;
+ Q_ASSERT(objects.at(0).irObject == context.document->objectAt(0));
+ // NB: unlike QQmlTypeCompiler, only set id for the root, completely
+ // ignoring the Components
+ setObjectId(context, 0, idToObjectIndex);
+}
diff --git a/tools/qmltc/prototype/qml2cppdefaultpasses.h b/tools/qmltc/prototype/qml2cppdefaultpasses.h
new file mode 100644
index 0000000000..f2635a4dbd
--- /dev/null
+++ b/tools/qmltc/prototype/qml2cppdefaultpasses.h
@@ -0,0 +1,75 @@
+/****************************************************************************
+**
+** 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 QML2CPPPASSES_H
+#define QML2CPPPASSES_H
+
+#include "prototype/qml2cppcontext.h"
+
+// verifies that each object, property (and etc.) has valid
+// QQmlJSScope::ConstPtr associated with it
+void verifyTypes(const Qml2CppContext &context, QList<Qml2CppObject> &objects);
+
+// checks whether some names in QQmlJSScope types:
+// - are known C++ vocabulary items
+// - are defined several times (e.g. property or enum with the same name appears
+// twice)
+void checkForNamingCollisionsWithCpp(const Qml2CppContext &context, QList<Qml2CppObject> &objects);
+
+// TODO: the below passes are not "default" (cannot be directly added)
+
+// ensures that all QQmlJSScope objects have unique internalName() and checks
+// whether some name is C++ reserved keyword
+QHash<QString, qsizetype> makeUniqueCppNames(const Qml2CppContext &context,
+ QList<Qml2CppObject> &objects);
+
+// sets up QML-originated base types of \a objects and \a objects themselves.
+// processed types are expected to be generated to C++. returns a set of QML
+// originated base types for all \a objects
+QSet<QString> setupQmlCppTypes(const Qml2CppContext &context, QList<Qml2CppObject> &objects);
+
+// resolves and finishes the verification of property aliases (checks that a
+// READ method is present and a WRITE method is present as well if the type is a
+// value type, etc.). returns aliases which point to ids. must be done after
+// setupQmlCppTypes() since some (own) aliased properties are not fully set up
+// untile then and thus do not have the necessary information
+QSet<QQmlJSMetaProperty> deferredResolveValidateAliases(const Qml2CppContext &context,
+ QList<Qml2CppObject> &objects);
+
+// finds all required C++ include files that are needed for the generated C++
+QSet<QString> findCppIncludes(const Qml2CppContext &context, QList<Qml2CppObject> &objects);
+
+// finds and resolves implicit QQmlComponent types. returns a mapping from
+// QmlIR::Object that is being wrapped into a QQmlComponent to an index of that
+// implicit wrapper, which is a synthetic QmlIR::Object
+QHash<int, int> findAndResolveImplicitComponents(const Qml2CppContext &context,
+ QList<Qml2CppObject> &objects);
+
+void setObjectIds(const Qml2CppContext &context, QList<Qml2CppObject> &objects);
+
+#endif // QML2CPPPASSES_H
diff --git a/tools/qmltc/prototype/qml2cpppropertyutils.h b/tools/qmltc/prototype/qml2cpppropertyutils.h
new file mode 100644
index 0000000000..f69e39dd83
--- /dev/null
+++ b/tools/qmltc/prototype/qml2cpppropertyutils.h
@@ -0,0 +1,73 @@
+/****************************************************************************
+**
+** 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 QML2CPPPROPERTYUTILS_H
+#define QML2CPPPROPERTYUTILS_H
+
+#include <private/qqmljsmetatypes_p.h>
+
+inline bool isPointer(const QQmlJSMetaProperty &p)
+{
+ Q_ASSERT(p.type());
+ return p.type()->accessSemantics() == QQmlJSScope::AccessSemantics::Reference;
+}
+
+inline QString getUnderlyingType(const QQmlJSMetaProperty &p)
+{
+ QString underlyingType = p.type()->internalName();
+ if (p.isList()) {
+ underlyingType = u"QQmlListProperty<" + underlyingType + u">";
+ } else if (isPointer(p)) {
+ underlyingType += u"*"_qs;
+ }
+ return underlyingType;
+}
+
+// dead simple class that, for a given property, creates information for
+// the Q_PROPERTY macro (READ/WRITE function names, etc.)
+struct Qml2CppPropertyData
+{
+ Qml2CppPropertyData(const QQmlJSMetaProperty &p) : Qml2CppPropertyData(p.propertyName()) { }
+
+ Qml2CppPropertyData(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";
+ }
+
+ QString read;
+ QString write;
+ QString bindable;
+ QString notify;
+};
+
+#endif // QML2CPPPROPERTYUTILS_H
diff --git a/tools/qmltc/prototype/qmlcompiler.h b/tools/qmltc/prototype/qmlcompiler.h
new file mode 100644
index 0000000000..c21ccf5f0c
--- /dev/null
+++ b/tools/qmltc/prototype/qmlcompiler.h
@@ -0,0 +1,177 @@
+/****************************************************************************
+**
+** 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
+};
+
+// 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;
+
+ // 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.cpp b/tools/qmltc/prototype/typeresolver.cpp
new file mode 100644
index 0000000000..5bfff44928
--- /dev/null
+++ b/tools/qmltc/prototype/typeresolver.cpp
@@ -0,0 +1,148 @@
+/****************************************************************************
+**
+** 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/typeresolver.h"
+#include "prototype/visitor.h"
+
+#include <private/qqmljsimporter_p.h>
+#include <private/qv4value_p.h>
+
+#include <QtCore/qqueue.h>
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qfileinfo.h>
+#include <QtCore/qdiriterator.h>
+
+Q_LOGGING_CATEGORY(lcTypeResolver2, "qml.compiler.typeresolver", QtInfoMsg);
+
+static QString prefixedName(const QString &prefix, const QString &name)
+{
+ Q_ASSERT(!prefix.endsWith(u'.'));
+ return prefix.isEmpty() ? name : (prefix + QLatin1Char('.') + name);
+}
+
+// TODO: this method is a (almost identical) copy-paste of QQmlJSImporter::importDirectory().
+static void customImportDirectory(QHash<QString, std::pair<QString, QQmlJSScope::Ptr>> &qmlTypes,
+ QQmlJSImporter *importer, const QString &directory,
+ const QString &prefix = QString())
+{
+ if (directory.startsWith(u':')) {
+ if (QQmlJSResourceFileMapper *mapper = importer->resourceFileMapper()) {
+ const auto resources = mapper->filter(
+ QQmlJSResourceFileMapper::resourceQmlDirectoryFilter(directory.mid(1)));
+ for (const auto &entry : resources) {
+ const QString name = QFileInfo(entry.resourcePath).baseName();
+ if (name.front().isUpper()) {
+ qmlTypes.insert(
+ prefixedName(prefix, name),
+ std::make_pair(entry.filePath, importer->importFile(entry.filePath)));
+ }
+ }
+ }
+ return;
+ }
+
+ QDirIterator it { directory, QStringList() << QLatin1String("*.qml"), QDir::NoFilter };
+ while (it.hasNext()) {
+ it.next();
+ if (!it.fileName().front().isUpper())
+ continue; // Non-uppercase names cannot be imported anyway.
+
+ qmlTypes.insert(prefixedName(prefix, QFileInfo(it.filePath()).baseName()),
+ std::make_pair(it.filePath(), importer->importFile(it.filePath())));
+ }
+}
+
+namespace Qmltc {
+
+TypeResolver::TypeResolver(QQmlJSImporter *importer)
+ : QQmlJSTypeResolver(importer), m_importer(importer)
+{
+ Q_ASSERT(m_importer);
+}
+
+void TypeResolver::init(Visitor &visitor, QQmlJS::AST::Node *program)
+{
+ QQmlJSTypeResolver::init(&visitor, program);
+ m_root = visitor.result();
+
+ QQueue<QQmlJSScope::Ptr> objects;
+ objects.enqueue(m_root);
+ while (!objects.isEmpty()) {
+ const QQmlJSScope::Ptr object = objects.dequeue();
+ const QQmlJS::SourceLocation location = object->sourceLocation();
+ qCDebug(lcTypeResolver2()).nospace() << "inserting " << object.data() << " at "
+ << location.startLine << ':' << location.startColumn;
+ m_objectsByLocationNonConst.insert({ location.startLine, location.startColumn }, object);
+
+ const auto childScopes = object->childScopes();
+ for (const auto &childScope : childScopes)
+ objects.enqueue(childScope);
+ }
+
+ m_implicitImportDir = visitor.getImplicitImportDirectory();
+ m_importedDirs = visitor.getImportedDirectories();
+ m_importedFiles = visitor.getImportedQmlFiles();
+}
+
+QQmlJSScope::Ptr TypeResolver::scopeForLocation(const QV4::CompiledData::Location &location) const
+{
+ qCDebug(lcTypeResolver2()).nospace()
+ << "looking for object at " << location.line << ':' << location.column;
+ return m_objectsByLocationNonConst.value(location);
+}
+
+QHash<QString, std::pair<QString, QQmlJSScope::Ptr>> TypeResolver::gatherCompiledQmlTypes() const
+{
+ QHash<QString, std::pair<QString, QQmlJSScope::Ptr>> qmlTypes;
+ // implicit directory first
+ customImportDirectory(qmlTypes, m_importer, m_implicitImportDir);
+ // followed by subdirectories and absolute directories
+ for (const QString &dir : m_importedDirs) {
+ QString dirPath = dir;
+ const QFileInfo dirInfo(dir);
+ if (dirInfo.isRelative()) {
+ dirPath = QDir(m_implicitImportDir).filePath(dirPath);
+ }
+ customImportDirectory(qmlTypes, m_importer, dirPath);
+ }
+ // followed by individual files
+ for (const QString &file : m_importedFiles) {
+ QString filePath = file;
+ const QFileInfo fileInfo(file);
+ if (fileInfo.isRelative()) {
+ filePath = QDir(m_implicitImportDir).filePath(filePath);
+ }
+ // TODO: file importing is untested
+ const QString baseName = QFileInfo(filePath).baseName();
+ if (baseName.front().isUpper())
+ qmlTypes.insert(baseName, std::make_pair(filePath, m_importer->importFile(filePath)));
+ }
+ return qmlTypes;
+}
+
+}
diff --git a/tools/qmltc/prototype/typeresolver.h b/tools/qmltc/prototype/typeresolver.h
new file mode 100644
index 0000000000..6dd98bb470
--- /dev/null
+++ b/tools/qmltc/prototype/typeresolver.h
@@ -0,0 +1,87 @@
+/****************************************************************************
+**
+** 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);
+
+ // helper function for code generator
+ QStringList gatherKnownCppClassNames() const
+ {
+ QStringList cppNames;
+ QHash<QString, QQmlJSScope::ConstPtr> builtins = m_importer->builtinInternalNames();
+ cppNames.reserve(builtins.size() + m_imports.size());
+ const auto getInternalName = [](const QQmlJSScope::ConstPtr &t) {
+ return t->internalName();
+ };
+ std::transform(builtins.cbegin(), builtins.cend(), std::back_inserter(cppNames),
+ getInternalName);
+ std::transform(m_imports.cbegin(), m_imports.cend(), std::back_inserter(cppNames),
+ getInternalName);
+ return cppNames;
+ }
+
+ 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 a mapping from "QML name" (typically QML file name without
+ // extension) to {file path, QML scope/type pointer} pair. the mapping
+ // contains only native, non-C++ originated, QML types that would be
+ // compiled into C++.
+ QHash<QString, std::pair<QString, QQmlJSScope::Ptr>> gatherCompiledQmlTypes() const;
+
+private:
+ QQmlJSImporter *m_importer = nullptr;
+
+ QHash<QV4::CompiledData::Location, QQmlJSScope::Ptr> m_objectsByLocationNonConst;
+ QString m_implicitImportDir; // implicit QML import directory
+ QQmlJSScope::Ptr m_root;
+
+ QList<QString> m_importedDirs;
+ QList<QString> m_importedFiles;
+};
+}
+
+#endif // TYPERESOLVER_H
diff --git a/tools/qmltc/prototype/visitor.cpp b/tools/qmltc/prototype/visitor.cpp
new file mode 100644
index 0000000000..47d16cf505
--- /dev/null
+++ b/tools/qmltc/prototype/visitor.cpp
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** 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::UiImport *import)
+{
+ if (!QQmlJSImportVisitor::visit(import))
+ return false;
+
+ auto filename = import->fileName.toString();
+ if (filename.isEmpty())
+ return true;
+
+ const QFileInfo file(filename);
+ const QString absolute =
+ file.isRelative() ? QDir(m_implicitImportDirectory).filePath(filename) : filename;
+
+ QFileInfo path(absolute);
+ if (path.isDir()) {
+ m_importedDirectories.append(filename);
+ } else if (path.isFile() && absolute.endsWith(u".qml"_qs)) {
+ m_importedFiles.append(filename);
+ }
+ return true;
+}
+}
diff --git a/tools/qmltc/prototype/visitor.h b/tools/qmltc/prototype/visitor.h
new file mode 100644
index 0000000000..4fc925d465
--- /dev/null
+++ b/tools/qmltc/prototype/visitor.h
@@ -0,0 +1,62 @@
+/****************************************************************************
+**
+** 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
+{
+ QStringList m_importedDirectories;
+ QStringList m_importedFiles;
+
+public:
+ Visitor(QQmlJSImporter *importer, QQmlJSLogger *logger, const QString &implicitImportDirectory,
+ const QStringList &qmltypesFiles = QStringList());
+
+ bool visit(QQmlJS::AST::UiImport *import) override;
+
+ QString getImplicitImportDirectory() const { return m_implicitImportDirectory; }
+ QStringList getImportedDirectories() const { return m_importedDirectories; }
+ QStringList getImportedQmlFiles() const { return m_importedFiles; }
+};
+}
+
+#endif // VISITOR_H