Acknowledged
CIQQA-3581

Casting Float to Number does not yield Number

This code:

var myFloat = 5.0;
var myNumber = myFloat as Number;
var mod = myNumber % 1;

Compiles without warnings, but crashes at runtime with:

Error: Unhandled Exception
Exception: UnexpectedTypeException: Expected Number/Long, given Number/Float

As far as I can tell:

  • this behavior is undocumented
  • the types "Number/Long" and "Number/Float" are undocumented
  • the effects of casting numeric types are undocumented

Consequently I'm not sure how to characterize this bug. If it's expected, this is a documentation bug. If it's not expected, this is a runtime and/or compiler bug.

I read the relevant section on "type casting" in the SDK docs. I've reproduced the totality of the documentation on type casting here:

The as keyword can also be used in an expression to type cast a value to another type. This can be useful if the type is not clear to the type system.

This, unfortunately, does not help very much.

---

Tested on Descent G1 & G2 simulators

SDK 8.3.0

  • > Separately, I have also noticed that the compiler is unable to verify initialization if the assignment happens anywhere other than in "initialize()":

    Personally I am ok with this. After all how can the compiler ensure that a derived class does not override setThing so that thing is no longer initialized?

    Swift and TypeScript are no different.

    I think it's a lot more serious that in Monkey C, if a derived class's initializer does not explicitly call the parent/super class's initializer, that's only a compiler warning and not an error. (Note that super.initialize() will not automatically be called)

    That leads to potential situations like this, where the type checker fails to prevent a run time error (not for the first time) because the dev ignored a warning:

  • Yes, I understand what is happening in the code now. My point is that the docs are worse than useless, they are actually incorrect.

    The Any type is special in the sense that it can be used in place of...any type.

    Directly contradicting the docs which state:

    the compiler will only allow values of that type to be assigned.

    Any is not Number. There is no caveat in this statement.

    As for the rest - yeah, I have turned on strict type checking and am converting my codebase to use it, although frankly at this point I do not trust the type checker to be correct based on the quality level in the rest of the platform.

    Separately, I have also noticed that the compiler is unable to verify initialization if the assignment happens anywhere other than in "initialize()":

    class Example {
      var thing as Float;
      function initialize() { setThing(1.0) }
      function setThing(v as Float) { thing = v; }
    }

    This doesn't compile as the compiler cannot verify that thing is initialized...No question here, just amusing.

    Thanks for all your help. Hopefully at some point Garmin or the Alpha Monkey comes to understand that when you invent a programming language, it's considered good practice to document how to use it, and keep these docs up to date and correct.

  • - Therefore, the best policy is to use type check level two or three, if you want the type check system to be legit useful
    - To set the type check level for all projects: CTRL-SHIFT-P / CMD-SHIFT-P > Open User Settings > [Search] Monkey C > Monkey C: Type Check Level 
    - To set the type check level for the current project, add the following line to monkey.jungle:
    Sorry for the code screenshots, but the forum is blocking attempts to post code, as it has been doing for about a year now
  • - You can remove this loophole by increasing the type checking level

    -- the default type checking level is 1 / gradual. In this mode, variables, arguments and return values are allowed to be untyped without any warnings or errors. The exception is that if you are passing a function as a callback to an API call, then that function needs to have the correct type)

    -- if you increase the type checking level to 2 / informative or 3 / strict, then you will be given a warning/error about untyped variabes

  • Considering this code:

    // Number is a 32-bit signed integer
    var myNumber as Number = 0;

    function setMyNumber(value) {
        myNumber = value;
    }

    function test() {
        setMyNumber(5.12);
        System.println(myNumber.format("%.2f"));
    }

    According to the docs, this code should not compile:

    Once a type has been bound to a value, the compiler will only allow values of that type to be assigned.

    Since it does compile, this implies type coercion; but thanks to your explanation, I now understand that the docs are wrong.

    Prior to your explanation, I expected a numeric conversion to occur and the output to be "5.0". In fact the output is "5.12". I see now that this is just normal duck typing behavior. The presence of a casting operator made me think that a numeric conversion was performed, but I suppose this is on me as the docs do state that there are no primitive types, only objects. Intuitively, I would expect "as Float" to call ".toFloat()" on numeric types; this would follow the least surprise principle which Moron C claims is a priority. In any case, it's now clear why casting effects aren't documented - there aren't any effects.

    Regarding this specific code:

    - "as Number" in "var myNumber as Number" is a type declaration, not a type cast.

    The equivalent code in TypeScript would be:

    let myNumber: number = 0;

    - Even if Monkey C had type coercion, it wouldn't apply here since there's no type cast

    What's actually happening here:

    - Normally, the type checker will indeed prevent myNumber from being assigned to a value of any type other than Number

    - However, the 1st argument (value) of setMyNumber is untyped, which means that the type is Any. (You cannot explicitly declare or cast a type of Any in Monkey C). The Any type is special in the sense that it can be used in place of...any type.

    - This behaviour exists for backwards compatibility with old pre-Monkey Types code