Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-87616

Qt6: calling C++ method from JS context within QML results in incorrect overload chosen

    XMLWordPrintable

Details

    • 43d0eae81e30ae8c8502e68d56c6c8b7e2c30215 (qt/qtdeclarative/dev)

    Description

      Interesting issue spotted while fixing QtPIM tests to work with Qt6.  It seems like the QML engine has some change which means a different method overload will be chosen than in Qt5 times.

      Consider the following QML file:

      import QtQuick 2.0
      
      Rectangle {
          color: "lightsteelblue"
          width: 200
          height: 200
      
          Timer {
              running: true
              repeat: false
              interval: 1000
              onTriggered: {
                  var strings = helper.getValues()
                  console.log("JS got strings: " + strings)
                  helper.printValues(strings)
              }
          }
      }
      

      And the following C++:

      #include <QtCore/QCoreApplication>
      #include <QtCore/QVariantMap>
      #include <QtCore/QString>
      #include <QtCore/QStringList>
      #include <QtCore/QTimer>
      #include <QtCore/QObject>
      
      #include <QtGui/QGuiApplication>
      #include <QtQml/QQmlEngine>
      #include <QtQml/QQmlComponent>
      #include <QtQml/QQmlContext>
      #include <QtQuick/QQuickView>
      
      #include <QtDebug>
      
      class CustomElement : public QObject
      {
          Q_OBJECT
      
      public:
          CustomElement(QObject *parent = nullptr) : QObject(parent) {}
      };
      
      QML_DECLARE_TYPE(CustomElement)
      
      class Helper : public QObject
      {
          Q_OBJECT
      
      public:
          Helper(QQuickView *parent) : QObject(parent), m_view(parent) {}
          Q_INVOKABLE void doQuit() { QCoreApplication::instance()->quit(); }
          Q_INVOKABLE void doShow() {
              m_view->setSource(QUrl(QStringLiteral("main.qml")));
              m_view->show();
          }
      
          Q_INVOKABLE QStringList getValues() const {
              return QStringList() << QStringLiteral("one") << QStringLiteral("two");
          }
      
          Q_INVOKABLE void printValues(const QStringList &strings) {
              qWarning() << "Printing strings from C++: " << strings;
          }
      
          Q_INVOKABLE void printValues(const QList<CustomElement> &elements) {
              qWarning() << "Printing elements from C++: ";
              for (const auto &e : elements) qWarning() << e.objectName();
          }
      
      private:
          QQuickView *m_view;
      };
      
      int main(int argc, char *argv[])
      {
          QGuiApplication app(argc, argv);
      
          QQuickView view;
          Helper *h = new Helper(&view);
          QTimer::singleShot(1, h, &Helper::doShow);
          view.rootContext()->setContextProperty("helper", QVariant::fromValue<QObject*>(h));
          return app.exec();
      }
      
      #include "main.moc"
      

      With Qt5 we get:

      $ ./testqstringlist
      qml: JS got strings: one,two
      Printing strings from C++:  ("one", "two")
      

      With Qt6 we get:

      $ ./testqstringlist
      qml: JS got strings: one,two
      "Could not convert argument 0 at"
               "onTriggered@file:///home/qinetic/Qt/testapps/testqstringlist/main.qml:15"
      file:///home/qinetic/Qt/testapps/testqstringlist/main.qml:15: TypeError: Passing incompatible arguments to C++ functions from JavaScript is not allowed.
      

      despite the QML_DECLARE_TYPE(CustomElement), and the fact that the first overload (with QStringList parameter) should be chosen for this call, I think.

       

      Note that if we change the declaration of the second printValues() overload to look like:

          Q_INVOKABLE void printValues(const QList<CustomElement*> &elements) {
              qWarning() << "Printing elements from C++: ";
              for (auto e : elements) qWarning() << e->objectName();
          }
      

      Then we see the following in Qt5:

      qml: JS got strings: one,two
      "Could not convert argument 0 at"
               "onTriggered@file:///home/qinetic/Qt/testapps/testqstringlist/main.qml:15"
      "Passing incompatible arguments to C++ functions from JavaScript is dangerous and deprecated."
      "This will throw a JavaScript TypeError in future releases of Qt!"
      Printing elements from C++:
      

      i.e. triggering the issue described in https://stackoverflow.com/questions/60915460/call-c-function-from-qml-js-with-c-object-as-argument

       

      In short: I know that the second printValues() overload is bad (and I'm not sure why an example like this existed in the QtPIM unit tests to begin with; my solution will definitely be to remove it entirely).  However, I still think it is strange that the QML engine is choosing the second overload even when the call parameter type should perfectly match the signature of the first method.

      Attachments

        For Gerrit Dashboard: QTBUG-87616
        # Subject Branch Project Status CR V

        Activity

          People

            fabiankosmale Fabian Kosmale
            chrisadams Christopher Adams
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved:

              Gerrit Reviews

                There are no open Gerrit changes