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

Parents
  • 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

Comment
  • 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

Children
  • 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.