Class name as function parameter, how does it work?

I have a function that takes a class as input parameter and then instantiates it.

It looks kind of like this:

class SomeClass {}

class OtherClass {
    function startHere() {
        doSomething( SomeClass );
    }
    function doSomething( myClass ) {
        var c = new myClass();
    }
}

This works fine, but I am wondering how it works exactly. What type is the parameter of doSomething()? In the language reference I could not find any type that represents a class.

Is there a way to at compile time restrict the class passed in to certain types (e.g. all classes extending a certain base class)? I know I could use instanceof at runtime, but would prefer compile time.

  • Not very scientific but usually works: turn on strict type checking and the error message will tell you the answer

  • Idk if this helps, but if you try to assign a "class" to a variable in a context when it's not allowed, the compiler refers to it as a "class definition".

    Perhaps even more relevant, if you let the compiler infer the type of such a class reference by assigning it to a local variable, it claims the variable is uninitialized (which is clearly wrong):

    iow, the compiler doesn't think of a class name as having a "type" (or even a value), even if you *can* assign it to a variable. (Which probably indicates that you shouldn't)

    If I print x using System.println, the console displays "class" (without the quotes).

    Just my two cents, but I would careful about using this "feature" as it doesn't seem to be an officially supported part of the language. It may work (in some ways), but I'm not sure if it was meant to work.

    Furthermore, I found a bug related to this exact pattern:

    Type checker does not detect access of non-existent symbol on class definition when class is assigned to a variable

    iow, using this pattern breaks the type checker.

    This is another reason I think this pattern is dangerous (and probably wasn't meant to work at all). I've never seen any Garmin code which uses it.

  • Another example, where the "type" of a class is just the class itself:

  • So you could try using the class name as its type:

    class SomeClass {}
    
    class OtherClass {
        function startHere() {
            doSomething(SomeClass);
        }
        function doSomething(myClass as SomeClass) {
            var c = new myClass(); // ERROR: Cannot instantiate object of type '$.SomeClass'.
            var d = new SomeClass(); // no problem
        }
    }
    

    Sadly it doesn't work due to the type check error (it might work at runtime tho, if you turn off the type checker).

    I think the presence of the type check error demonstrates that we're really not meant to be passing class names around like this.

    Indeed the following code works at runtime, as long as the type checker is disabled (or "as SomeClass" is omitted from doSomething())

    class SomeClass {
        var x;
        function initialize() {
            x = 42;
        }
        function printValue() {
            System.println(x);
        }
    }
    
    class OtherClass {
        function startHere() {
            doSomething(SomeClass);
        }
        function doSomething(myClass as SomeClass) {
            var c = new myClass(); // ERROR: Cannot instantiate object of type '$.SomeClass'.
            c.printValue();
        }
    }
    
    function test() {
      var c = new OtherClass();
      c.startHere();
    }
    

  • This code is incorrect. For one adding "as SomeClass" to doSomthing's declaration is agains the whole idea that you can send the class you want and don't have to know it when you write the code of doSomething.

    But also clearly if the type of "new SomeClass" is SomeClass, then it can't be the type of SomeClass as well.

  • Here's some real world example that I had some problem with. I think this is the only reason one of my apps has to be built with typecheck=2 (all my other apps now use typecheck=3):

    function onUpdate(dc as Dc) as Void {
        var statusIconId = isPaused ? Rez.Drawables.pause : Rez.Drawables.stop;
        var statusIcon = new statusIconId() as Drawable;
        statusIcon.draw(dc);
    }

    with typecheck=3 it gives an error:
    ERROR: vivoactive3: MyView.mc:122,3: Cannot determine type for creation. (the line of new statusIconId())

    See bug report: https://forums.garmin.com/developer/connect-iq/i/bug-reports/bug-drawable-variable-doesn-t-know-it-s-type-cannot-determine-type-for-creation

  • This code is incorrect. For one adding "as SomeClass" to doSomthing's declaration is agains the whole idea that you can send the class you want and don't have to know it when you write the code of doSomething.

    But also clearly if the type of "new SomeClass" is SomeClass, then it can't be the type of SomeClass as well.

    Yes, my entire point was that this type of code is incorrect, as it's not supported by the type checker. Certain things will "work" at runtime (like OP's example), but if you enable the type checker, you get inconsistent results and stuff doesn't work. See below.

    . For one adding "as SomeClass" to doSomthing's declaration is agains the whole idea that you can send the class you want and don't have to know it when you write the code of doSomething.

    Sigh, as usual I tried to say less to avoid annoying ppl with too many words, and now I have to end up saying more to clarify myself.

    It was meant to be an simple *proof-of-concept example*, not a practical example of how you would actually use it.

    To be clear, the following (hypothetical and incorrect) usages ALSO do not work.

    class DadClass {}
    class SomeClassA extends DadClass {
        function initialize() {
            DadClass.initialize();
        }
    }
    class SomeClassB extends DadClass {
        function initialize() {
            DadClass.initialize();
        }
    }
    
    class OtherClass {
        function startHere() {
            doSomething(SomeClassA);
        }
        function doSomething(myClass as SomeClassA or SomeClassB) {
            var c = new myClass(); // ERROR:  Cannot instantiate object of type 'PolyType<$.SomeClassA or $.SomeClassB>'
        }
        function doSomethingElse(myClass as DadClass) {
            var c = new myClass(); // ERROR:  Cannot instantiate object of type 'PolyType<$.DadClass>'
        }
    }

    Is that more realistic?

    But also clearly if the type of "new SomeClass" is SomeClass, then it can't be the type of SomeClass as well.

    Yes clearly. Tell that to the type checker, which erroneously suggests that the type of SomeClass is $.SomeClass, when you try to pass it into a function that takes Number:

    I had hoped this would be obvious from the screenshot before, but nope, here I am boring you with too many words again in an attempt to explain myself.

    If there's any doubt that the "X" in the error message "Invalid 'X' passed as parameter ... of type 'T"" apparently refers to a TYPE, take a look at the following similar error message (but with a string instead of SomeClass):

    Otoh if the first error message doesn't refer to the type of SomeClass, clearly this can only mean that there is no way to properly write the type of SomeClass in Monkey C. If the type checker doesn't know the type, then how are we supposed to know?

    But then again, if you assign a local variable x = SomeClass, the type checker considers x to be uninitialized (clearly untrue) and has no problem passing x into a function that takes Number:

    So clearly, the type checker is inconsistent about what it considers the "type" of a class name to be. I had hoped to make it clear that using a class name in this manner ACTUALLY DOES NOT WORK PROPERLY WITH THE TYPE CHECKER

    Is there an actual type that would work tho?

    Again my entire point is that the type checker doesn't work properly wrt to this case and that - as we all have noticed - there is no proper way to type this stuff in monkey c.

    Also, I agree that it makes no sense for the type of (uninstantiated) SomeClass to be SomeClass. But again:

    - the type checker suggests that *is* the type of SomeClass, in an error message about using the wrong type

    - the type checker doesn't complain when I pass in SomeClass as an argument that is typed as...SomeClass

    So the fact that my code is "incorrect" actually proves my point. It's incorrect bc there's no way to write the code "correctly".

    If you have a way to write correct code with this pattern that makes the type checker happy, I'd love to see it. Given that even the type checker doesn't agree with itself on what the type of an uninstantiated class is, I don't think anyone can write such code.

    (Again in one case it claims that the type of SomeClass is SomeClass. In another case if you assign "(local var) x = SomeClass" it claims x is uninitialized.)

  • TL;DR yes my code is incorrect, the whole point is there's no way to do this pattern (assigning a class name to a variable) "correctly" in monkey c that works properly and consistently with the type checker, even if it works at runtime. The fact that the type checker treats this pattern badly and inconsistently actually kinda suggests that it's not meant to be supported in the first place

    That's why I already filled out a bug report about this stuff. Again, once you assign a class name to a variable x, the type checker doesn't work with x anymore. But I already put that in the bug report, so I won't rehash it here.

  • Here's some real world example that I had some problem with.

    So here's where I'm gonna say *you* are missing the point.

    function onUpdate(dc as Dc) as Void {
        var statusIconId = isPaused ? Rez.Drawables.pause : Rez.Drawables.stop;
        var statusIcon = new statusIconId() as Drawable;
        statusIcon.draw(dc);
    }

    As with OP, you have discovered that assigning a class name to a variable actually does not work properly in Monkey C. Did you consider that the bug is not that your code should work at line 3 (new statusIconId), but instead that it should cause the compiler to return a hard error at line 2 ("var statusIconId = ...")?

    Iow the real problem could be that Monkey C allows you to write such code in the first place, not that code in question doesn't work properly.

    The point is that Monkey C has a certain pattern which seems to work only partially: assigning a class name to a variable. But who ever said that was supposed to work? I don't see any examples of it in Garmin samples or docs (as I tried to say before). As we have all noted, there doesn't seem to be a way to refer to the type of an uninstantiated class name (what the type checker would call a class definition), and the type checker does not treat code such as "x = SomeClass" consistently.

    AGAIN:

    class SomeClass {};
    function foo(y as Number) as Void {};
    //...
    foo(SomeClass); // type checker claims the type of SomeClass is $.SomeClass, as per error message about SomeClass not being a Number
    var x = SomeClass; // type checker claims the type of x is Uninitialized, when you hover over x

    If the type checker cannot reason properly about this pattern, it stands to reason Garmin "forgot" to support it because it's probably not meant to be in the language in the first place.

    I think the "Monkey C"-ish way of doing this is supposed to be:

    var statusIconId = isPaused ? :pause: :stop;
    var statusIcon = new Rez.Drawables[statusIconId]();

    Yes, I realize this still has problems:

    - produces warning:  Cannot determine if container access is using container type.

    - it's not type-safe at all

    If you really want type-safe code here, you could bite the bullet and write:

    function createDrawable(s as Symbol) as Drawable? {
        if (Rez.Drawables has s) {
            return new Rez.Drawables[s]();
        }
        return null;
    }
    //...
    var statusIconId = isPaused ? :pause : :stop;
    var statusIcon = createDrawable(statusIconId);

    You still get a warning but at least it's type safe (at run time).

    What I personally wouldn't do is try to force the compiler to work with code that is clearly does not have full language support and was probably not even meant to work in the first place. (I'm not telling you what to do though.)

  • Thanks for all the answers, it is a very interesting discussion!

    I use this feature only in one place in my app, but there it is quite useful. So far I did not see any adverse effect. Since it is used in a limited way, type-checking is not super-important. But it is good to know this is an undocumented feature. I'll keep an eye on it when updating the SDK.