diff options
author | Andrei Golubev <andrei.golubev@qt.io> | 2021-11-12 09:28:33 +0100 |
---|---|---|
committer | Andrei Golubev <andrei.golubev@qt.io> | 2021-11-17 18:04:41 +0100 |
commit | 794051a115ca5d03b34fa2288eb3c019f75b34aa (patch) | |
tree | 21b51933ba6dd583e2efce51f2760517bd026719 /tools/qmltc | |
parent | a67eba2513504836348f3e07a47c855ea4be413e (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.txt | 1 | ||||
-rw-r--r-- | tools/qmltc/qmltccodewriter.cpp | 24 | ||||
-rw-r--r-- | tools/qmltc/qmltccodewriter.h | 2 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.cpp | 26 | ||||
-rw-r--r-- | tools/qmltc/qmltccompiler.h | 1 | ||||
-rw-r--r-- | tools/qmltc/qmltccompilerpieces.h | 157 | ||||
-rw-r--r-- | tools/qmltc/qmltcoutputir.h | 1 |
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 ¤t, const QQmlJSScope::ConstPtr &type) { if (type->isSingleton()) { @@ -169,11 +186,10 @@ void QmltcCompiler::compileType(QmltcType ¤t, 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 ¤t, 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 ¤t, 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 ¤t, const QQmlJSScope::ConstPtr &type); void compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e); void compileMethod(QmltcType ¤t, 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 ¤t, + const QQmlJSScope::ConstPtr &type); +}; + +inline decltype(auto) +QmltcCodeGenerator::generate_qmlContextSetup(QmltcType ¤t, 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 = [¤t, 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++ }; |