Strict typing problem

I'm trying to get my app strict typing compliant. Already hit the wall with a simple array:

using Toybox.Lang;
...
var showList = [0, 0, 0] as Lang.Array<Lang.Number>;
...
showList[0] = 4;
produces
Cannot determine if container assignment is using container type.
What's wrong here?
  • I now solved this, hopefully safe, with an explicite string to number conversion function:

    // Safely convert a string with unknown content to a number value
        public function str2num(str as String) as Number {
    
            var v = str.toNumber();
    
            if (v == null)
            {
                v = 0;
            }
            else if (!(v instanceof Number))
            {
                v = v as Number;
                if (v == null)
                {
                    v = 0;
                }
            }
    
            return v as Number;
        }

    Any comments on this, is it safe?

  • Seems safe but a little bit of overkill. You def don't want to cast v to a Number after you've determined it's not a Number, but in the given code it's harmless.

    I would probably do something generic like this, assuming you do want to always return a number.

        public function str2num(str as String, defaultValue as Number) as Number {
            var v = str.toNumber();
            if (v == null) {
                return defaultValue;
            } else {
                return v;
            }
        }
    

    Or if you want to stay with the hardcoded default of 0:

        public function str2num(str as String) as Number {
            var v = str.toNumber();
            if (v == null) {
                return 0;
            } else {
                return v;
            }
        }
    

  • I think we're talking about 2 issues:
    1.
    var s as String;
    function f() {
        var n = self.s.toNumber() * 10;
    }

    String.toNumber returns Number or Null so the above line probably? should give a complilation warning that Null can't be multilied by Number, thus force the developer to do: either var n = (self.s.toNumber() as Number) * 10; or something better.

    2. var v as Array<Number>;
    this array currently can have Null elements, in facts that will be the default value when we do new [size];
    Probably this is another compiler bug and it should work differently (and consistently) with Array<Number> vs Array<Number?> (especially that we're trying to move towards stricter type checks).
    However I'm not sure how could this be really implemented. Ok so you'll have to give a value to v in the constructor (this already works now), but how can the compiler check/enforce that all the elements are also set to non-Null value?

  • this wastes lot of code for no reason (other than not knowing a few things):
    1. v instanceof Number will return false on null, so all the above could be just:
    var v = str.toNumber();
    return v instanceof Number ? v : 0;

    2. but then probably it's both faster and less code:
    return v != null ? v : 0;

  • I think we're talking about 2 issues:

    I understand that.

    If you look at the examples given in the previous comments, the case of multiplying the result of String.toNumber() (Number or Null) by a Number is correctly flagged as an error by the compiler, so there's no problem there. The dev either has to do an explicit cast (which is not safe), or write code to handle the null case, as discussed before.

    The case of assigning the result of String.toNumber() to an element of Array<Number> is not flagged by as an error by the compiler but it should be.

    2. var v as Array<Number>;
    this array currently can have Null elements, in facts that will be the default value when we do new [size];

    Yeah so I think the problem is with code like this:

    var arrayOfNumbers as Array<Number> = new [5] as Array<Number>;

    The compiler shouldn't allow you to directly cast new [5] to Array<Number>, because that's saying that an array full of nulls is actually an array that only contains numbers. That should be an error or at the very least, a warning. Ofc one of the problems here is that the type checker doesn't know how to infer the type of an array yet, iirc.

    I think casts should work like typescript, where you can't cast between incompatible types unless you cast to any first.

    return v != null ? v : 0;

    Yes, that code is more compact, but it seems the type checker doesn't work properly with "?" (as discussed elsewhere). Consider the following (subtly incorrect) code which nonetheless compiles:

        public function str2num(str as String) as Number {
            var v = str.toNumber();
            return v == null ? v : 0;
        }
    

    The equivalent code using an if-statement will correctly fail to compile:

        public function str2num(str as String) as Number {
            var v = str.toNumber();
            if (v == null) {
                return v; // compile error here
            } else {
                return 0;
            }
        }

    Honestly there's so many bugs and limitations that I wonder if there's any point in using the type checker, especially if you use casts to get around these problems since it just removes type safety.

  • Next how to question on Arrays, but this time regarding a ByteArray.

    I parse values of a ByteArray (received as is by a BLE delegate) this way:

    public function procData(data as ByteArray) as Void {
            if (null != data)
    			{
    				for (var i = 0; i < data.size(); i++) 
            		{ 
    				    _dataManager.encode(data[i] as Number);
            		}
    			}
        }

    I'm wondering if this is a) safe and b) fast.

    My thoughts:

    a) yes, because "as Number" for a byte of a ByteArray always will give a number 0..255, never null.

    b) ?, maybe better to use the decodeNumber method of ByteArray?

  • I'll add one small optimization:
    c) var size = data.size(); for (var i = 0; i < size; i++)
    this will be faster because it uses a local variable instead of calling a function N times.

  • Yes I think "as Number" here is fine.

    One thing is that if data can actually be null, then the function signature should be changed to:

    public function procData(data as ByteArray or Null) as Void {

    Otherwise, may as well ditch the null != data check. This is the kind of thing that type checking is useful for. (If you changed the function signature *and* ditched the null check, then the code would not compile.)

    I agree that it would be helpful if you store the value of data.size() instead of calling it on each iteration of the loop.