qmltc: Compile QML methods to C++

This also requires some changes to the output IR + couple things
could also be fixed while at it

Task-number: QTBUG-84368
Change-Id: Ie1535cbbe36cd874e9787ea91fe85b638326de20
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Andrei Golubev 2021-11-01 16:18:30 +01:00
parent 590dd43c4d
commit dd153ad174
10 changed files with 219 additions and 7 deletions

View File

@ -10,6 +10,7 @@ set(qml_sources
data/NameConflict.qml
data/simpleQtQuickTypes.qml
data/typeWithEnums.qml
data/methods.qml
)
set_source_files_properties(data/NameConflict.qml PROPERTIES

View File

@ -0,0 +1,18 @@
import QtQml
QtObject {
signal justSignal()
signal typedSignal(string a, QtObject b, real c)
function justMethod() {
console.log("justMethod()");
}
function untypedMethod(d, c) {
console.log("methodWithParams, d = " + d + ", c = " + c);
}
function typedMethod(a: real, b: int): string {
console.log("typedMethod, a = " + a + ", b = " + b);
return a + b;
}
}

View File

@ -33,6 +33,7 @@
#include "helloworld.h"
#include "simpleqtquicktypes.h"
#include "typewithenums.h"
#include "methods.h"
// Qt:
#include <QtCore/qstring.h>
@ -143,4 +144,48 @@ void tst_qmltc::enumerations()
QCOMPARE(enumerator2.value(2), PREPEND_NAMESPACE(typeWithEnums)::ValuesSpecified::B2_);
}
void tst_qmltc::methods()
{
QQmlEngine e;
PREPEND_NAMESPACE(methods) created(&e);
const QMetaObject *mo = created.metaObject();
QVERIFY(mo);
QMetaMethod metaJustSignal = mo->method(mo->indexOfSignal("justSignal()"));
QMetaMethod metaTypedSignal = mo->method(mo->indexOfSignal(
QMetaObject::normalizedSignature("typedSignal(QString,QObject *,double)")));
QMetaMethod metaJustMethod = mo->method(mo->indexOfMethod("justMethod()"));
QMetaMethod metaUntypedMethod = mo->method(mo->indexOfMethod(
QMetaObject::normalizedSignature("untypedMethod(QVariant,QVariant)")));
QMetaMethod metaTypedMethod = mo->method(
mo->indexOfMethod(QMetaObject::normalizedSignature("typedMethod(double,int)")));
QVERIFY(metaJustSignal.isValid());
QVERIFY(metaTypedSignal.isValid());
QVERIFY(metaJustMethod.isValid());
QVERIFY(metaUntypedMethod.isValid());
QVERIFY(metaTypedMethod.isValid());
QCOMPARE(metaJustSignal.methodType(), QMetaMethod::Signal);
QCOMPARE(metaTypedSignal.methodType(), QMetaMethod::Signal);
QCOMPARE(metaJustMethod.methodType(), QMetaMethod::Method);
QCOMPARE(metaUntypedMethod.methodType(), QMetaMethod::Method);
QCOMPARE(metaTypedMethod.methodType(), QMetaMethod::Method);
QCOMPARE(metaTypedSignal.parameterMetaType(0), QMetaType::fromType<QString>());
QCOMPARE(metaTypedSignal.parameterMetaType(1), QMetaType::fromType<QObject *>());
QCOMPARE(metaTypedSignal.parameterMetaType(2), QMetaType::fromType<double>());
QCOMPARE(metaTypedSignal.parameterNames(), QList<QByteArray>({ "a", "b", "c" }));
QCOMPARE(metaUntypedMethod.parameterMetaType(0), QMetaType::fromType<QVariant>());
QCOMPARE(metaUntypedMethod.parameterMetaType(1), QMetaType::fromType<QVariant>());
QCOMPARE(metaUntypedMethod.parameterNames(), QList<QByteArray>({ "d", "c" }));
QCOMPARE(metaTypedMethod.parameterMetaType(0), QMetaType::fromType<double>());
QCOMPARE(metaTypedMethod.parameterMetaType(1), QMetaType::fromType<int>());
QCOMPARE(metaTypedMethod.returnMetaType(), QMetaType::fromType<QString>());
QCOMPARE(metaTypedMethod.parameterNames(), QList<QByteArray>({ "a", "b" }));
}
QTEST_MAIN(tst_qmltc)

View File

@ -48,4 +48,5 @@ private slots:
void helloWorld();
void qtQuickIncludes();
void enumerations();
void methods();
};

View File

@ -11,6 +11,7 @@ qt_internal_add_tool(${target_name}
qmltctyperesolver.h
qmltcvisitor.h qmltcvisitor.cpp
qmltccompiler.h qmltccompiler.cpp
qmltccompilerutils.h
DEFINES
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_TO_ASCII

View File

@ -309,7 +309,10 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcMethod &method)
{
const auto [hSignature, cppSignature] = functionSignatures(method);
// Note: augment return type with preambles in declaration
code.rawAppendToHeader(method.returnType + u" " + hSignature + u";");
QString prefix = method.declarationPrefixes.join(u' ');
if (!prefix.isEmpty())
prefix.append(u' ');
code.rawAppendToHeader(prefix + method.returnType + u" " + hSignature + u";");
// do not generate method implementation if it is a signal
const auto methodType = method.type;
@ -331,13 +334,12 @@ void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcMethod &method)
void QmltcCodeWriter::write(QmltcOutputWrapper &code, const QmltcCtor &ctor)
{
const auto [hSignature, cppSignature] = functionSignatures(ctor);
const QString returnTypeWithSpace = ctor.returnType.isEmpty() ? u""_qs : ctor.returnType + u" ";
code.rawAppendToHeader(returnTypeWithSpace + hSignature + u";");
QString prefix = ctor.declarationPrefixes.join(u' ');
if (!prefix.isEmpty())
prefix.append(u' ');
code.rawAppendToHeader(prefix + hSignature + u";");
code.rawAppendToCpp(u""); // blank line
if (!returnTypeWithSpace.isEmpty())
code.rawAppendToCpp(returnTypeWithSpace);
code.rawAppendSignatureToCpp(cppSignature);
if (!ctor.initializerList.isEmpty()) {
code.rawAppendToCpp(u":", 1);

View File

@ -29,6 +29,8 @@
#include "qmltccompiler.h"
#include "qmltcoutputir.h"
#include "qmltccodewriter.h"
#include "qmltccompilerutils.h"
#include <QtCore/qloggingcategory.h>
#include <algorithm>
@ -205,6 +207,12 @@ void QmltcCompiler::compileType(QmltcType &current, const QQmlJSScope::ConstPtr
current.enums.reserve(enums.size());
for (auto it = enums.begin(); it != enums.end(); ++it)
compileEnum(current, it.value());
const auto methods = type->ownMethods();
const auto properties = type->ownProperties();
current.functions.reserve(methods.size() + properties.size() * 3); // sensible default
for (const QQmlJSMetaMethod &m : methods)
compileMethod(current, m);
}
void QmltcCompiler::compileEnum(QmltcType &current, const QQmlJSMetaEnum &e)
@ -220,4 +228,72 @@ void QmltcCompiler::compileEnum(QmltcType &current, const QQmlJSMetaEnum &e)
u"Q_ENUM(%1)"_qs.arg(e.name()));
}
static QList<QmltcVariable>
compileMethodParameters(const QStringList &names,
const QList<QSharedPointer<const QQmlJSScope>> &types,
bool allowUnnamed = false)
{
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);
parameters.emplaceBack(augmentInternalName(types[i]), name, QString());
}
return parameters;
}
void QmltcCompiler::compileMethod(QmltcType &current, const QQmlJSMetaMethod &m)
{
const auto 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"_qs;
} else {
type = augmentInternalName(m.returnType());
}
return type;
};
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) {
// just put "unimplemented" for now
for (const QmltcVariable &param : compiledParams)
code << u"Q_UNUSED(%1);"_qs.arg(param.name);
code << u"Q_UNIMPLEMENTED();"_qs;
if (returnType != u"void"_qs) {
code << u"return %1;"_qs.arg(m.returnType()->accessSemantics()
== QQmlJSScope::AccessSemantics::Reference
? u"nullptr"_qs
: returnType + u"{}");
}
}
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();
if (methodType == QQmlJSMetaMethod::Method)
compiled.declarationPrefixes << u"Q_INVOKABLE"_qs;
current.functions.emplaceBack(compiled);
}
QT_END_NAMESPACE

View File

@ -65,6 +65,7 @@ private:
void compileType(QmltcType &current, const QQmlJSScope::ConstPtr &type);
void compileEnum(QmltcType &current, const QQmlJSMetaEnum &e);
void compileMethod(QmltcType &current, const QQmlJSMetaMethod &m);
bool hasErrors() const { return m_logger->hasErrors(); } // TODO: count warnings as errors?
void recordError(const QQmlJS::SourceLocation &location, const QString &message,

View File

@ -0,0 +1,66 @@
/****************************************************************************
**
** 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 QMLTCCOMPILERUTILS_H
#define QMLTCCOMPILERUTILS_H
#include <QtCore/qstring.h>
#include <private/qqmljsscope_p.h>
QT_BEGIN_NAMESPACE
/*!
\internal
Wraps \a type into \c const and \c & if that is a "good" thing to do (e.g.
the type is not a pointer type).
*/
inline QString wrapInConstRef(QString type)
{
if (!type.endsWith(u'*'))
type = u"const " + type + u"&";
return type;
}
/*!
\internal
Returns an internalName() of \a s, using the accessSemantics() to augment
the result
*/
inline QString augmentInternalName(const QQmlJSScope::ConstPtr &s)
{
Q_ASSERT(s->accessSemantics() != QQmlJSScope::AccessSemantics::Sequence);
const QString suffix =
(s->accessSemantics() == QQmlJSScope::AccessSemantics::Reference) ? u" *"_qs : u""_qs;
return s->internalName() + suffix;
}
QT_END_NAMESPACE
#endif // QMLTCCOMPILERUTILS_H

View File

@ -75,16 +75,17 @@ struct QmltcEnum
struct QmltcMethodBase
{
QString returnType; // C++ return type
QString name; // C++ function name
QList<QmltcVariable> parameterList; // C++ function parameter list
QStringList body; // C++ function code
QQmlJSMetaMethod::Access access = QQmlJSMetaMethod::Public; // access specifier
QStringList declarationPrefixes;
};
// Represents QML -> C++ compiled function
struct QmltcMethod : QmltcMethodBase
{
QString returnType; // C++ return type
QQmlJSMetaMethod::Type type = QQmlJSMetaMethod::Method; // Qt function type
};