[Development] QTCS2019 Notes from QtQml session

Robin Burchell robin.burchell at crimson.no
Mon Nov 25 12:15:50 CET 2019


On Mon, Nov 25, 2019, at 10:43 AM, Ulf Hermann wrote:
> Indeed we need to deal with this somehow. Maybe we do need to retain 
> versioning on a module level. For example, the qmldir file could specify 
> what versions of which other modules it expects. Or we define that it's 
> not a problem ...

I think at least module-level versioning is a sensible idea.

> We could detect this in qmllint by just continuing the search for IDs
> even if we've found a property.

We could, but if I may gently suggest, I think I can count most of the people who use qmllint on a regular, useful basis on one hand ;-).

(One possible improvement to this could be to not _just_ have it as a separate tool: maybe debug builds should _also_ run qmllint - or at least a subset of its checks - on the code that the engine is loading? Alternatively, perhaps as an engine flag to at least allow it to be turned on once, and happen for everyone on a team in a unified zero-effort way)

> Mind that adding a method to a base class in C++ will shadow unrelated 
> other classes and functions even if it's not virtual or overridden. Why 
> did we actually decide that we can live with this effect in C++ but not 
> in QML? - Maybe because in C++ it's more likely to result in a compile 
> error due to mismatched signatures?

I think one of the biggest problems is that ID resolution crosses file boundaries. This essentially means that the ids chosen can very easily become part of the "API" of a component unless you are very vigilant to not allow that to happen.

Essentially this means that identifiers - and a child redefining an identifier that the parent happened to use - suffer from the same problem that you point out class members can suffer from.

This is a contrived example, but I have seen similar things to this cause bugs in production.

Picture a SettingsButton.qml with this sort of contents:
    Button { MouseArea { onClicked: console.log("clicked"); } }

Looks nice and innocent, right? But I notice that the MouseArea doesn't work because it has no geometry. I want to refer to it elsewhere in this component, too, so let's give it a name.

    Button { MouseArea { id: someFoo; width: parent.width; onClicked: console.log("clicked"); } }

In sensible code, this would work just fine, and have no complications.
Unfortunately, what you didn't know was that Button.qml is:

    Rectangle {
        width: someFoo.width
        height: 10
    }

The use of someFoo here was previously referring to a parent id. Now, it finds that id in the child instead, with a very different geometry to what was expected - just the same as if this was a property on the root item.

This is not an easy problem I think. For sure, you can't just take the "easy way" out and warn about duplicate ids during lookup, because while _sometimes_ this inheritance is decidedly wrong and not what you want, a lot of the time, id scoping is "private" and just fundamentally how code is written. In the C++ world, this would seem pretty silly:

    class Base { private: int m_myImplementationDetail; }
    class Base : Derived { private: int m_myImplementationDetail; } // warning: duplicate variable "m_myImplementationDetail" found in class Base

Yes, they do indeed have the same name, but that's also unambiguous and fine.

If we do consider them to be part of the API *sometimes* though, perhaps the right thing to do is to allow that to happen explicitly: specify what ids can be "exported/imported" and issue a compile error if those expectations are not met.

One problem though is that this import/export boundary should be checked at the file boundary, _not_ the component boundary, otherwise using things like Loader become a lot more annoying.

So perhaps it could look something like this:

import MyModule ...
expects Item myFoo; // we want something of type Item, identified as myFoo to be passed in from a containing file somewhere
provides MouseArea bar; // we provide this ID for children to find

Item {
    width: myFoo.width

    MouseArea {
        id: bar // is visible in scoped lookup (via 'provides' at the top)
    }
}

I guess you might also be able to use this for context properties - which suffer from many of the same unclear scoping issues rather than just outright removing them, but that might require some additional considerations that I'm not sure of right now.

-- 
  Robin Burchell
  robin at crimson.no


More information about the Development mailing list