Acknowledged

Unnecessary "Array index out of bounds" error for tuple PolyType

I have a typedef that can be either a tuple of length 1 or a tuple of length 2:

typedef ViewTuple as [WatchUi.Views] or [WatchUi.Views, WatchUi.InputDelegates];

(i.e. the return value of AppBase.getInitialView())

In my code, I have a function that takes a ViewTuple as an argument and tries to figure out which kind of tuple it is (length 1 or length 2) so it can treat it appropriately.

if (viewTuple.size() == 2) {
WatchUi.switchToView(viewTuple[0], viewTuple[1], transition);
} else {
WatchUi.switchToView(viewTuple[0], null, transition);
}

Note that in the first case, I have explictly checked the length of the tuple array and can be sure. But I get the following error:


Array index is out of bounds for type '$.Toybox.Lang.Array[$.Toybox.WatchUi.Confirmation or $.Toybox.WatchUi.Menu or $.Toybox.WatchUi.NumberPicker or $.Toybox.WatchUi.ProgressBar or $.Toybox.WatchUi.TextPicker or $.Toybox.WatchUi.View or $.Toybox.WatchUi.ViewLoop]'.

I can resolve this by adding an additional typecast:

if (viewTuple.size() == 2) {
viewTuple = viewTuple as [WatchUi.Views, WatchUi.InputDelegates];
WatchUi.switchToView(viewTuple[0], viewTuple[1], transition);
} else {
WatchUi.switchToView(viewTuple[0], null, transition);
}

But this looks really kludgy and is obviously unnecessary. IMO, the type checker should be smart enough to figure out which case of the PolyType we are dealing with here and that indexing the array at index 1 will not cause an error.


  • No need to define a new variable in this case,

    viewTuple = viewTuple as [WatchUi.Views, WatchUi.InputDelegates];

    works fine. But ideally simply checking the size of the array should be enough on its own.

  • But I agree that the type checker should be made smarter Slight smile

  • Reason 1: it doesn't work the way you try because of how the compiler "remembers" what are the possible types of a variable at a certain point. IMHO if you would've define a NEW variable, it would work: 

    var viewTuple2 = viewTuple as [WatchUi.Views, WatchUi.InputDelegates];

    but because you tried to reuse the existing variable, it just "added" a possible type to it (in other words it's not smart enough to know that in that branch it can't be viewTuple = viewTuple as [WatchUi.Views] any more)

    Reason 2: why I don't recommend to create a new variable but to use the in-place type cast is because the "problem" (not smart enough compiler) is a compile time only issue, try not to fix it by changing your code (aka the logic). And assigning a value to viewTuple is some code (though probably only 1 or 2 bytes) that I'd avoid.

    If you want to even further spare "duplicate" code and your code is really the above (you only do that in the if/else blocks) then I would even go as far as:

        WatchUi.switchToView(viewTuple[0], viewTuple.size()==2 ? (viewTuple as [WatchUi.Views, WatchUi.InputDelegates])[1] : null transition);

  • It seems to be an edge case of the usual "bug" that the type checker is not smart enough to let us do:

    if (a != null && ...) { a.foo ... }

    So the solution is the same: cast viewTuple BUT not the way you tried (for 2 reasons):

    (viewTuple as [WatchUi.Views, WatchUi.InputDelegates])[1]