Complete
over 3 years ago

Custom Exceptions are broken or mis-documented (Monkey C, SDK and/or documentation bug)

Hello,

I just wanted to write a custom exception and stumbled across this bug.

Starting with the documentation here: https://developer.garmin.com/connect-iq/monkey-c/exceptions-and-errors/#creatinganexception

Code example from there:

class AppSpecificException extends Lang.Exception {
    //! Constructor
    //! @param msg Message explaining cause
    function initalize(msg) {
        Exception.initialize();
        self.mMessage = msg;
    }
}

(great code formatting works neither here nor there. please fix that)
Problems with the above code:
  1. It misses the import for `Toybox.Lang`, so it does not work properly out of the box. Please fix.
  2. It triggers a build warning: `Class 'AppSpecificException' does not initialize its super class, 'Exception'`
    Which can neither be fixed by calling `Lang.Exception.initialize();` instead of `Exception.initialize();`.
    Please fix.
  3. When the exception is used with a message (`throw new AppSpecificException("foo");`) it triggers this error:
     
    Error: Too Many Arguments Error
    Details: Failed invoking <symbol>
    Stack: 
      - initalize() at AppSpecificException.mc:8 0x1000133b 

    This one only disappears if you initialize the exception without a message (`throw new Lang.AppSpecificException();`), which kinda defeats the purpose of it all.
    Please fix.
  4. If you just deleted the body of the custom exception in hope of the parent have the correct default behavior, the code goes completely rouge and shows the "Too many arguments" error with a backtrace, that starts right, but the triggering file for the error might be some code, that is not even called or included anywhere.

This also is true, if you do not extend `Toybox.Lang.Exception`, but an exception that already has multiple constructor arguments like `Toybox.Lang.UnexpectedTypeException`.
Also if you call `new Toybox.Lang.UnexpectedTypeException("ss")`, so with too less arguments, the same error is triggered, although there are not **too many**, but **less than needed** arguments given. This is just confusing. Please fix.

Also hard-coded user path in error messages? I'll talk to that rayburn guy ;)

Error: Not Enough Arguments Error
Details: Failed invoking <symbol>
Stack: 
  - initialize() at /Users/rayburn/projects/toolchain5/mbsimulator/submodules/technology/monkeybrains/virtual-machine/api/Lang.mb:1457 0x30002cc4

Another thing, that is besides the point, but also a problem: it is not possible to have exception factories, because `throw` seems to want to have a fresh `class definition`, with `new` and everything.

Example:

using Toybox.Lang;

class UnknownTypesException extends Lang.Exception {
    public static function create() {
    	return new self();
    }
}

throw UnknownTypesException.create();

So the TL;DR:

  1. Please update the documentation
  2. SDK does not trigger wrong warnings for parents of exception not being initialized
  3. Custom Exceptions can not have messages
  4. Compiler goes rouge if custom exception does not have an initializer
  5. (after the above) Please make exception factories a thing :)

If you need anything, please feel free to contact me :)

--- Update 1

Correct the error message in Number 2

Former Member
Former Member
  • Former Member
    Former Member over 4 years ago in reply to Travis.ConnectIQ

    > Many of the examples omit necessary using statements. They typically assume that the reader is familiar enough with the language to know they are necessary.

    Okay.

    > That code sample does not generate that warning; nothing uses the name UnknownTypeException.

    My bad. I copy&pasted the wrong message ^^
    The message was of course: `Class 'AppSpecificException' does not initialize its super class, 'Exception'`

    I updated the initial post. This is resolved though, because of the typo in the documentation.

    Thanks for pointing that out. Sometimes you are too blind to see stuff like that after hours xD

    > 4. Compiler goes rouge if custom exception does not have an initializer

    > I'd like to see a test case. I know that if you fail to initialize the base class properly you can encounter a system error, but I'd like to see what you're talking about.

    I attached a zip with an example. Notice how the stack trace show `source\Test\ClassThatIsNotUsedAnywhere.mc:3` as the source for the exception.

    This is mainly a confusing bug for the developers themselves.RougeException.zip

    > You can implement an exception factory. I'm not certain why you'd want to do such a thing, but you most definitely can do it.

    Could you give me a hint on how to that?

    The following does not work:

    using Toybox.Lang;
    
    class UnknownTypeException extends Lang.Exception {
    
        public function initialize(msg) {
            Lang.Exception.initialize();
    
    
            self.mMessage = msg;
        }
    
        public static function create() {
        	return new self("foo");
        }
    }

    Calling

    throw UnknownTypeException.create();

    Triggers this during automatic build:

    BUILD: ERROR: App.mc:23,36:  missing ';' at '('
    BUILD: ERROR: App.mc:23,37:  mismatched input ')' expecting {'{', '(', '[', ':', '+', '-', '~', '!', 'new', 'true', 'false', 'null', 'NaN', '$', IntNumber, HexNumber, OctalNumber, FloatNumber, Id, String, Char}
    

    and calling this:

    var e = UnknownTypeException.create();
    throw e;

    Triggers no error during automatic build, but causes this during the app run:

    Error: Unhandled Exception
    Exception: UnexpectedTypeException: Expected Class definition, given Object
    Stack: 
      - create() at UnknownTypeException.mc:13
      ...

    During writing this down, I found that the above code for the factory does not work, because I have used `self` instead of `UnknownTypeException` for the instantiation.

    So this would be the correct code:

    using Toybox.Lang;
    
    class UnknownTypeException extends Lang.Exception {
    
        public function initialize(msg) {
            Lang.Exception.initialize();
    
    
            self.mMessage = msg;
        }
    
        public static function create() {
        	return new UnknownTypeException("foo");
        }
    }

    This code works. (Yeah :D)

    I can life with the workaround of putting the exception in a variable before throwing it.

  • I'm pretty sure Travis has posted code in the past the does the checking with instanceOf to determine the object type.  You could just do something with an enum to return the type and not mess with exceptions, and have a "TypeUnknown" enum if you all through all the if() {} else if() {} conditions.

    I use instanceOf in a number of thing, but there it's to insure a type, or possibly follow a different code path based on the type.

    "If I see data is an InstanceOf Number in onBackgroundData, it's an error code, else it's real data"

  • Former Member
    Former Member over 4 years ago in reply to jim_m_58

    > I've been scratching my head all day trying to figure out what you want to do, and finally found the base code for what you are trying in the programmer's guide.  This is likely really old and updated).

    This is why I posted the link for the documentation I was referring to :D

    I wanted to give as much context as possible.

    If by "API Guide" you mean this site: https://developer.garmin.com/connect-iq/api-docs/Toybox/Lang/Exception.html

    and by

    > Notice the examples are blank.

    There is no actual javascript to toggle the code examples, then I am with you. I am having them both (Programmers guide and API Docs) open all the time.

    The problem with the hidden examples on the API docs page above is, that the `<code>` has a class `source_code` which has `display: none` by default.

    I am using the API docs that came with the SDK 3.2.2-2020-08-28 for Windows right now, because the bug is already fixed there (no toggles, just showing the examples).

    As for

    > What is it you want to do?

    I am potentially writing a Barrel, to determine the native type of a value (string, number etc.). As far as I saw there is a construct for `instanceof`, but not something like `typeof`(JS).
    For that I want to have an exception if the type can not be determined.
    The exception can be handle by the app developer who uses the Barrel so there is no crash or something.

    Please point me in the right direction, if I have missed something that might already exist.

    If you have further questions, please, let me know :)

  • The typographical error has been fixed, but it won't be published until the next time we push documentation.

  • > 1. It misses the import for `Toybox.Lang`, so it does not work properly out of the box. Please fix.

    Many of the examples omit necessary using statements. They typically assume that the reader is familiar enough with the language to know they are necessary.

    > 2. It triggers a build warning: `Unable to detect scope 'UnknownTypesException' for the symbol 'initialize'.`

    That code sample does not generate that warning; nothing uses the name UnknownTypeException.

    > 3. When the exception is used with a message (`throw new AppSpecificException("foo");`) it triggers this error:

    Yes, that is because there is a typographical error. In the code you copy/pasted, the function name is initalize, but it should be initialize.

    > 4. If you just deleted the body of the custom exception in hope of the parent have the correct default behavior, the code goes completely rouge

    I don't understand what you're doing, so I can't comment.

    > Also hard-coded user path in error messages? I'll talk to that rayburn guy ;)

    Full paths to source files are built into the debug information when the API is compiled. They could be stripped out.

    Also if you call `new Toybox.Lang.UnexpectedTypeException("ss")`, so with too less arguments, the same error is triggered, although there are not **too many**, but **less than needed** arguments given

    If you call new Toybox.Lang.UnexpectedTypeException("ss") you will get Error: Not Enough Arguments Error. This is entirely correct and expected. The documentation for UnexpectedTypeException says that the initialize method takes 3 parameters, and you are passing only 1.

    > 1. Please update the documentation

    I'll fix the typographical error that I mentioned in my response to #3 above.

    > 2. SDK does not trigger wrong warnings for parents of exception not being initialized

    Yes, it absolutely does.

    using Toybox.Application;
    using Toybox.WatchUi;
    using Toybox.Lang;
    using Toybox.System;
    
    class AppSpecificException extends Lang.Exception {
        function initialize(msg) {
            //Exception.initialize();
            mMessage = msg;
        }
    }
    
    class MyUnepxectedTypeException extends Lang.UnexpectedTypeException {
    	function initialize(msg) {
    		//UnexpectedTypeException.initialize(msg, null, null);
    	}
    }
    

    .. produces this output when compiled with warnings enabled:

    BUILD: WARNING: marqadventurer: /eclipse-workspace/Exceptions/source/ExceptionsApp.mc:7: \
      Class 'AppSpecificException' does not initialize its super class, 'Exception'
    BUILD: WARNING: marqadventurer: /eclipse-workspace/Exceptions/source/ExceptionsApp.mc:14: \
      Class 'MyUnepxectedTypeException' does not initialize its super class, 'UnexpectedTypeException'

    > 3. Custom Exceptions can not have messages

    Yes, they most certainly can. Just be sure to properly spell the word initialize and you'll be fine.

    > 4. Compiler goes rouge if custom exception does not have an initializer

    I'd like to see a test case. I know that if you fail to initialize the base class properly you can encounter a system error, but I'd like to see what you're talking about.

    > 5. Please make exception factories a thing :)

    You can implement an exception factory. I'm not certain why you'd want to do such a thing, but you most definitely can do it.