Acknowledged

Various issues with tuple types introduced in 7.x beta

Tuple types are a welcome addition to the language, but create some issues. Some are just temporary during the transition, others are more fundamental.

For example, the blog post shows the old way:

// So much ugly typing
function getInitialView() as Array<Views or InputDelegates>? {
    return [ new StartView(), new StartDelegate() ] as Array<Views or InputDelegates>;
}

and the new way:

function getInitialView() as [Views] or [Views, InputDelegates] {
    // Where did the as clause go?
    return [ new StartView(), new StartDelegate() ] ; 
}

If I try to build my code (written the old way) with the new sdk, it errors out:

ERROR: fenix5:MyApp.mc:17: Cannot override '$.Toybox.Application.AppBase.getInitialView' with a different return type.

But if I change it to the "new way", it won't compile with the old compiler. How about if I just drop the return type declaration? The compiler shouldn't need it, because it knows the type of the method its overriding, and won't allow you to change it.

With that change the old compiler is happy. The new compiler does a little better - it no longer complains about the declaration of the function, but now it complains about the returned value:

ERROR: fenix5: MyApp.mc:18,8: Object of type '$.Toybox.Lang.Array<$.Toybox.WatchUi.InputDelegates or $.Toybox.WatchUi.Views>' does not match return type 'PolyType<$.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] or $.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, $.Toybox.WatchUi.BehaviorDelegate or $.Toybox.WatchUi.ConfirmationDelegate or $.Toybox.WatchUi.InputDelegate or $.Toybox.WatchUi.Menu2InputDelegate or $.Toybox.WatchUi.MenuInputDelegate or $.Toybox.WatchUi.NumberPickerDelegate or $.Toybox.WatchUi.PickerDelegate or $.Toybox.WatchUi.TextPickerDelegate or $.Toybox.WatchUi.ViewLoopDelegate or $.Toybox.WatchUi.WatchFaceDelegate]>'.

That's not surprising, it's expecting a tuple type, but we're casting the tuple to a generic array. Ok, so drop the cast:

Now it compiles with 7.x but errors out with 6.x because its expecting Array<Views or InputDelegates>, but gets Array<Any> (the type that 6.x deduces for any Array literal).

So there's apparently no way to write the code that works for both 6.x and 7.x. This is a big problem for testing the beta because I'm forced to modify my code to test it, and then undo the modifications to go back to 6.x. Even when 7.x comes out of beta, I'm going to be reluctant to move to a release that forces me to make backwards incompatible changes to my code. Although many releases have forced me to make changes, I can't remember a single one where those changes couldn't be backward compatible.

The more fundamental issue is with the type enforcement itself. Again, taking an example from the blog post

function foo(x as [Number, Number, Number]) as [Number, Number, Number] {
    x[1] = "Hello"; // Allowed, type is now [Number, String, Number]
    return x; // Error, type mismatch
}

This totally breaks type safety. Suppose we modify it to return Void, so that it compiles, and then call it like:

function foo(x as [Number, Number, Number]) as Void {
    x[1] = "Hello"; // Allowed, type is now [Number, String, Number]
}

function bar() as Number {
  var x = [1, 2, 3];
  foo(x);
  // oops...
  return x[0]-x[1]-x[2];
}

The compiler builds this without any warnings, but I'm pretty sure it's going to crash at runtime when it tries to subtract a string from a number. I can't verify that because although I can build the App with 7.x, the simulator refuses to start (it opens a blank window, but never actually starts to run the code) [Edit: "Run and Debug" always hangs for me, but "Run without Debugging" works, and it does indeed crash computing x[0] - x[1], as expected].