Weird Unexpected Type Error in MakeWebRequest

Hi guys,

I've been getting errors lately with makeImageRequest on one of my apps.  It seems super sporadic, and I don't understand why this error could be happening. I had over 100 errors in era for line 5 below, although I've never hit the problem in my personal testing. Any ideas?

The error occurs on the 5th line.

var myURL = url as String;
var myParams = params as Dictionary;
var myOptions = options as Dictionary;
var myResponse = self.method(:onResponse) as Method;
Communications.makeImageRequest(myURL,
myParams,
myOptions,
myResponse);
I added the type checking to this code so that it is impossible (in my opinion) to get an unexpected type error. But I still got an error! How in the world could this happen?
Thanks,
Lance
  • Which line are you getting the error on?  Remember, type checking is strictly a compile time thing and there is no type checking at runtime.

  • I added the type checking to this code so that it is impossible (in my opinion) to get an unexpected type error. But I still got an error! How in the world could this happen?

    Without seeing the rest of your code it's hard to say, but I'll just point out that it's by no means impossible to get an unexpected type error at runtime based on the code snippet you posted.

    First you should distinguish between the different kinds of type checking in Monkey C:

    - compile-time type checking using Monkey Types and the as keyword. Note that it's very easy to lie to the compiler about the type of a variable at compile-time by using a type cast, which could lead to a runtime error (see below)

    - runtime type checking, based on the *actual* runtime type (i.e. the type associated with the instanceof keyword). An Unexpected Type Error signifies that the runtime type is wrong and is not directly related to any compile-time type checking

    The problem with using the as keyword as above (in a type cast) is that it simply asserts that url, params and options are the types you say they are. It doesn't actually *enforce* that.

    A simple example of how your code could crash, with some additional code of my own:

        var url = null; // uh oh
        // The compiler allows this type cast without warning, which IMO
        // is a bad feature of the Monkey C compiler (*).
        // So the compiler thinks myURL is a string,
        // but the actual runtime type/value is null,
        // which should lead to a crash when makeImageRequest()
        // is called
        var myURL = url as String;
        
        var myParams = params as Dictionary;
        var myOptions = options as Dictionary;
        var myResponse = self.method(:onResponse) as Method;
        Communications.makeImageRequest(myURL,
        myParams,
        myOptions,
        myResponse);

    (* Contrast with typescript which does not allow explicit type casts between incompatible types without first casting to any).

    In this case, the use of url as String (AKA "type cast") actually removes type safety for myURL at compile time, because you've lost information about the actual type of url, by asserting that it is a String. Same goes for myParams, myOptions and, to a lesser extent, myResponse.

    In general I would avoid type casting unless absolutely necessary (unfortunately, due to some quirks in the compiler, type casting is necessary in some cases.) To actually achieve type safety:

    - The types of function arguments and return values should be declared

    - The types of globals and method variables should be declared (and care should be taken so that initial values aren't inappropriately type cast)

    - Type casts should be avoided where possible -- let the compiler infer types as necessary

    For example, if the following code doesn't compile with compile-time type-checking enabled, then that should give you additional information about whether one of your arguments isn't guaranteed to be the type that you think it is:

    Communications.makeImageRequest(
        url,
      params,
      options,
      myResponse
    );

    It would help to see more context around the code, though, especially the signature of the containing function.

  • Thanks to both of you! I had no idea that the ‘as’ statements were only enforced in the compiler. (Type checking is fairly new to me).  I’m away from home, but can hopefully post more code soon. 

    Thanks again. 

  • Thanks to both of you! I had no idea that the ‘as’ statements were only enforced in the compiler. (Type checking is fairly new to me).  

    No problem! If you have the time, you should check out TypeScript to see where the inspiration for some of this stuff comes from. TypeScript also takes a duck-typed language (JavaScript) and adds a layer of type checking at compile time.

    Unfortunately Monkey C uses the as keyword for both type casts and type declarations, which can be confusing. But TL;DR: use type declarations wherever possible (they will be mandatory for certain levels of type checking) and use type casts as sparingly as possible.

    Also note that Monkey C forbids type declarations for local variables, but that doesn't mean you should automatically type cast whatever value you assign to a local variable, because then you end up with the kind of problem described above.

  • Alright, so here's more of my code, in the order that it's executed.

    var options = {                                        
        :palette => [
       		Graphics.COLOR_BLACK,
       		Graphics.COLOR_WHITE,
       		Graphics.COLOR_LT_GRAY,	
        ],
        :dithering => Communications.IMAGE_DITHERING_NONE  
    };
     
     var params = {
        "lat"=> lat,
        "lon"=> lon,
        "zoom"=>zoom
        //etc.
    };
    
     var context = {
        "lat"=>lat,
        "lon"=>lon
        // etc
    };
        
     var url = "https://myurl";
     new makeRequest2().makeImageRequest(
            url, 
            params, 
            options, 
            new Lang.Method($, :onReceiveMapImage),
            context);

    class makeRequest2 {
        hidden var mCallback;
        hidden var mContext;
    
    
        // other functions are in here, but are irrelevant 
        
        function makeImageRequest(url, params, options, callback, context) {
            mCallback = callback;
            mContext = context;
            var myURL = url as String;
            var myParams = params as Dictionary;
            var myOptions = options as Dictionary;
            var myResponse =  self.method(:onResponse) as Method;
            Communications.makeImageRequest(myURL,  // error on this line
                                            myParams, 
                                            myOptions, 
                                            myResponse);
        }
        
        function onResponse(code, data) {
            mCallback.invoke(code, data, mContext);
            mCallback = null;
            mContext = null;
            
        }
    }

    makeRequest2 simply passes a context object to the callback.

    I can't figure out where I'm getting the error from though.

    Thanks,

    Lance

  • I can't figure out where I'm getting the error from though.

    I'm not sure either, although, as discussed earlier, the correct way to do type checking here is to declare the types of function arguments/return values and member variables/globals. (If you use informative (2) or strict type (3) checking, failing to do so will either produce a warning or an error.)

    e.g. Something like this:

    class makeRequest2 {
        hidden var mCallback as Method;
        hidden var mContext as Dictionary<String, Float>;
    
        function makeImageRequest(
            url as String,
            params as Dictionary,
            options as Dictionary,
            callback as Method,
            context as Dictionary<String, Float>
        ) as Void {
            mCallback = callback;
            mContext = context;
            Communications.makeImageRequest(
                url,
                params, 
                options, 
                self.method(:onResponse));
        }

    It would probably be best to change your type check level to at least gradual (1), if you haven't done so.

    Either way, it's doubtful the compile-time type checking is going to help you here (imo although I could be wrong), given the nature of the error and the code you posted.