2021-09-16 07:35:33 +00:00
|
|
|
/****************************************************************************
|
|
|
|
**
|
|
|
|
** 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 "qmltccompiler.h"
|
2021-09-17 15:01:56 +00:00
|
|
|
#include "qmltcoutputir.h"
|
|
|
|
#include "qmltccodewriter.h"
|
2021-10-29 09:56:25 +00:00
|
|
|
#include "qmltcpropertyutils.h"
|
2021-11-12 08:28:33 +00:00
|
|
|
#include "qmltccompilerpieces.h"
|
2021-11-01 15:18:30 +00:00
|
|
|
|
2022-03-10 08:37:01 +00:00
|
|
|
#include "prototype/codegenerator.h"
|
|
|
|
|
2021-11-15 13:31:42 +00:00
|
|
|
#include <QtCore/qloggingcategory.h>
|
2021-12-17 14:52:17 +00:00
|
|
|
#include <private/qqmljsutils_p.h>
|
2021-09-16 07:35:33 +00:00
|
|
|
|
2021-10-29 10:43:44 +00:00
|
|
|
#include <algorithm>
|
|
|
|
|
2021-09-16 07:35:33 +00:00
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
using namespace Qt::StringLiterals;
|
|
|
|
|
2021-09-21 08:52:24 +00:00
|
|
|
Q_LOGGING_CATEGORY(lcQmltcCompiler, "qml.qmltc.compiler", QtWarningMsg);
|
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
const QString QmltcCodeGenerator::privateEngineName = u"ePriv"_s;
|
|
|
|
const QString QmltcCodeGenerator::typeCountName = u"q_qmltc_typeCount"_s;
|
2021-11-12 08:28:33 +00:00
|
|
|
|
2021-09-21 08:52:24 +00:00
|
|
|
QmltcCompiler::QmltcCompiler(const QString &url, QmltcTypeResolver *resolver, QmltcVisitor *visitor,
|
|
|
|
QQmlJSLogger *logger)
|
|
|
|
: m_url(url), m_typeResolver(resolver), m_visitor(visitor), m_logger(logger)
|
2021-09-16 07:35:33 +00:00
|
|
|
{
|
|
|
|
Q_UNUSED(m_typeResolver);
|
2021-09-21 08:52:24 +00:00
|
|
|
Q_ASSERT(!hasErrors());
|
2021-09-16 07:35:33 +00:00
|
|
|
}
|
|
|
|
|
2022-03-10 08:37:01 +00:00
|
|
|
// needed due to std::unique_ptr<CodeGenerator> with CodeGenerator being
|
|
|
|
// incomplete type in the header (~std::unique_ptr<> fails with a static_assert)
|
|
|
|
QmltcCompiler::~QmltcCompiler() = default;
|
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
QString QmltcCompiler::newSymbol(const QString &base)
|
|
|
|
{
|
|
|
|
QString symbol = base;
|
|
|
|
symbol.replace(QLatin1String("."), QLatin1String("_"));
|
|
|
|
while (symbol.startsWith(QLatin1Char('_')) && symbol.size() >= 2
|
|
|
|
&& (symbol[1].isUpper() || symbol[1] == QLatin1Char('_'))) {
|
|
|
|
symbol.remove(0, 1);
|
|
|
|
}
|
|
|
|
if (!m_symbols.contains(symbol)) {
|
|
|
|
m_symbols.insert(symbol, 1);
|
|
|
|
} else {
|
|
|
|
symbol += u"_" + QString::number(m_symbols[symbol]++);
|
|
|
|
}
|
|
|
|
return symbol;
|
|
|
|
}
|
|
|
|
|
2022-06-03 11:52:47 +00:00
|
|
|
void QmltcCompiler::compile(const QmltcCompilerInfo &info)
|
2021-09-16 07:35:33 +00:00
|
|
|
{
|
|
|
|
m_info = info;
|
2021-09-21 08:52:24 +00:00
|
|
|
Q_ASSERT(!m_info.outputCppFile.isEmpty());
|
|
|
|
Q_ASSERT(!m_info.outputHFile.isEmpty());
|
|
|
|
Q_ASSERT(!m_info.resourcePath.isEmpty());
|
|
|
|
|
2022-03-10 08:37:01 +00:00
|
|
|
m_prototypeCodegen =
|
2022-06-03 11:52:47 +00:00
|
|
|
std::make_unique<CodeGenerator>(m_url, m_logger, m_typeResolver, m_visitor, &info);
|
|
|
|
|
|
|
|
// Note: we only compile "pure" QML types. any component-wrapped type is
|
|
|
|
// expected to appear through a binding
|
|
|
|
auto pureTypes = m_visitor->pureQmlTypes();
|
|
|
|
Q_ASSERT(m_visitor->result() == pureTypes.at(0));
|
2022-03-10 08:37:01 +00:00
|
|
|
|
|
|
|
QSet<QString> cppIncludesFromPrototype;
|
2022-06-03 11:52:47 +00:00
|
|
|
m_prototypeCodegen->prepare(&cppIncludesFromPrototype,
|
|
|
|
QSet<QQmlJSScope::ConstPtr>(pureTypes.cbegin(), pureTypes.cend()));
|
2022-03-10 08:37:01 +00:00
|
|
|
if (hasErrors())
|
|
|
|
return;
|
|
|
|
|
|
|
|
const auto isComponent = [](const QQmlJSScope::ConstPtr &type) {
|
|
|
|
auto base = type->baseType();
|
2022-03-21 09:21:18 +00:00
|
|
|
return base && base->internalName() == u"QQmlComponent"_s;
|
2022-03-10 08:37:01 +00:00
|
|
|
};
|
|
|
|
|
2022-04-26 08:42:16 +00:00
|
|
|
QmltcCodeGenerator generator { m_url, m_visitor };
|
|
|
|
|
2021-11-12 08:28:33 +00:00
|
|
|
QmltcMethod urlMethod;
|
2022-04-26 08:42:16 +00:00
|
|
|
compileUrlMethod(urlMethod, generator.urlMethodName());
|
2022-05-12 12:39:38 +00:00
|
|
|
m_urlMethodName = urlMethod.name;
|
2022-03-10 08:37:01 +00:00
|
|
|
|
2022-06-03 11:52:47 +00:00
|
|
|
QQmlJSScope::ConstPtr root = pureTypes.at(0);
|
2022-03-10 08:37:01 +00:00
|
|
|
|
|
|
|
QList<QmltcType> compiledTypes;
|
|
|
|
if (isComponent(root)) {
|
|
|
|
compiledTypes.reserve(1);
|
|
|
|
compiledTypes.emplaceBack(); // create empty type
|
2022-04-26 08:42:16 +00:00
|
|
|
const auto compile = [&](QmltcType ¤t, const QQmlJSScope::ConstPtr &type) {
|
|
|
|
generator.generate_initCodeForTopLevelComponent(current, type);
|
2022-03-10 08:37:01 +00:00
|
|
|
};
|
2021-11-19 12:04:05 +00:00
|
|
|
compileType(compiledTypes.back(), root, compile);
|
2022-03-10 08:37:01 +00:00
|
|
|
} else {
|
2021-11-19 12:04:05 +00:00
|
|
|
const auto compile = [this](QmltcType ¤t, const QQmlJSScope::ConstPtr &type) {
|
|
|
|
compileTypeElements(current, type);
|
2022-03-10 08:37:01 +00:00
|
|
|
};
|
2021-11-12 08:28:33 +00:00
|
|
|
|
2022-06-03 11:52:47 +00:00
|
|
|
compiledTypes.reserve(pureTypes.size());
|
|
|
|
for (const auto &type : pureTypes) {
|
|
|
|
Q_ASSERT(type->scopeType() == QQmlJSScope::QMLScope);
|
2022-03-10 08:37:01 +00:00
|
|
|
compiledTypes.emplaceBack(); // create empty type
|
2022-06-03 11:52:47 +00:00
|
|
|
compileType(compiledTypes.back(), type, compile);
|
2022-03-10 08:37:01 +00:00
|
|
|
}
|
2021-09-21 08:52:24 +00:00
|
|
|
}
|
2022-03-10 08:37:01 +00:00
|
|
|
if (hasErrors())
|
|
|
|
return;
|
2021-09-17 15:01:56 +00:00
|
|
|
|
|
|
|
QmltcProgram program;
|
|
|
|
program.url = m_url;
|
|
|
|
program.cppPath = m_info.outputCppFile;
|
|
|
|
program.hPath = m_info.outputHFile;
|
2021-10-20 09:14:52 +00:00
|
|
|
program.outNamespace = m_info.outputNamespace;
|
2021-09-21 08:52:24 +00:00
|
|
|
program.compiledTypes = compiledTypes;
|
2022-03-10 08:37:01 +00:00
|
|
|
program.includes = m_visitor->cppIncludeFiles() | cppIncludesFromPrototype;
|
2021-11-12 08:28:33 +00:00
|
|
|
program.urlMethod = urlMethod;
|
2021-09-17 15:01:56 +00:00
|
|
|
|
|
|
|
QmltcOutput out;
|
|
|
|
QmltcOutputWrapper code(out);
|
|
|
|
QmltcCodeWriter::write(code, program);
|
2021-09-16 07:35:33 +00:00
|
|
|
}
|
|
|
|
|
2022-04-26 08:42:16 +00:00
|
|
|
void QmltcCompiler::compileUrlMethod(QmltcMethod &urlMethod, const QString &urlMethodName)
|
2021-11-12 08:28:33 +00:00
|
|
|
{
|
2022-04-26 08:42:16 +00:00
|
|
|
urlMethod.name = urlMethodName;
|
2022-03-21 09:21:18 +00:00
|
|
|
urlMethod.returnType = u"const QUrl&"_s;
|
|
|
|
urlMethod.body << u"static QUrl url {QStringLiteral(\"qrc:%1\")};"_s.arg(m_info.resourcePath);
|
|
|
|
urlMethod.body << u"return url;"_s;
|
|
|
|
urlMethod.declarationPrefixes << u"static"_s;
|
|
|
|
urlMethod.modifiers << u"noexcept"_s;
|
2021-11-12 08:28:33 +00:00
|
|
|
}
|
|
|
|
|
2021-11-19 12:04:05 +00:00
|
|
|
void QmltcCompiler::compileType(
|
|
|
|
QmltcType ¤t, const QQmlJSScope::ConstPtr &type,
|
|
|
|
std::function<void(QmltcType &, const QQmlJSScope::ConstPtr &)> compileElements)
|
2021-09-21 08:52:24 +00:00
|
|
|
{
|
|
|
|
if (type->isSingleton()) {
|
2022-03-21 09:21:18 +00:00
|
|
|
recordError(type->sourceLocation(), u"Singleton types are not supported"_s);
|
2021-09-21 08:52:24 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-10-28 12:18:04 +00:00
|
|
|
Q_ASSERT(!type->internalName().isEmpty());
|
2021-09-21 08:52:24 +00:00
|
|
|
current.cppType = type->internalName();
|
2021-10-28 12:18:04 +00:00
|
|
|
Q_ASSERT(!type->baseType()->internalName().isEmpty());
|
2021-09-21 08:52:24 +00:00
|
|
|
const QString baseClass = type->baseType()->internalName();
|
|
|
|
|
2021-10-22 10:02:37 +00:00
|
|
|
const auto rootType = m_visitor->result();
|
|
|
|
const bool documentRoot = (type == rootType);
|
2021-11-19 12:04:05 +00:00
|
|
|
const bool isAnonymous = !documentRoot || type->internalName().at(0).isLower();
|
|
|
|
|
2022-04-26 08:42:16 +00:00
|
|
|
QmltcCodeGenerator generator { m_url, m_visitor };
|
2021-09-21 08:52:24 +00:00
|
|
|
|
|
|
|
current.baseClasses = { baseClass };
|
|
|
|
if (!documentRoot) {
|
2022-03-03 13:03:58 +00:00
|
|
|
// make document root a friend to allow it to access init and endInit
|
2022-03-21 09:21:18 +00:00
|
|
|
current.otherCode << u"friend class %1;"_s.arg(rootType->internalName());
|
2021-11-19 12:04:05 +00:00
|
|
|
|
|
|
|
// additionally make an immediate parent a friend since that parent
|
|
|
|
// would create the object through a non-public constructor
|
|
|
|
const auto realQmlScope = [](const QQmlJSScope::ConstPtr &scope) {
|
|
|
|
if (scope->isArrayScope())
|
|
|
|
return scope->parentScope();
|
|
|
|
return scope;
|
|
|
|
};
|
2022-03-21 09:21:18 +00:00
|
|
|
current.otherCode << u"friend class %1;"_s.arg(
|
2021-11-19 12:04:05 +00:00
|
|
|
realQmlScope(type->parentScope())->internalName());
|
2021-09-21 08:52:24 +00:00
|
|
|
} else {
|
2021-10-22 10:02:37 +00:00
|
|
|
// make QQmltcObjectCreationBase<DocumentRoot> a friend to allow it to
|
|
|
|
// be created for the root object
|
2022-03-21 09:21:18 +00:00
|
|
|
current.otherCode << u"friend class QQmltcObjectCreationBase<%1>;"_s.arg(
|
2021-10-22 10:02:37 +00:00
|
|
|
rootType->internalName());
|
|
|
|
|
2021-11-19 12:04:05 +00:00
|
|
|
QmltcMethod typeCountMethod;
|
|
|
|
typeCountMethod.name = QmltcCodeGenerator::typeCountName;
|
2022-03-21 09:21:18 +00:00
|
|
|
typeCountMethod.returnType = u"uint"_s;
|
2021-11-19 12:04:05 +00:00
|
|
|
typeCountMethod.body << u"return " + generator.generate_typeCount() + u";";
|
|
|
|
current.typeCount = typeCountMethod;
|
2021-09-21 08:52:24 +00:00
|
|
|
}
|
2022-04-28 09:03:09 +00:00
|
|
|
|
2021-11-19 12:04:05 +00:00
|
|
|
// make QQmltcObjectCreationHelper a friend of every type since it provides
|
|
|
|
// useful helper methods for all types
|
2022-03-21 09:21:18 +00:00
|
|
|
current.otherCode << u"friend class QT_PREPEND_NAMESPACE(QQmltcObjectCreationHelper);"_s;
|
2021-09-21 08:52:24 +00:00
|
|
|
|
2021-10-28 12:18:04 +00:00
|
|
|
current.mocCode = {
|
2022-03-21 09:21:18 +00:00
|
|
|
u"Q_OBJECT"_s,
|
2021-10-28 12:18:04 +00:00
|
|
|
// Note: isAnonymous holds for non-root types in the document as well
|
2022-03-21 09:21:18 +00:00
|
|
|
isAnonymous ? u"QML_ANONYMOUS"_s : u"QML_ELEMENT"_s,
|
2021-10-28 12:18:04 +00:00
|
|
|
};
|
2021-09-21 08:52:24 +00:00
|
|
|
|
2021-10-22 10:02:37 +00:00
|
|
|
// add special member functions
|
2022-03-03 13:03:58 +00:00
|
|
|
current.baselineCtor.access = QQmlJSMetaMethod::Protected;
|
2021-11-19 12:04:05 +00:00
|
|
|
if (documentRoot) {
|
|
|
|
current.externalCtor.access = QQmlJSMetaMethod::Public;
|
|
|
|
} else {
|
|
|
|
current.externalCtor.access = QQmlJSMetaMethod::Protected;
|
|
|
|
}
|
2021-10-22 10:02:37 +00:00
|
|
|
current.init.access = QQmlJSMetaMethod::Protected;
|
2021-11-19 12:04:05 +00:00
|
|
|
current.beginClass.access = QQmlJSMetaMethod::Protected;
|
2022-03-03 13:03:58 +00:00
|
|
|
current.endInit.access = QQmlJSMetaMethod::Protected;
|
2021-11-19 12:04:05 +00:00
|
|
|
current.completeComponent.access = QQmlJSMetaMethod::Protected;
|
|
|
|
current.finalizeComponent.access = QQmlJSMetaMethod::Protected;
|
|
|
|
current.handleOnCompleted.access = QQmlJSMetaMethod::Protected;
|
2021-10-22 10:02:37 +00:00
|
|
|
|
2022-03-03 13:03:58 +00:00
|
|
|
current.baselineCtor.name = current.cppType;
|
|
|
|
current.externalCtor.name = current.cppType;
|
2022-03-21 09:21:18 +00:00
|
|
|
current.init.name = u"QML_init"_s;
|
|
|
|
current.init.returnType = u"QQmlRefPointer<QQmlContextData>"_s;
|
|
|
|
current.beginClass.name = u"QML_beginClass"_s;
|
|
|
|
current.beginClass.returnType = u"void"_s;
|
|
|
|
current.endInit.name = u"QML_endInit"_s;
|
|
|
|
current.endInit.returnType = u"void"_s;
|
|
|
|
current.completeComponent.name = u"QML_completeComponent"_s;
|
|
|
|
current.completeComponent.returnType = u"void"_s;
|
|
|
|
current.finalizeComponent.name = u"QML_finalizeComponent"_s;
|
|
|
|
current.finalizeComponent.returnType = u"void"_s;
|
|
|
|
current.handleOnCompleted.name = u"QML_handleOnCompleted"_s;
|
|
|
|
current.handleOnCompleted.returnType = u"void"_s;
|
|
|
|
QmltcVariable creator(u"QQmltcObjectCreationHelper*"_s, u"creator"_s);
|
|
|
|
QmltcVariable engine(u"QQmlEngine*"_s, u"engine"_s);
|
|
|
|
QmltcVariable parent(u"QObject*"_s, u"parent"_s, u"nullptr"_s);
|
|
|
|
QmltcVariable ctxtdata(u"const QQmlRefPointer<QQmlContextData>&"_s, u"parentContext"_s);
|
|
|
|
QmltcVariable finalizeFlag(u"bool"_s, u"canFinalize"_s);
|
2021-11-19 12:04:05 +00:00
|
|
|
current.baselineCtor.parameterList = { parent };
|
2021-09-21 08:52:24 +00:00
|
|
|
if (documentRoot) {
|
2022-03-03 13:03:58 +00:00
|
|
|
current.externalCtor.parameterList = { engine, parent };
|
2021-10-22 10:02:37 +00:00
|
|
|
current.init.parameterList = { creator, engine, ctxtdata, finalizeFlag };
|
2021-11-19 12:04:05 +00:00
|
|
|
current.beginClass.parameterList = { creator, finalizeFlag };
|
2022-03-03 13:03:58 +00:00
|
|
|
current.endInit.parameterList = { creator, engine, finalizeFlag };
|
2021-11-19 12:04:05 +00:00
|
|
|
current.completeComponent.parameterList = { creator, finalizeFlag };
|
|
|
|
current.finalizeComponent.parameterList = { creator, finalizeFlag };
|
|
|
|
current.handleOnCompleted.parameterList = { creator, finalizeFlag };
|
2021-09-21 08:52:24 +00:00
|
|
|
} else {
|
2022-03-03 13:03:58 +00:00
|
|
|
current.externalCtor.parameterList = { creator, engine, parent };
|
2021-10-22 10:02:37 +00:00
|
|
|
current.init.parameterList = { creator, engine, ctxtdata };
|
2021-11-19 12:04:05 +00:00
|
|
|
current.beginClass.parameterList = { creator };
|
2022-03-03 13:03:58 +00:00
|
|
|
current.endInit.parameterList = { creator, engine };
|
2021-11-19 12:04:05 +00:00
|
|
|
current.completeComponent.parameterList = { creator };
|
|
|
|
current.finalizeComponent.parameterList = { creator };
|
|
|
|
current.handleOnCompleted.parameterList = { creator };
|
2021-09-21 08:52:24 +00:00
|
|
|
}
|
|
|
|
|
2022-03-03 13:03:58 +00:00
|
|
|
current.externalCtor.initializerList = { current.baselineCtor.name + u"(" + parent.name
|
|
|
|
+ u")" };
|
2022-04-28 09:07:30 +00:00
|
|
|
if (QQmlJSUtils::hasCompositeBase(type)) {
|
2021-09-21 08:52:24 +00:00
|
|
|
// call parent's (QML type's) basic ctor from this. that one will take
|
|
|
|
// care about QObject::setParent()
|
2022-03-03 13:03:58 +00:00
|
|
|
current.baselineCtor.initializerList = { baseClass + u"(" + parent.name + u")" };
|
2021-09-21 08:52:24 +00:00
|
|
|
} else {
|
|
|
|
// default call to ctor is enough, but QQml_setParent_noEvent() is
|
|
|
|
// needed (note: faster? version of QObject::setParent())
|
2022-03-03 13:03:58 +00:00
|
|
|
current.baselineCtor.body << u"QQml_setParent_noEvent(this, " + parent.name + u");";
|
2021-09-21 08:52:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// compilation stub:
|
2022-03-21 09:21:18 +00:00
|
|
|
current.externalCtor.body << u"Q_UNUSED(engine);"_s;
|
|
|
|
current.endInit.body << u"Q_UNUSED(engine);"_s;
|
|
|
|
current.endInit.body << u"Q_UNUSED(creator);"_s;
|
2021-09-21 08:52:24 +00:00
|
|
|
if (documentRoot) {
|
2022-03-21 09:21:18 +00:00
|
|
|
current.externalCtor.body << u"// document root:"_s;
|
2021-10-22 10:02:37 +00:00
|
|
|
// if it's document root, we want to create our QQmltcObjectCreationBase
|
|
|
|
// that would store all the created objects
|
2022-03-21 09:21:18 +00:00
|
|
|
current.externalCtor.body << u"QQmltcObjectCreationBase<%1> objectHolder;"_s.arg(
|
2021-10-22 10:02:37 +00:00
|
|
|
type->internalName());
|
2022-03-03 13:03:58 +00:00
|
|
|
current.externalCtor.body
|
2022-03-21 09:21:18 +00:00
|
|
|
<< u"QQmltcObjectCreationHelper creator = objectHolder.view();"_s;
|
|
|
|
current.externalCtor.body << u"creator.set(0, this);"_s; // special case
|
2021-10-22 10:02:37 +00:00
|
|
|
// now call init
|
2022-03-03 13:03:58 +00:00
|
|
|
current.externalCtor.body << current.init.name
|
2021-10-22 10:02:37 +00:00
|
|
|
+ u"(&creator, engine, QQmlContextData::get(engine->rootContext()), /* "
|
2022-03-03 13:03:58 +00:00
|
|
|
u"endInit */ true);";
|
2021-10-22 10:02:37 +00:00
|
|
|
|
2022-03-21 09:21:18 +00:00
|
|
|
current.endInit.body << u"Q_UNUSED(canFinalize);"_s;
|
2021-10-22 10:02:37 +00:00
|
|
|
} else {
|
2022-03-21 09:21:18 +00:00
|
|
|
current.externalCtor.body << u"// not document root:"_s;
|
2021-10-22 10:02:37 +00:00
|
|
|
// just call init, we don't do any setup here otherwise
|
2022-03-03 13:03:58 +00:00
|
|
|
current.externalCtor.body << current.init.name
|
2021-10-22 10:02:37 +00:00
|
|
|
+ u"(creator, engine, QQmlData::get(parent)->outerContext);";
|
2021-09-21 08:52:24 +00:00
|
|
|
}
|
2021-11-12 08:28:33 +00:00
|
|
|
|
2021-11-19 12:04:05 +00:00
|
|
|
auto postponedQmlContextSetup = generator.generate_initCode(current, type);
|
|
|
|
auto postponedFinalizeCode = generator.generate_endInitCode(current, type);
|
|
|
|
generator.generate_beginClassCode(current, type);
|
|
|
|
generator.generate_completeComponentCode(current, type);
|
|
|
|
generator.generate_finalizeComponentCode(current, type);
|
|
|
|
generator.generate_handleOnCompletedCode(current, type);
|
|
|
|
|
|
|
|
compileElements(current, type);
|
|
|
|
}
|
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
template<typename Iterator>
|
|
|
|
static Iterator partitionBindings(Iterator first, Iterator last)
|
2021-11-19 12:04:05 +00:00
|
|
|
{
|
2022-05-12 12:39:38 +00:00
|
|
|
// NB: the code generator cares about script bindings being processed at a
|
|
|
|
// later point, so we should sort or partition the range. we do a stable
|
|
|
|
// partition since the relative order of binding evaluation affects the UI
|
|
|
|
return std::stable_partition(first, last, [](const QQmlJSMetaPropertyBinding &b) {
|
|
|
|
// we want script bindings to be at the end, so do the negation of "is
|
|
|
|
// script binding"
|
|
|
|
return b.bindingType() != QQmlJSMetaPropertyBinding::Script;
|
|
|
|
});
|
|
|
|
}
|
2021-11-19 12:04:05 +00:00
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
void QmltcCompiler::compileTypeElements(QmltcType ¤t, const QQmlJSScope::ConstPtr &type)
|
|
|
|
{
|
2021-10-29 10:43:44 +00:00
|
|
|
// compile components of a type:
|
|
|
|
// - enums
|
|
|
|
// - properties
|
|
|
|
// - methods
|
|
|
|
// - bindings
|
|
|
|
|
|
|
|
const auto enums = type->ownEnumerations();
|
|
|
|
current.enums.reserve(enums.size());
|
|
|
|
for (auto it = enums.begin(); it != enums.end(); ++it)
|
|
|
|
compileEnum(current, it.value());
|
2021-11-01 15:18:30 +00:00
|
|
|
|
2021-10-29 09:56:25 +00:00
|
|
|
auto properties = type->ownProperties().values();
|
2021-11-19 12:04:05 +00:00
|
|
|
current.properties.reserve(properties.size());
|
2021-10-29 09:56:25 +00:00
|
|
|
// Note: index() is the (future) meta property index, so make sure given
|
|
|
|
// properties are ordered by that index before compiling
|
|
|
|
std::sort(properties.begin(), properties.end(),
|
|
|
|
[](const QQmlJSMetaProperty &x, const QQmlJSMetaProperty &y) {
|
|
|
|
return x.index() < y.index();
|
|
|
|
});
|
|
|
|
for (const QQmlJSMetaProperty &p : qAsConst(properties)) {
|
|
|
|
if (p.index() == -1) {
|
|
|
|
recordError(type->sourceLocation(),
|
2022-03-21 09:21:18 +00:00
|
|
|
u"Internal error: property '%1' has incomplete information"_s.arg(
|
2021-10-29 09:56:25 +00:00
|
|
|
p.propertyName()));
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (p.isAlias()) {
|
2022-04-22 10:51:41 +00:00
|
|
|
compileAlias(current, p, type);
|
2021-10-29 09:56:25 +00:00
|
|
|
} else {
|
|
|
|
compileProperty(current, p, type);
|
|
|
|
}
|
|
|
|
}
|
2021-11-15 16:10:31 +00:00
|
|
|
|
2021-11-19 12:04:05 +00:00
|
|
|
const auto methods = type->ownMethods();
|
2022-04-19 11:35:36 +00:00
|
|
|
for (const QQmlJSMetaMethod &m : methods)
|
|
|
|
compileMethod(current, m, type);
|
2021-11-19 12:04:05 +00:00
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
auto bindings = type->ownPropertyBindingsInQmlIROrder();
|
|
|
|
partitionBindings(bindings.begin(), bindings.end());
|
2021-11-19 12:04:05 +00:00
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
for (auto it = bindings.begin(); it != bindings.end(); ++it)
|
|
|
|
compileBinding(current, *it, type, { type });
|
2021-10-29 10:43:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void QmltcCompiler::compileEnum(QmltcType ¤t, 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),
|
2022-03-21 09:21:18 +00:00
|
|
|
u"Q_ENUM(%1)"_s.arg(e.name()));
|
2021-09-21 08:52:24 +00:00
|
|
|
}
|
|
|
|
|
2021-11-01 15:18:30 +00:00
|
|
|
static QList<QmltcVariable>
|
|
|
|
compileMethodParameters(const QStringList &names,
|
|
|
|
const QList<QSharedPointer<const QQmlJSScope>> &types,
|
|
|
|
bool allowUnnamed = false)
|
|
|
|
{
|
2022-05-12 12:39:38 +00:00
|
|
|
Q_ASSERT(names.size() == types.size());
|
|
|
|
|
2021-11-01 15:18:30 +00:00
|
|
|
QList<QmltcVariable> parameters;
|
|
|
|
const auto size = names.size();
|
|
|
|
parameters.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);
|
2021-12-17 14:52:17 +00:00
|
|
|
parameters.emplaceBack(types[i]->augmentedInternalName(), name, QString());
|
2021-11-01 15:18:30 +00:00
|
|
|
}
|
|
|
|
return parameters;
|
|
|
|
}
|
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
static QString figureReturnType(const QQmlJSMetaMethod &m)
|
|
|
|
{
|
|
|
|
const bool isVoidMethod =
|
|
|
|
m.returnTypeName() == u"void" || m.methodType() == QQmlJSMetaMethod::Signal;
|
|
|
|
Q_ASSERT(isVoidMethod || m.returnType());
|
|
|
|
QString type;
|
|
|
|
if (isVoidMethod) {
|
|
|
|
type = u"void"_s;
|
|
|
|
} else {
|
|
|
|
type = m.returnType()->augmentedInternalName();
|
|
|
|
}
|
|
|
|
return type;
|
|
|
|
}
|
|
|
|
|
2022-04-19 11:35:36 +00:00
|
|
|
void QmltcCompiler::compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m,
|
|
|
|
const QQmlJSScope::ConstPtr &owner)
|
2021-11-01 15:18:30 +00:00
|
|
|
{
|
|
|
|
const auto returnType = figureReturnType(m);
|
|
|
|
const auto paramNames = m.parameterNames();
|
|
|
|
const auto paramTypes = m.parameterTypes();
|
|
|
|
Q_ASSERT(paramNames.size() == paramTypes.size()); // assume verified
|
|
|
|
const QList<QmltcVariable> compiledParams = compileMethodParameters(paramNames, paramTypes);
|
|
|
|
const auto methodType = QQmlJSMetaMethod::Type(m.methodType());
|
|
|
|
|
|
|
|
QStringList code;
|
|
|
|
if (methodType != QQmlJSMetaMethod::Signal) {
|
2022-04-19 11:35:36 +00:00
|
|
|
QmltcCodeGenerator urlGenerator { m_url, m_visitor };
|
|
|
|
QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
|
2022-05-12 12:39:38 +00:00
|
|
|
&code, urlGenerator.urlMethodName() + u"()",
|
|
|
|
owner->ownRuntimeFunctionIndex(m.jsFunctionIndex()), u"this"_s, returnType,
|
2022-04-19 11:35:36 +00:00
|
|
|
compiledParams);
|
2021-11-01 15:18:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QmltcMethod compiled {};
|
|
|
|
compiled.returnType = returnType;
|
|
|
|
compiled.name = m.methodName();
|
|
|
|
compiled.parameterList = std::move(compiledParams);
|
|
|
|
compiled.body = std::move(code);
|
|
|
|
compiled.type = methodType;
|
|
|
|
compiled.access = m.access();
|
2022-04-19 11:35:36 +00:00
|
|
|
if (methodType != QQmlJSMetaMethod::Signal) {
|
2022-03-21 09:21:18 +00:00
|
|
|
compiled.declarationPrefixes << u"Q_INVOKABLE"_s;
|
2022-04-19 11:35:36 +00:00
|
|
|
compiled.userVisible = m.access() == QQmlJSMetaMethod::Public;
|
|
|
|
} else {
|
|
|
|
compiled.userVisible = !m.isImplicitQmlPropertyChangeSignal();
|
|
|
|
}
|
2021-11-01 15:18:30 +00:00
|
|
|
current.functions.emplaceBack(compiled);
|
|
|
|
}
|
|
|
|
|
2021-10-29 09:56:25 +00:00
|
|
|
void QmltcCompiler::compileProperty(QmltcType ¤t, 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());
|
2022-03-03 13:03:58 +00:00
|
|
|
current.baselineCtor.initializerList.emplaceBack(variableName + u"(" + underlyingType
|
|
|
|
+ u"(this, std::addressof(" + storageName
|
|
|
|
+ u")))");
|
2021-10-29 09:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2021-11-19 12:04:05 +00:00
|
|
|
QmltcPropertyData compilationData(p);
|
|
|
|
|
2021-10-29 09:56:25 +00:00
|
|
|
// 1. add setter and getter
|
2021-11-19 12:04:05 +00:00
|
|
|
// If p.isList(), it's a QQmlListProperty. Then you can write the underlying list through
|
|
|
|
// the QQmlListProperty object retrieved with the getter. Setting it would make no sense.
|
|
|
|
if (p.isWritable() && !p.isList()) {
|
2021-10-29 09:56:25 +00:00
|
|
|
QmltcMethod setter {};
|
2022-03-21 09:21:18 +00:00
|
|
|
setter.returnType = u"void"_s;
|
2021-11-19 12:04:05 +00:00
|
|
|
setter.name = compilationData.write;
|
|
|
|
// QmltcVariable
|
2021-12-17 14:52:17 +00:00
|
|
|
setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(underlyingType), name + u"_",
|
2022-03-21 09:21:18 +00:00
|
|
|
u""_s);
|
2021-10-29 09:56:25 +00:00
|
|
|
setter.body << variableName + u".setValue(" + name + u"_);";
|
2021-11-19 12:04:05 +00:00
|
|
|
setter.body << u"Q_EMIT " + compilationData.notify + u"();";
|
|
|
|
setter.userVisible = true;
|
2021-10-29 09:56:25 +00:00
|
|
|
current.functions.emplaceBack(setter);
|
2022-03-21 09:21:18 +00:00
|
|
|
mocPieces << u"WRITE"_s << setter.name;
|
2021-10-29 09:56:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QmltcMethod getter {};
|
|
|
|
getter.returnType = underlyingType;
|
2021-11-19 12:04:05 +00:00
|
|
|
getter.name = compilationData.read;
|
2021-10-29 09:56:25 +00:00
|
|
|
getter.body << u"return " + variableName + u".value();";
|
2021-11-19 12:04:05 +00:00
|
|
|
getter.userVisible = true;
|
2021-10-29 09:56:25 +00:00
|
|
|
current.functions.emplaceBack(getter);
|
2022-03-21 09:21:18 +00:00
|
|
|
mocPieces << u"READ"_s << getter.name;
|
2021-10-29 09:56:25 +00:00
|
|
|
|
|
|
|
// 2. add bindable
|
2021-11-19 12:04:05 +00:00
|
|
|
if (!p.isList()) {
|
|
|
|
QmltcMethod bindable {};
|
|
|
|
bindable.returnType = u"QBindable<" + underlyingType + u">";
|
|
|
|
bindable.name = compilationData.bindable;
|
|
|
|
bindable.body << u"return QBindable<" + underlyingType + u">(std::addressof(" + variableName
|
|
|
|
+ u"));";
|
|
|
|
bindable.userVisible = true;
|
|
|
|
current.functions.emplaceBack(bindable);
|
2022-03-21 09:21:18 +00:00
|
|
|
mocPieces << u"BINDABLE"_s << bindable.name;
|
2021-11-19 12:04:05 +00:00
|
|
|
}
|
2021-10-29 09:56:25 +00:00
|
|
|
|
|
|
|
// 3. add/check notify (actually, this is already done inside QmltcVisitor)
|
|
|
|
|
|
|
|
if (owner->isPropertyRequired(name))
|
2022-03-21 09:21:18 +00:00
|
|
|
mocPieces << u"REQUIRED"_s;
|
2021-10-29 09:56:25 +00:00
|
|
|
|
|
|
|
// 4. add moc entry
|
|
|
|
// e.g. Q_PROPERTY(QString p READ getP WRITE setP BINDABLE bindableP)
|
2022-03-21 09:21:18 +00:00
|
|
|
current.mocCode << u"Q_PROPERTY(" + mocPieces.join(u" "_s) + u")";
|
2021-10-29 09:56:25 +00:00
|
|
|
|
|
|
|
// 5. add extra moc entry if this property is marked default
|
|
|
|
if (name == owner->defaultPropertyName())
|
2022-03-21 09:21:18 +00:00
|
|
|
current.mocCode << u"Q_CLASSINFO(\"DefaultProperty\", \"%1\")"_s.arg(name);
|
2021-10-29 09:56:25 +00:00
|
|
|
|
|
|
|
// structure: (C++ type name, name, C++ class name, C++ signal name)
|
2021-11-19 12:04:05 +00:00
|
|
|
current.properties.emplaceBack(underlyingType, variableName, current.cppType,
|
|
|
|
compilationData.notify);
|
2021-10-29 09:56:25 +00:00
|
|
|
}
|
|
|
|
|
2022-04-22 10:51:41 +00:00
|
|
|
struct AliasResolutionFrame
|
|
|
|
{
|
|
|
|
static QString inVar;
|
|
|
|
QStringList prologue;
|
2022-04-28 09:03:09 +00:00
|
|
|
QStringList epilogue;
|
2022-04-22 10:51:41 +00:00
|
|
|
QStringList epilogueForWrite;
|
|
|
|
QString outVar;
|
|
|
|
};
|
|
|
|
// special string replaced by outVar of the previous frame
|
|
|
|
QString AliasResolutionFrame::inVar = QStringLiteral("__QMLTC_ALIAS_FRAME_INPUT_VAR__");
|
|
|
|
|
|
|
|
static void unpackFrames(QStack<AliasResolutionFrame> &frames)
|
|
|
|
{
|
|
|
|
if (frames.size() < 2)
|
|
|
|
return;
|
|
|
|
|
|
|
|
// assume first frame is fine
|
|
|
|
auto prev = frames.begin();
|
|
|
|
for (auto it = std::next(prev); it != frames.end(); ++it, ++prev) {
|
|
|
|
for (QString &line : it->prologue)
|
|
|
|
line.replace(AliasResolutionFrame::inVar, prev->outVar);
|
|
|
|
for (QString &line : it->epilogueForWrite)
|
|
|
|
line.replace(AliasResolutionFrame::inVar, prev->outVar);
|
|
|
|
it->outVar.replace(AliasResolutionFrame::inVar, prev->outVar);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
template<typename Projection>
|
|
|
|
static QStringList joinFrames(const QStack<AliasResolutionFrame> &frames, Projection project)
|
|
|
|
{
|
|
|
|
QStringList joined;
|
|
|
|
for (const AliasResolutionFrame &frame : frames)
|
|
|
|
joined += project(frame);
|
|
|
|
return joined;
|
|
|
|
}
|
|
|
|
|
|
|
|
void QmltcCompiler::compileAlias(QmltcType ¤t, 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());
|
|
|
|
|
|
|
|
QStack<AliasResolutionFrame> frames;
|
|
|
|
|
|
|
|
QQmlJSUtils::AliasResolutionVisitor aliasVisitor;
|
|
|
|
qsizetype i = 0;
|
|
|
|
aliasVisitor.reset = [&]() {
|
|
|
|
frames.clear();
|
|
|
|
i = 0; // we use it in property processing
|
|
|
|
|
|
|
|
// first frame is a dummy one:
|
2022-04-28 09:03:09 +00:00
|
|
|
frames.push(
|
|
|
|
AliasResolutionFrame { QStringList(), QStringList(), QStringList(), u"this"_s });
|
2022-04-22 10:51:41 +00:00
|
|
|
};
|
|
|
|
aliasVisitor.processResolvedId = [&](const QQmlJSScope::ConstPtr &type) {
|
|
|
|
Q_ASSERT(type);
|
|
|
|
if (owner != type) { // cannot start at `this`, need to fetch object through context
|
|
|
|
const int id = m_visitor->runtimeId(type);
|
|
|
|
Q_ASSERT(id >= 0); // since the type is found by id, it must have an id
|
|
|
|
|
|
|
|
AliasResolutionFrame queryIdFrame {};
|
2022-06-08 10:33:14 +00:00
|
|
|
Q_ASSERT(frames.top().outVar == u"this"_s); // so inVar would be "this" as well
|
|
|
|
queryIdFrame.prologue << u"auto context = %1::q_qmltc_thisContext;"_s.arg(
|
|
|
|
owner->internalName());
|
2022-04-22 10:51:41 +00:00
|
|
|
|
|
|
|
// doing the above allows us to lookup id object by index (fast)
|
|
|
|
queryIdFrame.outVar = u"alias_objectById_" + aliasExprBits.front(); // unique enough
|
|
|
|
queryIdFrame.prologue << u"auto " + queryIdFrame.outVar + u" = static_cast<"
|
|
|
|
+ type->internalName() + u"*>(context->idValue(" + QString::number(id)
|
|
|
|
+ u"));";
|
|
|
|
queryIdFrame.prologue << u"Q_ASSERT(" + queryIdFrame.outVar + u");";
|
|
|
|
|
|
|
|
frames.push(queryIdFrame);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
aliasVisitor.processResolvedProperty = [&](const QQmlJSMetaProperty &p,
|
2022-04-28 09:03:09 +00:00
|
|
|
const QQmlJSScope::ConstPtr &owner) {
|
2022-04-22 10:51:41 +00:00
|
|
|
AliasResolutionFrame queryPropertyFrame {};
|
|
|
|
|
2022-04-28 09:03:09 +00:00
|
|
|
auto [extensionPrologue, extensionAccessor, extensionEpilogue] =
|
|
|
|
QmltcCodeGenerator::wrap_extensionType(
|
|
|
|
owner, p,
|
|
|
|
QmltcCodeGenerator::wrap_privateClass(AliasResolutionFrame::inVar, p));
|
|
|
|
QString inVar = extensionAccessor;
|
|
|
|
queryPropertyFrame.prologue += extensionPrologue;
|
2022-04-22 10:51:41 +00:00
|
|
|
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
|
|
|
|
const QString aliasVar = u"alias_" + QString::number(i); // should be fairly unique
|
|
|
|
++i;
|
|
|
|
queryPropertyFrame.prologue
|
|
|
|
<< u"auto " + aliasVar + u" = " + inVar + u"->" + p.read() + u"();";
|
|
|
|
queryPropertyFrame.epilogueForWrite
|
|
|
|
<< inVar + u"->" + p.write() + u"(" + aliasVar + u");";
|
|
|
|
// NB: since accessor becomes a value type, wrap it into an
|
|
|
|
// addressof operator so that we could access it as a pointer
|
|
|
|
inVar = QmltcCodeGenerator::wrap_addressof(aliasVar); // reset
|
|
|
|
} else {
|
|
|
|
inVar += u"->" + p.read() + u"()"; // update
|
|
|
|
}
|
|
|
|
queryPropertyFrame.outVar = inVar;
|
2022-04-28 09:03:09 +00:00
|
|
|
queryPropertyFrame.epilogue += extensionEpilogue;
|
2022-04-22 10:51:41 +00:00
|
|
|
|
|
|
|
frames.push(queryPropertyFrame);
|
|
|
|
};
|
|
|
|
|
|
|
|
QQmlJSUtils::ResolvedAlias result =
|
|
|
|
QQmlJSUtils::resolveAlias(m_typeResolver, alias, owner, aliasVisitor);
|
|
|
|
Q_ASSERT(result.kind != QQmlJSUtils::AliasTarget_Invalid);
|
|
|
|
|
|
|
|
unpackFrames(frames);
|
|
|
|
|
|
|
|
if (result.kind == QQmlJSUtils::AliasTarget_Property) {
|
|
|
|
// we don't need the last frame here
|
|
|
|
frames.pop();
|
|
|
|
|
|
|
|
// instead, add a custom frame
|
|
|
|
AliasResolutionFrame customFinalFrame {};
|
2022-04-28 09:03:09 +00:00
|
|
|
auto [extensionPrologue, extensionAccessor, extensionEpilogue] =
|
|
|
|
QmltcCodeGenerator::wrap_extensionType(
|
|
|
|
result.owner, result.property,
|
|
|
|
QmltcCodeGenerator::wrap_privateClass(frames.top().outVar,
|
|
|
|
result.property));
|
|
|
|
customFinalFrame.prologue = extensionPrologue;
|
|
|
|
customFinalFrame.outVar = extensionAccessor;
|
|
|
|
customFinalFrame.epilogue = extensionEpilogue;
|
2022-04-22 10:51:41 +00:00
|
|
|
frames.push(customFinalFrame);
|
|
|
|
}
|
|
|
|
|
|
|
|
const QString latestAccessor = frames.top().outVar;
|
|
|
|
const QStringList prologue =
|
|
|
|
joinFrames(frames, [](const AliasResolutionFrame &frame) { return frame.prologue; });
|
2022-04-28 09:03:09 +00:00
|
|
|
const QStringList epilogue =
|
|
|
|
joinFrames(frames, [](const AliasResolutionFrame &frame) { return frame.epilogue; });
|
2022-04-22 10:51:41 +00:00
|
|
|
const QString underlyingType = (result.kind == QQmlJSUtils::AliasTarget_Property)
|
|
|
|
? getUnderlyingType(result.property)
|
|
|
|
: result.owner->internalName() + u" *";
|
|
|
|
|
|
|
|
QStringList mocLines;
|
|
|
|
mocLines.reserve(10);
|
|
|
|
mocLines << underlyingType << aliasName;
|
|
|
|
|
|
|
|
QmltcPropertyData compilationData(aliasName);
|
|
|
|
// 1. add setter and getter
|
|
|
|
QmltcMethod getter {};
|
|
|
|
getter.returnType = underlyingType;
|
|
|
|
getter.name = compilationData.read;
|
|
|
|
getter.body += prologue;
|
|
|
|
if (result.kind == QQmlJSUtils::AliasTarget_Property)
|
|
|
|
getter.body << u"return " + latestAccessor + u"->" + result.property.read() + u"();";
|
|
|
|
else // AliasTarget_Object
|
|
|
|
getter.body << u"return " + latestAccessor + u";";
|
2022-04-28 09:03:09 +00:00
|
|
|
getter.body += epilogue;
|
2022-04-22 10:51:41 +00:00
|
|
|
getter.userVisible = true;
|
|
|
|
current.functions.emplaceBack(getter);
|
|
|
|
mocLines << u"READ"_s << getter.name;
|
|
|
|
|
|
|
|
if (QString setName = result.property.write(); !setName.isEmpty()) {
|
|
|
|
Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
|
|
|
|
QmltcMethod setter {};
|
|
|
|
setter.returnType = u"void"_s;
|
|
|
|
setter.name = compilationData.write;
|
|
|
|
|
|
|
|
QList<QQmlJSMetaMethod> methods = result.owner->methods(setName);
|
|
|
|
if (methods.isEmpty()) { // when we are compiling the property as well
|
|
|
|
// QmltcVariable
|
|
|
|
setter.parameterList.emplaceBack(QQmlJSUtils::constRefify(underlyingType),
|
|
|
|
aliasName + u"_", u""_s);
|
|
|
|
} else {
|
|
|
|
setter.parameterList = compileMethodParameters(methods.at(0).parameterNames(),
|
|
|
|
methods.at(0).parameterTypes(),
|
|
|
|
/* allow unnamed = */ true);
|
|
|
|
}
|
|
|
|
|
|
|
|
setter.body += prologue;
|
|
|
|
QStringList parameterNames;
|
|
|
|
parameterNames.reserve(setter.parameterList.size());
|
|
|
|
std::transform(setter.parameterList.cbegin(), setter.parameterList.cend(),
|
|
|
|
std::back_inserter(parameterNames),
|
|
|
|
[](const QmltcVariable &x) { return x.name; });
|
|
|
|
QString commaSeparatedParameterNames = parameterNames.join(u", "_s);
|
|
|
|
setter.body << latestAccessor + u"->" + setName
|
|
|
|
+ u"(%1)"_s.arg(commaSeparatedParameterNames) + u";";
|
|
|
|
setter.body += joinFrames(
|
|
|
|
frames, [](const AliasResolutionFrame &frame) { return frame.epilogueForWrite; });
|
2022-04-28 09:03:09 +00:00
|
|
|
setter.body += epilogue; // NB: *after* epilogueForWrite - see prologue construction
|
2022-04-22 10:51:41 +00:00
|
|
|
setter.userVisible = true;
|
|
|
|
current.functions.emplaceBack(setter);
|
|
|
|
mocLines << u"WRITE"_s << setter.name;
|
|
|
|
}
|
|
|
|
// 2. add bindable
|
|
|
|
if (QString bindableName = result.property.bindable(); !bindableName.isEmpty()) {
|
|
|
|
Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
|
|
|
|
QmltcMethod bindable {};
|
|
|
|
bindable.returnType = u"QBindable<" + underlyingType + u">";
|
|
|
|
bindable.name = compilationData.bindable;
|
|
|
|
bindable.body += prologue;
|
|
|
|
bindable.body << u"return " + latestAccessor + u"->" + bindableName + u"()" + u";";
|
2022-04-28 09:03:09 +00:00
|
|
|
bindable.body += epilogue;
|
2022-04-22 10:51:41 +00:00
|
|
|
bindable.userVisible = true;
|
|
|
|
current.functions.emplaceBack(bindable);
|
|
|
|
mocLines << u"BINDABLE"_s << bindable.name;
|
|
|
|
}
|
|
|
|
// 3. add notify - which is pretty special
|
|
|
|
if (QString notifyName = result.property.notify(); !notifyName.isEmpty()) {
|
|
|
|
Q_ASSERT(result.kind == QQmlJSUtils::AliasTarget_Property); // property is invalid otherwise
|
|
|
|
|
2022-04-28 09:03:09 +00:00
|
|
|
auto notifyFrames = frames;
|
|
|
|
notifyFrames.pop(); // we don't need the last frame at all in this case
|
|
|
|
|
|
|
|
const QStringList notifyPrologue = joinFrames(
|
|
|
|
frames, [](const AliasResolutionFrame &frame) { return frame.prologue; });
|
|
|
|
const QStringList notifyEpilogue = joinFrames(
|
|
|
|
frames, [](const AliasResolutionFrame &frame) { return frame.epilogue; });
|
|
|
|
|
2022-04-22 10:51:41 +00:00
|
|
|
// notify is very special
|
|
|
|
current.endInit.body << u"{ // alias notify connection:"_s;
|
2022-04-28 09:03:09 +00:00
|
|
|
current.endInit.body += notifyPrologue;
|
2022-04-22 10:51:41 +00:00
|
|
|
// TODO: use non-private accessor since signals must exist on the public
|
|
|
|
// type, not on the private one -- otherwise, you can't connect to a
|
|
|
|
// private property signal in C++ and so it is useless (hence, use
|
|
|
|
// public type)
|
2022-04-28 09:03:09 +00:00
|
|
|
const QString latestAccessorNonPrivate = notifyFrames.top().outVar;
|
2022-04-22 10:51:41 +00:00
|
|
|
current.endInit.body << u"QObject::connect(" + latestAccessorNonPrivate + u", &"
|
|
|
|
+ result.owner->internalName() + u"::" + notifyName + u", this, &"
|
|
|
|
+ current.cppType + u"::" + compilationData.notify + u");";
|
2022-04-28 09:03:09 +00:00
|
|
|
current.endInit.body += notifyEpilogue;
|
2022-04-22 10:51:41 +00:00
|
|
|
current.endInit.body << u"}"_s;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 4. add moc entry
|
|
|
|
// Q_PROPERTY(QString text READ text WRITE setText BINDABLE bindableText NOTIFY textChanged)
|
|
|
|
current.mocCode << u"Q_PROPERTY(" + mocLines.join(u" "_s) + 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\")"_s.arg(aliasName);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
static QString generate_callCompilationUnit(const QString &urlMethodName)
|
|
|
|
{
|
|
|
|
return u"QQmlEnginePrivate::get(engine)->compilationUnitFromUrl(%1())"_s.arg(urlMethodName);
|
|
|
|
}
|
|
|
|
|
2021-11-15 16:10:31 +00:00
|
|
|
void QmltcCompiler::compileBinding(QmltcType ¤t, const QQmlJSMetaPropertyBinding &binding,
|
|
|
|
const QQmlJSScope::ConstPtr &type,
|
|
|
|
const BindingAccessorData &accessor)
|
|
|
|
{
|
|
|
|
QString propertyName = binding.propertyName();
|
2022-05-12 12:39:38 +00:00
|
|
|
Q_ASSERT(!propertyName.isEmpty());
|
|
|
|
|
|
|
|
auto bindingType = binding.bindingType();
|
|
|
|
|
|
|
|
// Note: unlike QQmlObjectCreator, we don't have to do a complicated
|
|
|
|
// deferral logic for bindings: if a binding is deferred, it is not compiled
|
|
|
|
// (potentially, with all the bindings inside of it), period.
|
|
|
|
if (type->isNameDeferred(propertyName)) {
|
|
|
|
const auto location = binding.sourceLocation();
|
|
|
|
if (bindingType == QQmlJSMetaPropertyBinding::GroupProperty) {
|
|
|
|
qCWarning(lcQmltcCompiler)
|
|
|
|
<< QStringLiteral("Binding at line %1 column %2 is not deferred as it is a "
|
|
|
|
"binding on a group property.")
|
|
|
|
.arg(QString::number(location.startLine),
|
|
|
|
QString::number(location.startColumn));
|
|
|
|
// we do not support PropertyChanges and other types with similar
|
|
|
|
// behavior yet, so this binding is compiled
|
|
|
|
} else {
|
|
|
|
qCDebug(lcQmltcCompiler)
|
|
|
|
<< QStringLiteral(
|
|
|
|
"Binding at line %1 column %2 is deferred and thus not compiled")
|
|
|
|
.arg(QString::number(location.startLine),
|
|
|
|
QString::number(location.startColumn));
|
|
|
|
return;
|
2021-11-15 16:10:31 +00:00
|
|
|
}
|
|
|
|
}
|
2022-05-12 12:39:38 +00:00
|
|
|
|
|
|
|
const auto assignToProperty = [&](const QQmlJSMetaProperty &p, const QString &value,
|
|
|
|
bool constructFromQObject = false) {
|
2022-06-03 11:52:47 +00:00
|
|
|
if (p.isAlias() && QQmlJSUtils::hasCompositeBase(type)) {
|
2022-05-12 12:39:38 +00:00
|
|
|
qCDebug(lcQmltcCompiler) << u"Property '" + p.propertyName()
|
|
|
|
+ u"' is an alias on type '" + type->internalName()
|
|
|
|
+ u"' which is a QML type compiled to C++. The assignment is special"
|
|
|
|
+ u"in this case";
|
|
|
|
// TODO: attest whether we could simplify this (see why prototype
|
|
|
|
// did special code generation)
|
|
|
|
QmltcCodeGenerator::generate_assignToProperty(¤t.endInit.body, type, p, value,
|
|
|
|
accessor.name, constructFromQObject);
|
|
|
|
} else {
|
|
|
|
QmltcCodeGenerator::generate_assignToProperty(¤t.endInit.body, type, p, value,
|
|
|
|
accessor.name, constructFromQObject);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-11-15 16:10:31 +00:00
|
|
|
QQmlJSMetaProperty p = type->property(propertyName);
|
|
|
|
QQmlJSScope::ConstPtr propertyType = p.type();
|
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
const auto addObjectBinding = [&](const QString &value) {
|
|
|
|
if (p.isList()) {
|
|
|
|
const auto &listName =
|
|
|
|
m_uniques[UniqueStringId(current, propertyName)].qmlListVariableName;
|
|
|
|
Q_ASSERT(!listName.isEmpty());
|
|
|
|
current.endInit.body << u"%1.append(%2);"_qs.arg(listName, value);
|
|
|
|
} else {
|
|
|
|
assignToProperty(p, value, /* constructFromQObject = */ true);
|
|
|
|
}
|
|
|
|
};
|
2021-11-15 16:10:31 +00:00
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
// when property is list, create a local variable (unique per-scope &&
|
|
|
|
// per-property) that would be used to append new elements
|
|
|
|
if (p.isList()) {
|
|
|
|
auto &listName = m_uniques[UniqueStringId(current, propertyName)].qmlListVariableName;
|
|
|
|
if (listName.isEmpty()) { // not created yet, add extra instructions
|
|
|
|
listName = u"listref_" + propertyName;
|
|
|
|
current.endInit.body << QStringLiteral("QQmlListReference %1(%2, %3);")
|
|
|
|
.arg(listName, accessor.name,
|
|
|
|
QQmlJSUtils::toLiteral(propertyName,
|
|
|
|
u"QByteArrayLiteral"));
|
|
|
|
current.endInit.body << QStringLiteral("Q_ASSERT(%1.canAppend());").arg(listName);
|
|
|
|
}
|
|
|
|
}
|
2021-11-15 16:10:31 +00:00
|
|
|
|
|
|
|
switch (binding.bindingType()) {
|
|
|
|
case QQmlJSMetaPropertyBinding::BoolLiteral: {
|
Refactor QQmlJSMetaPropertyBinding
- Store the "binding content", i.e. the literal, object, interceptor or
value source in a std::variant. This saves space compared to the
previous approach (where we had individual fields), and also helps
with type-safety. We also get rid of m_bindingType, which can now be
obtained via BindingType(m_bindingContent.index()), as long as we keep
the types in the variant in the correct order.
- Remove a few methods that were not used anywhere. Those can be brought
back once we actually need them.
- Removed the setLiteral method in lieu of type-safe setX methods where
X is one of the literal types. QQmlJSImportVisitor has been refactored
to make use of this, and its parseLiteralBinding method has been
changed into a parseLiteralOrScriptBinding method. This simplifies the
control flow, and ensures that we always add the parsed binding.
- Literals no longer store the literal type (as in, the actual
QQmlJSScope pointer) themselves. Instead, literalType takes a pointer
to a QQmlJSTypeResolver, and we use that one to resolve the type on
demand.
Change-Id: I0612d49f2f46fec0fa90f0f5047d8c9f831214ef
Reviewed-by: Ulf Hermann <ulf.hermann@qt.io>
Reviewed-by: Andrei Golubev <andrei.golubev@qt.io>
2022-02-24 10:58:38 +00:00
|
|
|
const bool value = binding.boolValue();
|
2022-05-12 12:39:38 +00:00
|
|
|
assignToProperty(p, value ? u"true"_s : u"false"_s);
|
2021-11-15 16:10:31 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case QQmlJSMetaPropertyBinding::NumberLiteral: {
|
2022-05-12 12:39:38 +00:00
|
|
|
assignToProperty(p, QString::number(binding.numberValue()));
|
2021-11-15 16:10:31 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case QQmlJSMetaPropertyBinding::StringLiteral: {
|
2022-05-12 12:39:38 +00:00
|
|
|
assignToProperty(p, QQmlJSUtils::toLiteral(binding.stringValue()));
|
2021-11-15 16:10:31 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case QQmlJSMetaPropertyBinding::Null: {
|
|
|
|
// poor check: null bindings are only supported for var and objects
|
2022-05-12 12:39:38 +00:00
|
|
|
Q_ASSERT(propertyType->isSameType(m_typeResolver->varType())
|
|
|
|
|| propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference);
|
|
|
|
if (propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Reference)
|
|
|
|
assignToProperty(p, u"nullptr"_s);
|
|
|
|
else
|
|
|
|
assignToProperty(p, u"QVariant::fromValue(nullptr)"_s);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case QQmlJSMetaPropertyBinding::Script: {
|
|
|
|
QString bindingSymbolName = type->internalName() + u'_' + propertyName + u"_binding";
|
|
|
|
bindingSymbolName.replace(u'.', u'_'); // can happen with group properties
|
|
|
|
compileScriptBinding(current, binding, bindingSymbolName, type, propertyName, propertyType,
|
|
|
|
accessor);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case QQmlJSMetaPropertyBinding::Object: {
|
|
|
|
// NB: object is compiled with compileType(), here just need to use it
|
|
|
|
auto object = binding.objectType();
|
|
|
|
|
|
|
|
// 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
|
|
|
|
const QString qobjectParent = u"this"_s;
|
|
|
|
|
|
|
|
if (!propertyType) {
|
2021-11-15 16:10:31 +00:00
|
|
|
recordError(binding.sourceLocation(),
|
2022-05-12 12:39:38 +00:00
|
|
|
u"Binding on property '" + propertyName + u"' of unknown type");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// special case of implicit or explicit component:
|
|
|
|
if (qsizetype index = m_visitor->qmlComponentIndex(object); index >= 0) {
|
|
|
|
// TODO: or do this in current.init? - yes, because otherwise
|
|
|
|
// components are not going to be id-accessible, which is bad
|
|
|
|
|
|
|
|
const QString objectName = newSymbol(u"sc"_s);
|
|
|
|
current.endInit.body << u"{"_s;
|
|
|
|
current.endInit.body << QStringLiteral(
|
|
|
|
"auto thisContext = QQmlData::get(%1)->outerContext;")
|
|
|
|
.arg(qobjectParent);
|
|
|
|
current.endInit.body << QStringLiteral(
|
|
|
|
"auto %1 = QQmlObjectCreator::createComponent(engine, "
|
|
|
|
"%2, %3, %4, thisContext);")
|
|
|
|
.arg(objectName,
|
|
|
|
generate_callCompilationUnit(m_urlMethodName),
|
|
|
|
QString::number(index), qobjectParent);
|
|
|
|
current.endInit.body << QStringLiteral("thisContext->installContext(QQmlData::get(%1), "
|
|
|
|
"QQmlContextData::OrdinaryObject);")
|
|
|
|
.arg(objectName);
|
|
|
|
|
|
|
|
// objects wrapped in implicit components do not have visible ids,
|
|
|
|
// however, explicit components can have an id and that one is going
|
|
|
|
// to be visible in the common document context
|
|
|
|
if (!object->isComponentRootElement()) {
|
|
|
|
if (int id = m_visitor->runtimeId(object); id >= 0) {
|
|
|
|
QString idString = m_visitor->addressableScopes().id(object);
|
|
|
|
if (idString.isEmpty())
|
|
|
|
idString = u"<unknown>"_s;
|
|
|
|
QmltcCodeGenerator::generate_setIdValue(¤t.endInit.body, u"thisContext"_s,
|
|
|
|
id, objectName, idString);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
addObjectBinding(objectName);
|
|
|
|
current.endInit.body << u"}"_s;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
const QString objectName = newSymbol(u"o"_s);
|
|
|
|
current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg(
|
|
|
|
objectName, object->internalName(), qobjectParent);
|
|
|
|
current.init.body << u"creator->set(%1, %2);"_s.arg(
|
|
|
|
QString::number(m_visitor->creationIndex(object)), objectName);
|
|
|
|
|
|
|
|
// refetch the same object during endInit to set the bindings
|
|
|
|
current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg(
|
|
|
|
objectName, object->internalName(),
|
|
|
|
QString::number(m_visitor->creationIndex(object)));
|
|
|
|
addObjectBinding(objectName);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case QQmlJSMetaPropertyBinding::Interceptor:
|
|
|
|
Q_FALLTHROUGH();
|
|
|
|
case QQmlJSMetaPropertyBinding::ValueSource: {
|
|
|
|
// NB: object is compiled with compileType(), here just need to use it
|
|
|
|
QSharedPointer<const QQmlJSScope> object;
|
|
|
|
if (bindingType == QQmlJSMetaPropertyBinding::Interceptor)
|
|
|
|
object = binding.interceptorType();
|
|
|
|
else
|
|
|
|
object = binding.valueSourceType();
|
|
|
|
|
|
|
|
// 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
|
|
|
|
const QString qobjectParent = u"this"_s;
|
|
|
|
|
|
|
|
if (!propertyType) {
|
|
|
|
recordError(binding.sourceLocation(),
|
|
|
|
u"Binding on property '" + propertyName + u"' of unknown type");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto &objectName = m_uniques[UniqueStringId(current, propertyName)].onAssignmentObjectName;
|
|
|
|
if (objectName.isEmpty()) {
|
|
|
|
objectName = u"onAssign_" + propertyName;
|
|
|
|
|
|
|
|
current.init.body << u"auto %1 = new %2(creator, engine, %3);"_s.arg(
|
|
|
|
objectName, object->internalName(), qobjectParent);
|
|
|
|
current.init.body << u"creator->set(%1, %2);"_s.arg(
|
|
|
|
QString::number(m_visitor->creationIndex(object)), objectName);
|
|
|
|
|
|
|
|
current.endInit.body << u"auto %1 = creator->get<%2>(%3);"_s.arg(
|
|
|
|
objectName, object->internalName(),
|
|
|
|
QString::number(m_visitor->creationIndex(object)));
|
|
|
|
}
|
|
|
|
|
|
|
|
// NB: we expect one "on" assignment per property, so creating
|
|
|
|
// QQmlProperty each time should be fine (unlike QQmlListReference)
|
|
|
|
current.endInit.body << u"{"_s;
|
|
|
|
current.endInit.body << u"QQmlProperty qmlprop(%1, %2);"_s.arg(
|
|
|
|
accessor.name, QQmlJSUtils::toLiteral(propertyName));
|
|
|
|
current.endInit.body
|
|
|
|
<< u"QT_PREPEND_NAMESPACE(QQmlCppOnAssignmentHelper)::set(%1, qmlprop);"_s.arg(
|
|
|
|
objectName);
|
|
|
|
current.endInit.body << u"}"_s;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case QQmlJSMetaPropertyBinding::AttachedProperty: {
|
|
|
|
Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact
|
|
|
|
const auto attachedType = binding.attachingType();
|
|
|
|
Q_ASSERT(attachedType);
|
|
|
|
auto subbindings = attachedType->ownPropertyBindingsInQmlIROrder();
|
|
|
|
if (propertyName == u"Component"_s) {
|
|
|
|
// TODO: there's a special QQmlComponentAttached, which has to be
|
|
|
|
// called? c.f. qqmlobjectcreator.cpp's finalize()
|
|
|
|
for (const auto &b : qAsConst(subbindings)) {
|
|
|
|
Q_ASSERT(b.bindingType() == QQmlJSMetaPropertyBinding::Script);
|
|
|
|
compileScriptBindingOfComponent(current, attachedType, b, b.propertyName());
|
|
|
|
}
|
2021-11-15 16:10:31 +00:00
|
|
|
} else {
|
2022-05-12 12:39:38 +00:00
|
|
|
const QString attachingTypeName = propertyName; // acts as an identifier
|
|
|
|
auto attachingType = m_typeResolver->typeForName(attachingTypeName);
|
|
|
|
|
|
|
|
QString attachedTypeName = attachedType->baseTypeName();
|
|
|
|
Q_ASSERT(!attachedTypeName.isEmpty());
|
|
|
|
|
|
|
|
auto &attachedMemberName =
|
|
|
|
m_uniques[UniqueStringId(current, propertyName)].attachedVariableName;
|
|
|
|
if (attachedMemberName.isEmpty()) {
|
|
|
|
attachedMemberName = u"m_" + attachingTypeName;
|
|
|
|
// add attached type as a member variable to allow noop lookup
|
|
|
|
current.variables.emplaceBack(attachedTypeName + u" *", attachedMemberName,
|
|
|
|
u"nullptr"_s);
|
|
|
|
// Note: getting attached property is fairly expensive
|
|
|
|
const QString getAttachedPropertyLine = u"qobject_cast<" + attachedTypeName
|
|
|
|
+ u" *>(qmlAttachedPropertiesObject<" + attachingType->internalName()
|
|
|
|
+ u">(this, /* create = */ true))";
|
|
|
|
current.endInit.body
|
|
|
|
<< attachedMemberName + u" = " + getAttachedPropertyLine + u";";
|
|
|
|
}
|
|
|
|
|
|
|
|
// compile bindings of the attached property
|
|
|
|
partitionBindings(subbindings.begin(), subbindings.end());
|
|
|
|
for (const auto &b : qAsConst(subbindings)) {
|
|
|
|
compileBinding(current, b, attachedType,
|
|
|
|
{ type, attachedMemberName, propertyName, false });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case QQmlJSMetaPropertyBinding::GroupProperty: {
|
|
|
|
Q_ASSERT(accessor.name == u"this"_s); // doesn't have to hold, in fact
|
|
|
|
if (p.read().isEmpty()) {
|
|
|
|
recordError(binding.sourceLocation(),
|
|
|
|
u"READ function of group property '" + propertyName + u"' is unknown");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto groupType = binding.groupType();
|
|
|
|
Q_ASSERT(groupType);
|
|
|
|
|
|
|
|
const bool isValueType =
|
|
|
|
propertyType->accessSemantics() == QQmlJSScope::AccessSemantics::Value;
|
|
|
|
if (!isValueType
|
|
|
|
&& propertyType->accessSemantics() != QQmlJSScope::AccessSemantics::Reference) {
|
|
|
|
recordError(binding.sourceLocation(),
|
|
|
|
u"Group property '" + propertyName + u"' has unsupported access semantics");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto subbindings = groupType->ownPropertyBindingsInQmlIROrder();
|
|
|
|
auto firstScript = partitionBindings(subbindings.begin(), subbindings.end());
|
|
|
|
|
|
|
|
// if we have no non-script bindings, we have no bindings that affect
|
|
|
|
// the value type group, so no reason to generate the wrapping code
|
|
|
|
const bool generateValueTypeCode = isValueType && (subbindings.begin() != firstScript);
|
|
|
|
|
|
|
|
QString groupAccessor =
|
|
|
|
QmltcCodeGenerator::wrap_privateClass(accessor.name, p) + u"->" + p.read() + u"()";
|
|
|
|
// NB: used when isValueType == true
|
|
|
|
const QString groupPropertyVarName = accessor.name + u"_group_" + propertyName;
|
|
|
|
// value types are special
|
|
|
|
if (generateValueTypeCode) {
|
|
|
|
if (p.write().isEmpty()) { // just reject this
|
|
|
|
recordError(binding.sourceLocation(),
|
|
|
|
u"Group property '" + propertyName
|
|
|
|
+ u"' is a value type without a setter");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
current.endInit.body << u"auto " + groupPropertyVarName + u" = " + groupAccessor + u";";
|
|
|
|
// addressof operator is to make the binding logic work, which
|
|
|
|
// expects that `accessor.name` is a pointer type
|
|
|
|
groupAccessor = QmltcCodeGenerator::wrap_addressof(groupPropertyVarName);
|
|
|
|
}
|
|
|
|
|
|
|
|
// compile bindings of the grouped property
|
|
|
|
const auto compile = [&](const QQmlJSMetaPropertyBinding &b) {
|
|
|
|
compileBinding(current, b, groupType,
|
|
|
|
{ type, groupAccessor, propertyName, isValueType });
|
|
|
|
};
|
|
|
|
|
|
|
|
auto it = subbindings.begin();
|
|
|
|
for (; it != firstScript; ++it) {
|
|
|
|
Q_ASSERT(it->bindingType() != QQmlJSMetaPropertyBinding::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 << QmltcCodeGenerator::wrap_privateClass(accessor.name, p) + u"->"
|
|
|
|
+ p.write() + u"(" + groupPropertyVarName + u");";
|
|
|
|
}
|
|
|
|
|
|
|
|
// once the value is written back, process the script bindings
|
|
|
|
for (; it != subbindings.end(); ++it) {
|
|
|
|
Q_ASSERT(it->bindingType() == QQmlJSMetaPropertyBinding::Script);
|
|
|
|
compile(*it);
|
2021-11-15 16:10:31 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case QQmlJSMetaPropertyBinding::Invalid: {
|
2022-05-12 12:39:38 +00:00
|
|
|
recordError(binding.sourceLocation(), u"This binding is invalid"_s);
|
2021-11-15 16:10:31 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
default: {
|
2022-05-12 12:39:38 +00:00
|
|
|
recordError(binding.sourceLocation(), u"Binding is not supported"_s);
|
2021-11-15 16:10:31 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-12 12:39:38 +00:00
|
|
|
// returns compiled script binding for "property changed" handler in a form of object type
|
|
|
|
static QmltcType compileScriptBindingPropertyChangeHandler(const QQmlJSMetaPropertyBinding &binding,
|
|
|
|
const QQmlJSScope::ConstPtr &objectType,
|
|
|
|
const QString &urlMethodName,
|
|
|
|
const QString &functorCppType,
|
|
|
|
const QString &objectCppType)
|
|
|
|
{
|
|
|
|
QmltcType bindingFunctor {};
|
|
|
|
bindingFunctor.cppType = functorCppType;
|
|
|
|
bindingFunctor.ignoreInit = true;
|
|
|
|
|
|
|
|
// default member variable and ctor:
|
|
|
|
const QString pointerToObject = objectCppType + u" *";
|
|
|
|
bindingFunctor.variables.emplaceBack(
|
|
|
|
QmltcVariable { pointerToObject, u"m_self"_s, u"nullptr"_s });
|
|
|
|
bindingFunctor.baselineCtor.name = functorCppType;
|
|
|
|
bindingFunctor.baselineCtor.parameterList.emplaceBack(
|
|
|
|
QmltcVariable { pointerToObject, u"self"_s, QString() });
|
|
|
|
bindingFunctor.baselineCtor.initializerList.emplaceBack(u"m_self(self)"_s);
|
|
|
|
|
|
|
|
// call operator:
|
|
|
|
QmltcMethod callOperator {};
|
|
|
|
callOperator.returnType = u"void"_s;
|
|
|
|
callOperator.name = u"operator()"_s;
|
|
|
|
callOperator.modifiers << u"const"_s;
|
|
|
|
QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
|
|
|
|
&callOperator.body, urlMethodName + u"()",
|
|
|
|
objectType->ownRuntimeFunctionIndex(binding.scriptIndex()), u"m_self"_s, u"void"_s, {});
|
|
|
|
|
|
|
|
bindingFunctor.functions.emplaceBack(std::move(callOperator));
|
|
|
|
|
|
|
|
return bindingFunctor;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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).scope;
|
|
|
|
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
|
|
|
|
|
|
|
|
// TODO: we also have to go over extensions here!
|
|
|
|
for (QQmlJSScope::ConstPtr base = owner->baseType(); base; base = base->baseType())
|
|
|
|
index += int(base->ownProperties().size());
|
|
|
|
return { p, index };
|
|
|
|
}
|
|
|
|
|
|
|
|
void QmltcCompiler::compileScriptBinding(QmltcType ¤t,
|
|
|
|
const QQmlJSMetaPropertyBinding &binding,
|
|
|
|
const QString &bindingSymbolName,
|
|
|
|
const QQmlJSScope::ConstPtr &objectType,
|
|
|
|
const QString &propertyName,
|
|
|
|
const QQmlJSScope::ConstPtr &propertyType,
|
|
|
|
const QmltcCompiler::BindingAccessorData &accessor)
|
|
|
|
{
|
|
|
|
const auto compileScriptSignal = [&](const QString &name) {
|
|
|
|
QString This_signal = u"this"_s;
|
|
|
|
QString This_slot = u"this"_s;
|
|
|
|
QString objectClassName_signal = objectType->internalName();
|
|
|
|
QString objectClassName_slot = objectType->internalName();
|
|
|
|
|
|
|
|
// TODO: ugly crutch to make stuff work
|
|
|
|
if (accessor.name != u"this"_s) { // e.g. if attached property
|
|
|
|
This_signal = accessor.name;
|
|
|
|
This_slot = u"this"_s; // 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 auto signalMethods = objectType->methods(name, QQmlJSMetaMethod::Signal);
|
|
|
|
Q_ASSERT(!signalMethods.isEmpty()); // an error somewhere else
|
|
|
|
QQmlJSMetaMethod signal = signalMethods.at(0);
|
|
|
|
Q_ASSERT(signal.methodType() == QQmlJSMetaMethod::Signal);
|
|
|
|
|
|
|
|
const QString signalName = signal.methodName();
|
|
|
|
const QString slotName = newSymbol(signalName + u"_slot");
|
|
|
|
|
|
|
|
const QString signalReturnType = figureReturnType(signal);
|
|
|
|
const QList<QmltcVariable> slotParameters = compileMethodParameters(
|
|
|
|
signal.parameterNames(), signal.parameterTypes(), /* allow unnamed = */ true);
|
|
|
|
|
|
|
|
// SignalHander specific:
|
|
|
|
QmltcMethod slotMethod {};
|
|
|
|
slotMethod.returnType = signalReturnType;
|
|
|
|
slotMethod.name = slotName;
|
|
|
|
slotMethod.parameterList = slotParameters;
|
|
|
|
|
|
|
|
QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
|
|
|
|
&slotMethod.body, m_urlMethodName + u"()",
|
|
|
|
objectType->ownRuntimeFunctionIndex(binding.scriptIndex()),
|
|
|
|
u"this"_s, // 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", "
|
|
|
|
+ QmltcCodeGenerator::wrap_qOverload(slotParameters,
|
|
|
|
u"&" + objectClassName_signal + u"::"
|
|
|
|
+ signalName)
|
|
|
|
+ u", " + This_slot + u", &" + objectClassName_slot + u"::" + slotName
|
|
|
|
+ u");";
|
|
|
|
};
|
|
|
|
|
|
|
|
switch (binding.scriptKind()) {
|
|
|
|
case QQmlJSMetaPropertyBinding::Script_PropertyBinding: {
|
|
|
|
if (!propertyType) {
|
|
|
|
recordError(binding.sourceLocation(),
|
|
|
|
u"Binding on property '" + propertyName + u"' of unknown type");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto [property, absoluteIndex] = getMetaPropertyIndex(objectType, propertyName);
|
|
|
|
if (absoluteIndex < 0) {
|
|
|
|
recordError(binding.sourceLocation(),
|
|
|
|
u"Binding on unknown property '" + propertyName + u"'");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
QString bindingTarget = accessor.name;
|
|
|
|
|
|
|
|
int valueTypeIndex = -1;
|
|
|
|
if (accessor.isValueType) {
|
|
|
|
Q_ASSERT(accessor.scope != objectType);
|
|
|
|
bindingTarget = u"this"_s; // TODO: not necessarily "this"?
|
|
|
|
auto [groupProperty, groupPropertyIndex] =
|
|
|
|
getMetaPropertyIndex(accessor.scope, accessor.propertyName);
|
|
|
|
if (groupPropertyIndex < 0) {
|
|
|
|
recordError(binding.sourceLocation(),
|
|
|
|
u"Binding on group property '" + accessor.propertyName
|
|
|
|
+ u"' of unknown type");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
valueTypeIndex = absoluteIndex;
|
|
|
|
absoluteIndex = groupPropertyIndex; // e.g. index of accessor.name
|
|
|
|
}
|
|
|
|
|
|
|
|
QmltcCodeGenerator::generate_createBindingOnProperty(
|
|
|
|
¤t.endInit.body, generate_callCompilationUnit(m_urlMethodName),
|
|
|
|
u"this"_s, // NB: always using enclosing object as a scope for the binding
|
|
|
|
static_cast<qsizetype>(objectType->ownRuntimeFunctionIndex(binding.scriptIndex())),
|
|
|
|
bindingTarget, // binding target
|
|
|
|
absoluteIndex, property, valueTypeIndex, accessor.name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case QQmlJSMetaPropertyBinding::Script_SignalHandler: {
|
|
|
|
const auto name = QQmlJSUtils::signalName(propertyName);
|
|
|
|
Q_ASSERT(name.has_value());
|
|
|
|
compileScriptSignal(*name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case QQmlJSMetaPropertyBinding::Script_ChangeHandler: {
|
|
|
|
const QString objectClassName = objectType->internalName();
|
|
|
|
const QString bindingFunctorName = newSymbol(bindingSymbolName + u"Functor");
|
|
|
|
|
|
|
|
const auto signalName = QQmlJSUtils::signalName(propertyName);
|
|
|
|
Q_ASSERT(signalName.has_value()); // an error somewhere else
|
|
|
|
const auto actualProperty = QQmlJSUtils::changeHandlerProperty(objectType, *signalName);
|
|
|
|
Q_ASSERT(actualProperty.has_value()); // an error somewhere else
|
|
|
|
const auto actualPropertyType = actualProperty->type();
|
|
|
|
if (!actualPropertyType) {
|
|
|
|
recordError(binding.sourceLocation(),
|
|
|
|
u"Binding on property '" + actualProperty->propertyName()
|
|
|
|
+ u"' of unknown type");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// due to historical reasons (QQmlObjectCreator), prefer NOTIFY over
|
|
|
|
// BINDABLE when both are available. thus, test for notify first
|
|
|
|
const QString notifyString = actualProperty->notify();
|
|
|
|
if (!notifyString.isEmpty()) {
|
|
|
|
compileScriptSignal(notifyString);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
const QString bindableString = actualProperty->bindable();
|
|
|
|
QString typeOfQmlBinding =
|
|
|
|
u"std::unique_ptr<QPropertyChangeHandler<" + bindingFunctorName + u">>";
|
|
|
|
|
|
|
|
current.children << compileScriptBindingPropertyChangeHandler(
|
|
|
|
binding, objectType, m_urlMethodName, bindingFunctorName, objectClassName);
|
|
|
|
|
|
|
|
// 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">("
|
|
|
|
+ QmltcCodeGenerator::wrap_privateClass(accessor.name, *actualProperty)
|
|
|
|
+ u"->" + bindableString + u"().onValueChanged(" + bindingFunctorName + u"("
|
|
|
|
+ accessor.name + u"))));";
|
|
|
|
|
|
|
|
current.variables.emplaceBack(
|
|
|
|
QmltcVariable { typeOfQmlBinding, bindingSymbolName, QString() });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
recordError(binding.sourceLocation(), u"Invalid script binding found"_s);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: should use "compileScriptBinding" instead of custom code
|
|
|
|
void QmltcCompiler::compileScriptBindingOfComponent(QmltcType ¤t,
|
|
|
|
const QQmlJSScope::ConstPtr &type,
|
|
|
|
const QQmlJSMetaPropertyBinding &binding,
|
|
|
|
const QString &propertyName)
|
|
|
|
{
|
|
|
|
const auto signalName = QQmlJSUtils::signalName(propertyName);
|
|
|
|
Q_ASSERT(signalName.has_value());
|
|
|
|
const QList<QQmlJSMetaMethod> signalMethods = type->methods(*signalName);
|
|
|
|
Q_ASSERT(!signalMethods.isEmpty());
|
|
|
|
// Component signals do not have parameters
|
|
|
|
Q_ASSERT(signalMethods.at(0).parameterNames().isEmpty());
|
|
|
|
const QString signalReturnType = figureReturnType(signalMethods.at(0));
|
|
|
|
const QString slotName = newSymbol(*signalName + u"_slot");
|
|
|
|
|
|
|
|
// SignalHander specific:
|
|
|
|
QmltcMethod slotMethod {};
|
|
|
|
slotMethod.returnType = signalReturnType;
|
|
|
|
slotMethod.name = slotName;
|
|
|
|
|
|
|
|
// Component is special:
|
|
|
|
QmltcCodeGenerator::generate_callExecuteRuntimeFunction(
|
|
|
|
&slotMethod.body, m_urlMethodName + u"()",
|
|
|
|
type->ownRuntimeFunctionIndex(binding.scriptIndex()), u"this"_s, 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"_s) {
|
|
|
|
current.handleOnCompleted.body << slotName + u"();";
|
|
|
|
} else if (*signalName == u"destruction"_s) {
|
|
|
|
if (!current.dtor) {
|
|
|
|
current.dtor = QmltcDtor {};
|
|
|
|
current.dtor->name = u"~" + current.cppType;
|
|
|
|
}
|
|
|
|
current.dtor->body << slotName + u"();";
|
|
|
|
}
|
|
|
|
current.functions << std::move(slotMethod);
|
|
|
|
}
|
|
|
|
|
2021-09-16 07:35:33 +00:00
|
|
|
QT_END_NAMESPACE
|