Greetings and Salutations,

As some of you may know, QMetaObject has a problem with what we call "shadowing". Consider:

class Base
{
    Q_OBJECT
    Q_PROPERTY(int a READ a CONSTANT)

public:
    int a() const { return 4; }

    Q_INVOKABLE int b() const { return 5; }
};

class Derived : public Base
{
    Q_OBJECT
    Q_PROPERTY(int a READ a CONSTANT) // shadows Base::a

public:
    int a() const { return 6; }

    // Shadows Base::b
    Q_INVOKABLE int b() const { return 7; }
};

Now when you get a Base* and retrieve its property "a" or call its method "b" via the QMetaObject, you don't actually know which property you are retrieving or which method you are calling.

From C++ you can introspect the QMetaObject hierarchy and determine what property or method you're dealing with. This is much more complicated than the usual way of simply doing someObject->property("a"), though.

From QML, you can't. QML will always use the most-derived QMetaObject that has the property or method. You can shadow properties and methods further in QML, and most egregiously, you can shadow properties with methods and vice versa. This is rather dangerous because we internally rely on many of the properties of, say QQuickItem, to actually assume the values we set on them. If you shadow a property like QQuickItem::data, you're certainly in for some surprises. It is quite common to add a property called "data" to one of your custom types, though. So, this is inviting some serious foot archery. For fun and confusion, try the following:

// Base.qml
import QtQuick
Item {
   id: root
   property int data
}

// Derived.qml
import QtQuick
Base{
   Rectangle {}
}

This results in the following warning:

Derived.qml:3:4: Cannot assign value of type "Rectangle" to property "data", expecting "int"

We've recognized this as a problem a long time ago and introduced the FINAL attribute to Q_PROPERTY. The FINAL attribute only takes effect if you use QML. If you shadow a FINAL property in pure C++, it does nothing (unfortunately). However, if you try to use a *shadowed* FINAL property in QML you get a warning or an error, depending on the context. We can't really do better than this for FINAL alone. You can call moc on Base and Derived independently, and you can re-moc Base after building Derived. This way you can sneakily introduce a FINAL after the derived type already exists. So we have to accept some fuzziness there. It follows that also in QML we can't flat out reject all your shadowed properties. We can just assume they are not shadowed and still use the base type's properties in case they are.

On top of all this complication, however, the presence of FINAL is not quite enough because you have to remember to add FINAL to every property that's eligible for finality. Keeping track of this is messy and error-prone. As a result, few properties are FINAL in practice, even if most should be.

C++, back in the day, had a similar problem with virtual methods. When you declared a method in a derived type, you'd have to look up all the methods in all base types to figure out if the method you were declaring was virtual by inheritance. This was error-prone. You could accidentally introduce overriding methods that weren't meant to be virtual and vice versa.

In C++, this was solved by introducing the "override" and "final" keywords to mark methods that are intentionally overriding a virtual one. The introduction of the "override" and "final" keywords came with a number of warnings and errors that now steer you towards adding the new keywords to all methods that intentionally override, and only those.

In Qt 6.11, we're doing the same to Q_PROPERTY. In addition to FINAL, you can now add attributes VIRTUAL and OVERRIDE to mark properties that are intended to be overridden and ones that intentionally override a virtual property. Likewise, in QML you can mark properties with the "virtual", "override", and "final" keywords. If you use those new attributes and keywords, you get appropriate warnings and errors in QML (but not in C++, because see above). If you don't use them, you don't get warned ... yet. This is because we haven't adapted all of Qt to be free of those warnings, yet.

We still highly recommend you clean up unintended shadowing and use the new keywords and attributes where applicable already in Qt 6.11. You can get warned about shadowing properties without the new keywords and attributes by enabling debug messages from the qt.qml.propertyCache.append logging category. For example with an environment variable like this:

QT_LOGGING_RULES="qt.qml.propertyCache.append.debug=true"

For Qt 6.12, we are planning to enable these warnings by default. Among other things, this will mean that you will get warned when shadowing properties from Qt types that aren't meant to be shadowed.

In addition, we are planning to resolve the issue of shadowing methods and shadowing properties with methods in a similar way. We don't know when this will be ready, though.

best regards,
Ulf
--
Development mailing list
[email protected]
https://lists.qt-project.org/listinfo/development

Reply via email to