diff --git a/tests/auto/qml/qmltc/CMakeLists.txt b/tests/auto/qml/qmltc/CMakeLists.txt index f7f33fe214..fe2fbb8e75 100644 --- a/tests/auto/qml/qmltc/CMakeLists.txt +++ b/tests/auto/qml/qmltc/CMakeLists.txt @@ -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 diff --git a/tests/auto/qml/qmltc/data/methods.qml b/tests/auto/qml/qmltc/data/methods.qml new file mode 100644 index 0000000000..2ee128af92 --- /dev/null +++ b/tests/auto/qml/qmltc/data/methods.qml @@ -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; + } +} diff --git a/tests/auto/qml/qmltc/tst_qmltc.cpp b/tests/auto/qml/qmltc/tst_qmltc.cpp index 19e32e7941..1ccb971df6 100644 --- a/tests/auto/qml/qmltc/tst_qmltc.cpp +++ b/tests/auto/qml/qmltc/tst_qmltc.cpp @@ -33,6 +33,7 @@ #include "helloworld.h" #include "simpleqtquicktypes.h" #include "typewithenums.h" +#include "methods.h" // Qt: #include @@ -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()); + QCOMPARE(metaTypedSignal.parameterMetaType(1), QMetaType::fromType()); + QCOMPARE(metaTypedSignal.parameterMetaType(2), QMetaType::fromType()); + QCOMPARE(metaTypedSignal.parameterNames(), QList({ "a", "b", "c" })); + + QCOMPARE(metaUntypedMethod.parameterMetaType(0), QMetaType::fromType()); + QCOMPARE(metaUntypedMethod.parameterMetaType(1), QMetaType::fromType()); + QCOMPARE(metaUntypedMethod.parameterNames(), QList({ "d", "c" })); + + QCOMPARE(metaTypedMethod.parameterMetaType(0), QMetaType::fromType()); + QCOMPARE(metaTypedMethod.parameterMetaType(1), QMetaType::fromType()); + QCOMPARE(metaTypedMethod.returnMetaType(), QMetaType::fromType()); + QCOMPARE(metaTypedMethod.parameterNames(), QList({ "a", "b" })); +} + QTEST_MAIN(tst_qmltc) diff --git a/tests/auto/qml/qmltc/tst_qmltc.h b/tests/auto/qml/qmltc/tst_qmltc.h index 23c2d383c4..36c0145a75 100644 --- a/tests/auto/qml/qmltc/tst_qmltc.h +++ b/tests/auto/qml/qmltc/tst_qmltc.h @@ -48,4 +48,5 @@ private slots: void helloWorld(); void qtQuickIncludes(); void enumerations(); + void methods(); }; diff --git a/tools/qmltc/CMakeLists.txt b/tools/qmltc/CMakeLists.txt index 74f0ab5f84..64c071c583 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 + qmltccompilerutils.h DEFINES QT_NO_CAST_FROM_ASCII QT_NO_CAST_TO_ASCII diff --git a/tools/qmltc/qmltccodewriter.cpp b/tools/qmltc/qmltccodewriter.cpp index dff4885604..df9d11a6f7 100644 --- a/tools/qmltc/qmltccodewriter.cpp +++ b/tools/qmltc/qmltccodewriter.cpp @@ -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); diff --git a/tools/qmltc/qmltccompiler.cpp b/tools/qmltc/qmltccompiler.cpp index 0aeff12217..7c9c7b1734 100644 --- a/tools/qmltc/qmltccompiler.cpp +++ b/tools/qmltc/qmltccompiler.cpp @@ -29,6 +29,8 @@ #include "qmltccompiler.h" #include "qmltcoutputir.h" #include "qmltccodewriter.h" +#include "qmltccompilerutils.h" + #include #include @@ -205,6 +207,12 @@ void QmltcCompiler::compileType(QmltcType ¤t, 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 ¤t, const QQmlJSMetaEnum &e) @@ -220,4 +228,72 @@ void QmltcCompiler::compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e) u"Q_ENUM(%1)"_qs.arg(e.name())); } +static QList +compileMethodParameters(const QStringList &names, + const QList> &types, + bool allowUnnamed = false) +{ + QList 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 ¤t, 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 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 ¶m : 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 diff --git a/tools/qmltc/qmltccompiler.h b/tools/qmltc/qmltccompiler.h index 8d2f360d75..94247fbfea 100644 --- a/tools/qmltc/qmltccompiler.h +++ b/tools/qmltc/qmltccompiler.h @@ -65,6 +65,7 @@ private: void compileType(QmltcType ¤t, const QQmlJSScope::ConstPtr &type); void compileEnum(QmltcType ¤t, const QQmlJSMetaEnum &e); + void compileMethod(QmltcType ¤t, const QQmlJSMetaMethod &m); bool hasErrors() const { return m_logger->hasErrors(); } // TODO: count warnings as errors? void recordError(const QQmlJS::SourceLocation &location, const QString &message, diff --git a/tools/qmltc/qmltccompilerutils.h b/tools/qmltc/qmltccompilerutils.h new file mode 100644 index 0000000000..7af21532b9 --- /dev/null +++ b/tools/qmltc/qmltccompilerutils.h @@ -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 +#include + +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 diff --git a/tools/qmltc/qmltcoutputir.h b/tools/qmltc/qmltcoutputir.h index 067fb1bd43..cddbb8dada 100644 --- a/tools/qmltc/qmltcoutputir.h +++ b/tools/qmltc/qmltcoutputir.h @@ -75,16 +75,17 @@ struct QmltcEnum struct QmltcMethodBase { - QString returnType; // C++ return type QString name; // C++ function name QList 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 };