3
votes

I notice that QML has some unspecified problem with the following - when a delegate tries to instantiate an object that contains it (the delegate that is).

Consider this trivial tree builder example:

// Object.qml
Row {
  property int c : 0
  Rectangle {
    width: 50
    height: 50
    color: "red"
    border.color: "black"
    MouseArea {
      anchors.fill: parent
      acceptedButtons: Qt.LeftButton | Qt.RightButton
      onClicked: {
        if (mouse.button === Qt.LeftButton) c++
        else if (c) c--
      }
    }
  }
  List { model: c }
}

// List.qml
Column {
  property alias model : r.model
  Repeater {
    id: r
    delegate: Object {} // wont work
  }
}

In this example Object contains List, which has a delegate that instantiates another Object. However it doesn't work, there are no error messages, not even in debug mode, the application window simply doesn't appear, and from the looks of memory usage, it is hopelessly stuck somewhere in oblivion. It doesn't crash either. It just doesn't work.

My first suspicion is that this is yet another one of QML's shortsighted implementation details, with the noble goal to protect against accidental infinite recursions. However, in this case there is no danger of such, because even though the two objects mutually instantiate each other, the process is controlled by the "model".

It also happens to work "flawlessly" if an "indirection" is added, for example:

Column {
  property alias model : r.model
  Repeater {
    id: r
    delegate: Loader { source: "Object.qml" }
  }
}

If the delegate is replaced from an Object to a Loader that instnatiates an Object, it works OK, I assume because it doesn't trigger the dumb cross-reference check which interrupts execution.

enter image description here

You might be asking why I simply don't use the second scenario, and the answer is simple - it is trivial example it is OK, but in my actual production code this breaks something very complex I am making with cascading the amount of children items in negative xy space, and inserting a Loader in between simply kills it, and I haven't found a way to get it to work.

I also notice delegates have that absolutely identical problem when instantiating the tree example from Components within that same source, cross-reference or self-reference - when this type of instantiation recursion is used, the application gets stuck the same way - no errors, no warnings, no crash, no window, no nothing.

Any idea what's going on and how to fix it? My money is on infinite recursion protection, which assumes it is infinite even if it is in fact controlled and very much finite recursion.

EDIT: Also note that wrapping it this way doesn't work either delegate: Component { Object {} }. It works only if Object is not referenced as an instance, so it looks like some very superficial check if objects mutually contain each other regardless of the actual circumstances. So as expected, it also works if the wrapping is moved to Object:

 Loader {
   Component.onCompleted: setSource("List.qml", {"model": Qt.binding(function() {return c})})
 }

UPDATE:

I also noticed this:

// Obj.qml
Item {
  id: main
  property Obj thisObj : main
}

Which doesn't work either. This time there is some actual output in the console, saying that Obj is instantiated recursively which it really isn't since it is used in the context of a type property, not a value/instance.

It seems that the recursive instantiation checks in QML and fundamentally broken, they just produce absolutely nothing if a literal QML element type self/cross reference is found in the body of an object tree, and produce false output when it the reference is used to specify a property type, even if QML object properties are actually implemented as references rather than instances.

I also issued a bug report and proposing how to distinguish between actual infinite recursions and cases of self or cross reference which are actually not infinite recursions, which is quite simple - simply don't trigger if the reference is a property type or inside a delegate.

It doesn't give any reason or diagnostics, looks like a bug. Consider filing a bug report bugreports.qt.ioKrzysztof Piekutowski