Compiling QML to C++: Making types visible

This is the third installment in the series of blog posts on how to adjust your QML application to take the maximum advantage of qmlsc. In the first post we've set up the environment. You should read that post first in order to understand the others. In the second post I've shown how to add type annotations to JavaScript functions. Now we need to make sure that all types we want to use in QML are visible at compile time.

If you compile Qt Creator again, after our latest changes to the ButtonsBar.qml file, you may see some warnings like these:

Warning: ButtonsBar.qml: Object type Timeline::TimelineTheme is not derived from QObject or QQmlComponent
Warning: ButtonsBar.qml:59:34: Property "PanelStatusBarBackgroundColor" not found on type "Timeline::TimelineTheme"
        color: Theme.color(Theme.PanelStatusBarBackgroundColor)

Clearly, qmlsc has trouble identifying the type of our TimelineTheme. This means the binding for the background color of our button is not compiled to C++. Let's see how long it takes to execute this binding in its unoptimized form. QML-profile Qt Creator and load our example trace in the instance being profiled as described in the first blog post. Then look at ButtonsBar.qml again in the editor. You should see a little label to the left of the binding we're interested in.

sourcelabel

Clicking that label focuses the respective events in the QML profiler. We note that the binding is called once, and on my computer it takes 14.2µs to evaluate. I won't repeat the point about it not being a statistically significant measurement anymore. This disclaimer holds for all such numbers from here on.

Back to the original warning, though. Why is this type unknown to qmlsc? The base type of Timeline::TimelineTheme is Utils::Theme, as we can see in timelinetheme.h:

class TRACING_EXPORT TimelineTheme : public Utils::Theme
{
    Q_OBJECT
    [...]

The Utils library, however, is not prepared to be integrated with a QML module. In particular, it does not generate a metatypes.json file. The QML module for our Tracing library needs such metatypes to, well, know the types it's dealing with. You can make a library generate metatypes.json files using the qt_extract_metatypes() CMake command.

There is no downside to having those metatypes generated. Therefore, let's generate them for all of the libraries in Qt Creator. For this, we adapt Qt Creator's cmake/QtCreatorAPI.cmake and add this command to add_qtc_library(), conditional on Qt Creator being compiled with Qt >= 6.2 and using CMake's AUTOMOC feature:

get_target_property(have_automoc_prop ${name} AUTOMOC)
if("${Qt5_VERSION}" VERSION_GREATER_EQUAL "6.2.0" AND "${have_automoc_prop}")
  qt_extract_metatypes(${name})
endif()

We need to query for AUTOMOC as the metatypes files are generated by moc. In places where we don't run moc, we cannot generate any metatypes. However, there probably aren't any types to be exposed to QML in such places.

Indeed this helps and the original warning is gone. However, we get a new warning in the same place:

Warning: ButtonsBar.qml:59:22: Could not compile binding for color: type Color for argument 0 cannot be resolved
        color: Theme.color(Theme.PanelStatusBarBackgroundColor)

Why is that? The "color" method is declared in the Utils::Theme C++ type, in the file src/libs/utils/theme/theme.h, like this:

Q_INVOKABLE QColor color(Color role) const;

It's always a good idea to run the available C++ static analysis tools on all your files. If Qt Creator doesn't do it automatically, you can trigger it using the "Tools" -> "C++" -> "Analyze Current File" menu entry. Among other things, this will run the equivalent of the "clazy" tool on theme.h, which will give you a warning that tells you exactly what is wrong:

theme.h:471:17: invokable arguments need to be fully-qualified (Utils::Theme::Color instead of Color) [clazy-fully-qualified-moc-types]

Indeed, the metatype system will only be able to see the argument type at compile time if you fully qualify it. If you don't, then qmlsc doesn't know it. Let's fix it:

Q_INVOKABLE QColor color(Utils::Theme::Color role) const;

With that out of the way we're fine, one might think. Alas, not quite:

Warning: ButtonsBar.qml:59:22: Could not compile binding for color: return type QColor cannot be resolved
        color: Theme.color(Theme.PanelStatusBarBackgroundColor)

What is this? The compiler does not know QColor. QColor, as you may know, is a value type in QML. You can declare properties of type "color" on your elements, and if you assign them elsewhere, they are copied, rather than referred to like QObjects. Value types, like object types, are either built-in or belong to some module. QColor is not built-in as you may verify by inspecting the qml/builtins.qmltypes file of your Qt installation. As colors are inherently a visual thing, it belongs to the QtQuick module. The documentation states as much:

This value type is provided by the QtQuick import.

Now, if you look at the header of ButtonsBar.qml you see:

import QtQuick 2.1

So we should be fine, right? Look at the warning again. It says "return type QColor". It does not complain about the handling of QColor in the binding on Rectangle's color. Rather, it does not know what QColor is supposed to mean in the context of the function declaration of Theme::color(). This makes sense because a C++ type can be exposed by multiple modules in different ways. Just because QtQuick happens to have a QML type for QColor, we cannot assume that this is the one to be used here. However, we don't have any special rules for QColor in the QML module QtCreator.Tracing. We just want it to be passed through. So, let's tell the compiler to depend on QtQuick for everything it does with the QtCreator.Tracing module. This is what the "DEPENDENCIES" option to the the qt_add_qml_module() CMake command is for. So, in the CMakeLists.txt for QtCreator.Tracing at src/libs/tracing/CMakeLists.txt, adjust the QML module to add the dependency:

qt_add_qml_module(Tracing
  URI "QtCreator.Tracing"
  VERSION "1.0"
  NO_PLUGIN
  DEPENDENCIES
    QtQuick
  QML_FILES
    ${TRACING_QML_FILES}
  RESOURCES
    ${TRACING_QML_RESOURCES}
  SOURCES
    ${TRACING_CPP_SOURCES}
)

This was the last warning in the ButtonsBar.qml file. Yay! How long does the binding on color take now? Profile again and click on the source label again. 9.17µs it says on my machine, so we've saved 5µs.

Compatibility

We heavily depend on declarative QML type registration for all of this. Declarative type registration has been around since Qt 5.15. qt_add_qml_module() has been available since Qt 6.2, but if you are still writing qmldir files manually, you can also just add the dependencies manually by inserting "depends" entries as described in the docs.


Blog Topics:

Comments