aboutsummaryrefslogtreecommitdiffstats
path: root/tools/qmltc
diff options
context:
space:
mode:
authorAndrei Golubev <andrei.golubev@qt.io>2021-11-12 09:28:33 +0100
committerAndrei Golubev <andrei.golubev@qt.io>2021-11-17 18:04:41 +0100
commit794051a115ca5d03b34fa2288eb3c019f75b34aa (patch)
tree21b51933ba6dd583e2efce51f2760517bd026719 /tools/qmltc
parenta67eba2513504836348f3e07a47c855ea4be413e (diff)
qmltc: Learn to set QML context for objects
Add the minimal test along the way. The most we can do at present is to check that root object's context is set correctly. Comprehensive tests require binding compilation The QQmlParserStatus::classBegin() shenanigans are skipped for now Change-Id: I5c28c8a4406753dcf0fc93d968800b4376ec6017 Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Diffstat (limited to 'tools/qmltc')
-rw-r--r--tools/qmltc/CMakeLists.txt1
-rw-r--r--tools/qmltc/qmltccodewriter.cpp24
-rw-r--r--tools/qmltc/qmltccodewriter.h2
-rw-r--r--tools/qmltc/qmltccompiler.cpp26
-rw-r--r--tools/qmltc/qmltccompiler.h1
-rw-r--r--tools/qmltc/qmltccompilerpieces.h157
-rw-r--r--tools/qmltc/qmltcoutputir.h1
7 files changed, 207 insertions, 5 deletions
diff --git a/tools/qmltc/CMakeLists.txt b/tools/qmltc/CMakeLists.txt
index 415dc0f245..cda6eef7e8 100644
--- a/tools/qmltc/CMakeLists.txt
+++ b/tools/qmltc/CMakeLists.txt
@@ -11,6 +11,7 @@ qt_internal_add_tool(${target_name}
qmltctyperesolver.h
qmltcvisitor.h qmltcvisitor.cpp
qmltccompiler.h qmltccompiler.cpp
+ qmltccompilerpieces.h
qmltccompilerutils.h
qmltcpropertyutils.h
DEFINES
diff --git a/tools/qmltc/qmltccodewriter.cpp b/tools/qmltc/qmltccodewriter.cpp
index 02cb47243b..6c2c018833 100644
--- a/tools/qmltc/qmltccodewriter.cpp
+++ b/tools/qmltc/qmltccodewriter.cpp
@@ -192,6 +192,9 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProgram &progra
code.rawAppendToHeader(u"/* QMLTC: NOT IMPLEMENTED */");
code.rawAppendToCpp(u"/* QMLTC: NOT IMPLEMENTED */");
+ // url method comes first
+ writeUrl(code, program.urlMethod);
+
// forward declare all the types first
for (const QmltcType &type : qAsConst(program.compiledTypes))
code.rawAppendToHeader(u"class " + type.cppType + u";");
@@ -372,4 +375,25 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcProperty &prop)
prop.containingClass, prop.cppType, prop.name, prop.signalName));
}
+void QmltcCodeWriter::writeUrl(QmltcOutputWrapper &code, const QmltcMethod &urlMethod)
+{
+ // unlike ordinary methods, url function only exists in .cpp
+ Q_ASSERT(!urlMethod.returnType.isEmpty());
+ const auto [hSignature, _] = functionSignatures(urlMethod);
+ Q_UNUSED(_);
+ // Note: augment return type with preambles in declaration
+ QString prefix = urlMethod.declarationPrefixes.join(u' ');
+ if (!prefix.isEmpty())
+ prefix.append(u' ');
+ code.rawAppendToCpp(prefix + urlMethod.returnType + u" " + hSignature);
+ code.rawAppendToCpp(u"{");
+ {
+ QmltcOutputWrapper::CppIndentationScope cppIndent(&code);
+ Q_UNUSED(cppIndent);
+ for (const QString &line : qAsConst(urlMethod.body))
+ code.rawAppendToCpp(line);
+ }
+ code.rawAppendToCpp(u"}");
+}
+
QT_END_NAMESPACE
diff --git a/tools/qmltc/qmltccodewriter.h b/tools/qmltc/qmltccodewriter.h
index f577d1a7be..1d1e023d43 100644
--- a/tools/qmltc/qmltccodewriter.h
+++ b/tools/qmltc/qmltccodewriter.h
@@ -51,6 +51,8 @@ struct QmltcCodeWriter
static void write(QmltcOutputWrapper &code, const QmltcCtor &ctor);
static void write(QmltcOutputWrapper &code, const QmltcVariable &var);
static void write(QmltcOutputWrapper &code, const QmltcProperty &prop);
+
+ static void writeUrl(QmltcOutputWrapper &code, const QmltcMethod &urlMethod); // special
};
QT_END_NAMESPACE
diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp
index e134258eea..a6671423b7 100644
--- a/tools/qmltc/qmltccompiler.cpp
+++ b/tools/qmltc/qmltccompiler.cpp
@@ -31,6 +31,7 @@
#include "qmltccodewriter.h"
#include "qmltccompilerutils.h"
#include "qmltcpropertyutils.h"
+#include "qmltccompilerpieces.h"
#include <QtCore/qloggingcategory.h>
@@ -40,6 +41,9 @@ QT_BEGIN_NAMESPACE
Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg);
+const QString QmltcCodeGenerator::privateEngineName = u"ePriv"_qs;
+const QString QmltcCodeGenerator::urlMethodName = u"q_qmltc_docUrl"_qs;
+
QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor,
QQmlJSLogger *logger)
: m_url(url), m_typeResolver(resolver), m_visitor(visitor), m_logger(logger)
@@ -59,6 +63,9 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info)
QList<QmltcType> compiledTypes;
compiledTypes.reserve(types.size());
+ QmltcMethod urlMethod;
+ compileUrlMethod(urlMethod);
+
for (const QQmlJSScope::ConstPtr &type : types) {
compiledTypes.emplaceBack(); // creates empty type
compileType(compiledTypes.back(), type);
@@ -73,12 +80,22 @@ void QmltcCompiler::compile(const QmltcCompilerInfo &info)
program.outNamespace = m_info.outputNamespace;
program.compiledTypes = compiledTypes;
program.includes = m_visitor->cppIncludeFiles();
+ program.urlMethod = urlMethod;
QmltcOutput out;
QmltcOutputWrapper code(out);
QmltcCodeWriter::write(code, program);
}
+void QmltcCompiler::compileUrlMethod(QmltcMethod &urlMethod)
+{
+ urlMethod.name = QmltcCodeGenerator::urlMethodName;
+ urlMethod.returnType = u"const QUrl&"_qs;
+ urlMethod.body << u"static QUrl url {QStringLiteral(\"qrc:%1\")};"_qs.arg(m_info.resourcePath);
+ urlMethod.body << u"return url;"_qs;
+ urlMethod.declarationPrefixes << u"static"_qs;
+}
+
void QmltcCompiler::compileType(QmltcType &current, const QQmlJSScope::ConstPtr &type)
{
if (type->isSingleton()) {
@@ -169,11 +186,10 @@ void QmltcCompiler::compileType(QmltcType &current, const QQmlJSScope::ConstPtr
current.basicCtor.body << u"QQml_setParent_noEvent(this, " + parent.name + u");";
}
+ QmltcCodeGenerator generator { rootType };
+
// compilation stub:
current.fullCtor.body << u"Q_UNUSED(engine);"_qs;
- current.init.body << u"Q_UNUSED(creator);"_qs;
- current.init.body << u"Q_UNUSED(engine);"_qs;
- current.init.body << u"Q_UNUSED(parentContext);"_qs;
current.finalize.body << u"Q_UNUSED(engine);"_qs;
current.finalize.body << u"Q_UNUSED(creator);"_qs;
if (documentRoot) {
@@ -188,7 +204,6 @@ void QmltcCompiler::compileType(QmltcType &current, const QQmlJSScope::ConstPtr
+ u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* "
u"finalize */ true);";
- current.init.body << u"Q_UNUSED(canFinalize);"_qs;
current.finalize.body << u"Q_UNUSED(canFinalize);"_qs;
} else {
current.fullCtor.body << u"// not document root:"_qs;
@@ -196,7 +211,8 @@ void QmltcCompiler::compileType(QmltcType &current, const QQmlJSScope::ConstPtr
current.fullCtor.body << current.init.name
+ u"(creator, engine, QQmlData::get(parent)->outerContext);";
}
- current.init.body << u"return nullptr;"_qs;
+
+ auto postponedGenerate = generator.generate_qmlContextSetup(current, type);
// compile components of a type:
// - enums
diff --git a/tools/qmltc/qmltccompiler.h b/tools/qmltc/qmltccompiler.h
index 2997c3c7d1..c934dfffac 100644
--- a/tools/qmltc/qmltccompiler.h
+++ b/tools/qmltc/qmltccompiler.h
@@ -63,6 +63,7 @@ private:
QQmlJSLogger *m_logger = nullptr;
QmltcCompilerInfo m_info {}; // miscellaneous input/output information
+ void compileUrlMethod(QmltcMethod &urlMethod);
void compileType(QmltcType &current, const QQmlJSScope::ConstPtr &type);
void compileEnum(QmltcType &current, const QQmlJSMetaEnum &e);
void compileMethod(QmltcType &current, const QQmlJSMetaMethod &m);
diff --git a/tools/qmltc/qmltccompilerpieces.h b/tools/qmltc/qmltccompilerpieces.h
new file mode 100644
index 0000000000..94d14a3abd
--- /dev/null
+++ b/tools/qmltc/qmltccompilerpieces.h
@@ -0,0 +1,157 @@
+/****************************************************************************
+**
+** 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 QMLTCCOMPILERPIECES_H
+#define QMLTCCOMPILERPIECES_H
+
+#include <QtCore/qscopeguard.h>
+#include <QtCore/qstringbuilder.h>
+
+#include "qmltcoutputir.h"
+#include "qmltcvisitor.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \internal
+
+ Helper class that generates different code for the output IR. Takes care of
+ complicated, repetitive, nasty logic which is better kept in a single
+ confined place
+*/
+struct QmltcCodeGenerator
+{
+ static const QString privateEngineName;
+ static const QString urlMethodName;
+
+ QQmlJSScope::ConstPtr documentRoot;
+
+ /*!
+ \internal
+
+ Generates \a current.init 's code which sets up the QQmlContext for \a
+ type. Returns a QScopeGuard with the final instructions of the function
+ that have to be generated at a later point, once everything else is
+ compiled.
+ */
+ [[nodiscard]] inline decltype(auto) generate_qmlContextSetup(QmltcType &current,
+ const QQmlJSScope::ConstPtr &type);
+};
+
+inline decltype(auto)
+QmltcCodeGenerator::generate_qmlContextSetup(QmltcType &current, const QQmlJSScope::ConstPtr &type)
+{
+ // qmltc_init()'s parameters:
+ // * QQmltcObjectCreationHelper* creator
+ // * QQmlEngine* engine
+ // * const QQmlRefPointer<QQmlContextData>& parentContext
+ // * bool canFinalize [optional, when document root]
+ const bool isDocumentRoot = type == documentRoot;
+
+ current.init.body << u"Q_UNUSED(creator);"_qs; // can happen sometimes
+
+ current.init.body << u"auto context = parentContext;"_qs;
+
+ // if parent scope is a QML type and is not a (current) document root, the
+ // parentContext we passed as input to this object is a context of another
+ // document. we need to fix it by using parentContext->parent()
+ if (auto parentScope = type->parentScope(); parentScope && parentScope->isComposite()
+ && parentScope->scopeType() == QQmlJSScope::QMLScope && parentScope != documentRoot) {
+ current.init.body << u"// NB: context->parent() is the context of this document"_qs;
+ current.init.body << u"context = context->parent();"_qs;
+ }
+
+ // any object with QML base class has to call base's init method
+ if (auto base = type->baseType(); base->isComposite()) {
+ QString lhs;
+ // init creates new context. for document root, it's going to be a real
+ // parent context, so store it temporarily in `context` variable
+ if (isDocumentRoot)
+ lhs = u"context = "_qs;
+ current.init.body << u"// 0. call base's init method"_qs;
+ current.init.body << QStringLiteral(
+ "%1%2::%3(creator, engine, context, /* finalize */ false)")
+ .arg(lhs, base->internalName(), current.init.name);
+ }
+
+ current.init.body
+ << QStringLiteral("auto %1 = QQmlEnginePrivate::get(engine);").arg(privateEngineName);
+ current.init.body << QStringLiteral("Q_UNUSED(%1);").arg(privateEngineName); // precaution
+
+ // when generating root, we need to create a new (document-level) context.
+ // otherwise, just use existing context as is
+ if (isDocumentRoot) {
+ current.init.body << u"// 1. create new QML context for this document"_qs;
+ // TODO: the last 2 parameters are {0, true} because we deal with
+ // document root only here. in reality, there are inline components
+ // which need { index, true } instead
+ current.init.body
+ << QStringLiteral(
+ "context = %1->createInternalContext(%1->compilationUnitFromUrl(%2()), "
+ "context, 0, true);")
+ .arg(privateEngineName, urlMethodName);
+ } else {
+ current.init.body << u"// 1. use current context as this object's context"_qs;
+ current.init.body << u"// context = context;"_qs;
+ }
+
+ if (!type->baseType()->isComposite() || isDocumentRoot) {
+ current.init.body << u"// 2. set context for this object"_qs;
+ current.init.body << QStringLiteral(
+ "%1->setInternalContext(this, context, QQmlContextData::%2);")
+ .arg(privateEngineName,
+ (isDocumentRoot ? u"DocumentRoot"_qs
+ : u"OrdinaryObject"_qs));
+ if (isDocumentRoot)
+ current.init.body << u"context->setContextObject(this);"_qs;
+ }
+
+ if (int id = type->runtimeId(); id >= 0) {
+ current.init.body << u"// 3. set id since it is provided"_qs;
+ current.init.body << u"context->setIdValue(%1, this);"_qs.arg(QString::number(id));
+ }
+
+ // TODO: add QQmlParserStatus::classBegin() to init
+
+ const auto generateFinalLines = [&current, isDocumentRoot]() {
+ if (isDocumentRoot) {
+ current.init.body << u"// 4. call finalize in the document root"_qs;
+ current.init.body << u"if (canFinalize) {"_qs;
+ current.init.body << QStringLiteral(" %1(creator, engine, /* finalize */ true);")
+ .arg(current.finalize.name);
+ current.init.body << u"}"_qs;
+ }
+ current.init.body << u"return context;"_qs;
+ };
+
+ return QScopeGuard(generateFinalLines);
+}
+
+QT_END_NAMESPACE
+
+#endif // QMLTCCOMPILERPIECES_H
diff --git a/tools/qmltc/qmltcoutputir.h b/tools/qmltc/qmltcoutputir.h
index 4b66a8de1b..9d000cbc11 100644
--- a/tools/qmltc/qmltcoutputir.h
+++ b/tools/qmltc/qmltcoutputir.h
@@ -143,6 +143,7 @@ struct QmltcProgram
QString hPath; // C++ output .h path
QString outNamespace;
QSet<QString> includes; // non-default C++ include files
+ QmltcMethod urlMethod; // returns QUrl of the QML document
QList<QmltcType> compiledTypes; // all QML types that are compiled to C++
};