Evaluation of null values and type checks - why is this an error?

Why is this an error:

if (workoutStartTime != null && workoutStartDistance != null) {
var elapsedTime = info.timerTime != null ? (info.timerTime - workoutStartTime)/1000.0 : 0.0;
[...]
}

I get the following error message:
Attempting to perform operation 'sub' with invalid types 'Null' and '$.Toybox.Lang.Number'.

workoutStartTime is not null when the minus operation is executed

info.timerTime is not null as per the initial test in this expression, otherwise we would not execute the minus operation.

I have more examples where the operation x != null ? true-statement : false-statement does not seem to understand that x is not null if the true-statement is executed.

I am using project.typecheck = 2

  • Hi . My usage would not translate seamlessly into your code, I was just trying to convey a rough approach that meant I was able to avoid disabling the type checking.

    My goal was to allow the type checker to be happy if was to try to make a Number null by also giving the variable a Null type.

  • My goal was to allow the type checker to be happy if was to try to make a Number null by also giving the variable a Null type

    Can you give us an example? How are you going to use nullNumber? I can't think of anywhere it would help, let alone relating to the original post.

  • Sorry, the stupid forum engine doesn't let me write a comment that has code, so here it is as an image:

  • This goes back to the fact that type declarations (“var a as Number”) and type casts (“1.0 as Number”) use similar syntax in Monkey C (“as”), but they arguably shouldn’t.

    I think it leads to a lot of confusion, especially since type declarations arguably make your code better (by avoiding type errors), but type casts do the opposite (they’re a necessary evil which can make your code worse by potentially allowing type errors which wouldn’t otherwise be possible).

  • var nullNumber = null as Number | Null;

    Not sure what problem this is intended to solve, as it’s an untyped variable declaration with an initial value of null (but the compiler thinks the type of this initial value is Number or Null due to the type cast).

    I don’t think the cast of null to Number of Null will actually have any effect, in this case, assuming that nullNumber is *not* a local variable, but it’s a member variable or global variable. [If nullNumber is actually a local variable, then it’s a different story, but I don’t think that’s the case, as you said you switched to flocsy’s version, which should not be possible for local variables, as local variables are not allowed to have type declarations.]

    Here, “untyped” means the variable is not constrained to any type at all. For example, I think you’ll find that assigning a string to nullNumber will not result in a compiler / type checker error.

    nullNumber = “foo"; // no error (sorry for the wrong type of quotes here, but the forum won’t let me edit this comment to use straight quotes)

    The difference with flocsy’s code is deeper than just using ?.

    His code is equivalent to:

    var nullNumber as Number | Null = null;

    This is completely different than your example. It’s saying that nullNumber is a typed variable that’s constrained to be Number or null, with an initial value of null. In this case, assigning a non-number/non-null value to nullNumber should result in an error:

    nullNumber = "foo" ; // type check error

    To recap:

    var nullNumber = null as Number | Null; // untyped variable, initial value is null (but cast to Number or Null)

    var nullNumber as Number? = null; // variable typed as Number or Null, initial value is null
    var nullNumber as Number | Null = null; // variable typed as Number or Null, initial value is null

    Like I said in my other comment, it’s a shame that “as” is used for both type declarations and type casting, as the two concepts are completely different (and should have different syntax, as they do in every other language).

  • To make the comparison with typescript:

    Type cast:

    Monkey C:

    var nullNumber = null as Number | Null; // untyped variable, initial value is null (but cast to Number or Null)

    TypeScript:

    var nullNumber = null as Number | Null; // untyped variable, initial value is null (but cast to Number or Null)

    Type declaration:

    Monkey C: 

    var nullNumber as Number? = null; // variable typed as Number or Null, initial value is null

    TypeScript:

    var nullNumber: Number? = null; // variable typed as Number or Null, initial value is null

  • TypeScript:

    var nullNumber = null as Number | Null; // untyped variable, initial value is null (but cast to Number or Null)

    Doesn't change any of your conclusions, but just for correctness, in typescript this is a typed variable that must hold a number or null - I'm assuming you meant to use the typescript names there. Even if you didn't and Number and Null are some types declared elsewhere, it would still be a typed variable, that must hold one or the other.

    See this ts playground link, for example. In case the link goes away:

    function foo() {
      var x = null as number | null;
      x = "hello"; // this is an error
      return x;
    }

    In typescript, a variable's initializer determines its type even if it doesn't have a type declaration.