Unexpected Compiler Behavior with WatchUi.getCurrentView()

Hi,

While investigating crash reports that appear to originate from WatchUi.getCurrentView(), I started working on a safe wrapper that explicitly validates all returned types.

What I find confusing is that I seem to be unable to check whether the returned delegate is an instance of BehaviorDelegate. The compiler reports an error stating that the delegate can never be an instance of BehaviorDelegate.

According to the documentation for WatchUi.InputDelegates, this should be possible. However, the compiler seems to disagree.

I’d appreciate your thoughts on this behavior. Is this a compiler bug, or am I doing something wrong?

Below is my code, followed by the compiler error message.

46 public function getCurrentViewSafe() as [ WatchUi.View or Null, WatchUi.InputDelegates or Null ] {
47     
48     var cwArray = WatchUi.getCurrentView();
49     if( ! (cwArray instanceof Array ) ) {
50         throw new GeneralException( "cwArray not an Array" );
51     } 
52     if( cwArray.size() != 2 ) {
53         throw new GeneralException( "cwArray.size = " + cwArray.size() );
54     }
55     
56     var view = cwArray[0];
57     if( view != null && ! ( view instanceof WatchUi.View ) ) {
58         throw new GeneralException( "cwArray[0] not a View" );
59     } 
60 
61     var delegate = cwArray[1];
62     if( delegate != null && 
63             ! ( delegate instanceof WatchUi.InputDelegate 
64                 || delegate instanceof WatchUi.BehaviorDelegate
65                 || delegate instanceof WatchUi.ConfirmationDelegate
66                 || delegate instanceof WatchUi.MenuInputDelegate
67                 || delegate instanceof WatchUi.NumberPickerDelegate
68                 || delegate instanceof WatchUi.PickerDelegate
69                 || delegate instanceof WatchUi.TextPickerDelegate
70                 || delegate instanceof WatchUi.WatchFaceDelegate
71                 || delegate instanceof WatchUi.Menu2InputDelegate
72                 || delegate instanceof WatchUi.ViewLoopDelegate ) 
73     ) {
74         throw new GeneralException( "cwArray[1] not an input delegate" );
75     }
76 
77     return cwArray;
78 }
WARNING: epix2pro47mm: D:\GitHub\ohg\source\user-interface\view-handling\ViewHandler.mc:50,12: Statement is not reachable.
WARNING: epix2pro47mm: D:\GitHub\ohg\source\user-interface\view-handling\ViewHandler.mc:53,12: Statement is not reachable.
WARNING: epix2pro47mm: D:\GitHub\ohg\source\user-interface\view-handling\ViewHandler.mc:58,12: Statement is not reachable.
WARNING: epix2pro47mm: D:\GitHub\ohg\source\user-interface\view-handling\ViewHandler.mc:62,8: '$.Toybox.WatchUi.NumberPickerDelegate' is deprecated.
WARNING: epix2pro47mm: D:\GitHub\ohg\source\user-interface\view-handling\ViewHandler.mc:74,12: Statement is not reachable.
ERROR: epix2pro47mm: D:\GitHub\ohg\source\user-interface\view-handling\ViewHandler.mc:62,8: Type 'PolyType<$.Toybox.WatchUi.ConfirmationDelegate 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>' is not an instance of '$.Toybox.WatchUi.BehaviorDelegate'.

Regards, Robert

Top Replies

  • What I find confusing is that I seem to be unable to check whether the returned delegate is an instance of BehaviorDelegate. The compiler reports an error stating that the delegate can

All Replies

  • Strange, because it really makes sense (what the documentation writes)

    Garmin really should've added WatchUi.Delegate as a base class for all of them.

  • What I find confusing is that I seem to be unable to check whether the returned delegate is an instance of BehaviorDelegate. The compiler reports an error stating that the delegate can never be an instance of BehaviorDelegate.

    No, you are drawing an incorrect conclusion.

    For example, this code compiles fine with strict type checking:

    var delegate = cwArray[1];
    if (delegate instanceof WatchUi.BehaviorDelegate) {
      System.println("foo");
    }

    The actual issue in your code is that (for once) type narrowing is working properly, which causes your code to have an impossible condition (regarding types) as written.

    If you look very closely at the error message, you'll see that the referenced PolyType does *not* include InputDelegate, which is a huge clue.

    Consider the following code snippet:

       delegate instanceof WatchUi.InputDelegate 
    || delegate instanceof WatchUi.BehaviorDelegate

    The 2nd condition will only be evaluated if the first condition is false. But if (delegate instanceof WatchUi.InputDelegate) is false, then delegate cannot be BehaviorDelegate, because BehaviorDelegate is a subclass of InputDelegate.

    The type checker was smart enough to narrow the InputDelegates polytype to get rid of InputDelegate *and* any derived classes (e.g. BehaviorDelegate).

    If you reverse the order of the conditions, you will find that it builds fine.

    if (delegate != null &&
        !(delegate instanceof WatchUi.BehaviorDelegate
          || delegate instanceof WatchUi.InputDelegate
          //...

  • Garmin really should've added WatchUi.Delegate as a base class for all of them.

    Monkey C doesn't support abstract classes, which probably explains why they didn't do this.

    Also, there are other delegates than just input delegates (like sync delegates), so if anything, they should've created an InputDelegate base class. (Oh wait they already have InputDelegate.)

    But again that would only work if they had a common base implementation for all of those input delegate classes, which apparently they don't.

  • Speaking of type checking, type narrowing and unreachable code warnings, I hope the compiler isn't optimizing away all of this code that it thinks is unreachable (due to type checking), because that would defeat the purpose of your typesafe function (not to state the obvious).

  • The actual issue in your code is that (for once) type narrowing is working properly, which causes your code to have an impossible condition (regarding types) as written.

    That makes sense, thanks for explaining!

    Speaking of type checking, type narrowing and unreachable code warnings, I hope the compiler isn't optimizing away all of this code that it thinks is unreachable (due to type checking), because that would defeat the purpose of your typesafe function (not to state the obvious).

    Good point. I tested this using the code below, and it works. The exception is thrown.

    Also, I was able to get rid of the compiler warnings by adding a type cast to an object and removing the check for the deprecated NumberPickerDelegate:



    I had to post the code as images, the forum wouldn’t let me post it otherwise.