Compiling QML to C++: Fixing unqualified access

This is the eighth 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. We're going to learn how to avoid unqualified access now.

You will see quite a few warnings on unqualified access when compiling Qt Creator with qmlsc enabled. An access to a name in QML is called "unqualified" if the name is neither found in the directly surrounding QML scope, nor an ID or a type name. Usually this means you're accessing members of the root element of a component, context properties, or undeclared signal parameters. Most of those are easy to fix. Let's look at the TimeMarks.qml file in the tracing library. It implements the time marks you see above the timeline view in the QML profiler and Performance Analyzer. In line 72, we get:

Warning: TimeMarks.qml:72:17: Unqualified access

The line in question is simply:

target: model

What does this mean? The model we want here is the one from the surrounding element. When interpreting this code, the QML engine will retrieve properties from he outermost element in the current scope without qualification. This concept is surprisingly complex as you can open extra scopes in various ways and those will interfere with your lookup. The compiler won't let you do this. Avoiding such lookups will not only make your code compile to C++ but will also make it more readable. So, let's just rewrite this to explicitly pick the model from the object we mean:

target: timeMarks.model

What does that win? When profiling Qt Creator the usual way the binding is run 26 times here. Without the optimization we get a total time of 38.1µs. With the optimization it's 29.8µs.

The same problem, the model being accessed in an unqualified way, is repeated several times in the same file. You can fix them all the same way. In order to access the timeMarks object inside the repeater, you have to add the following to the start of the file:

pragma ComponentBehavior: Bound

This makes sure that the delegate passed to the repeater is only instantiated in the context it's defined in. This way the compiler can guarantee that the "timeMarks" ID is actually available there. You can retrieve the delegate from the repeater, and try to instantiate it in a different place. With the above pragma, this will result in an error.

Then, however, we have a slightly different situation with the same warning:

Warning: TimeMarks.qml:89:26: Unqualified access

Looking at the code, we see:

color: ((index + (timeMarks.startOdd ? 1 : 0)) % 2)
        ? Theme.color(Theme.Timeline_BackgroundColor1)
        : Theme.color(Theme.Timeline_BackgroundColor2)

This is not the model, but rather the index injected into the context by the surrounding Repeater element. How can we qualify that one? We cannot really specify the place it comes from because it's not strictly a property. The repeater creates it on the fly for each object it creates. qmlsc does not know what it is because Repeater is not a built-in type and we cannot hardcode the context manipulations each possible type may do into the compiler. However, Repeater, like all of our view types that create delegates, has a different, better mode of operation: You can specify the data it should inject as "required" properties on the component it uses to create the delegates. Then the index is a property and we can access it as such. So let's add a required property to the "scaleItem" Rectangle:

required property int index

This way, the warning indeed disappears. Now that there are no more warnings, the binding is compiled to C++. The color binding is called fairly often, 163 times if you just load the example trace we've saved in the first post. Without the required property it takes 804µs in total, of which 718µs are spend on executing the JavaScript. With the required property we get 654µs and 596µs.

Compatibility

QML has always supported qualifying access to properties with IDs and it's always been a good idea. Required properties were introduced in Qt 5.15.0. The ComponentBehavior pragma is quite new. It will be released with Qt 6.4.


Blog Topics:

Comments