Overload resolution issue

Topics: General
Jul 9, 2013 at 6:12 PM
Edited Jul 9, 2013 at 6:14 PM
There seems to be an overload/type inference issue with the typings for the JQueryPromise in the definition file at DefinitelyTyped. The typing of the then method of the JQueryPromise contains overloads whereby a promise gets returned from the callback functions and it is completed and unwrapped before passing to any further continuation.

These are the four relevant overloads:
interface JQueryPromise<T> {      
    then<U>(onFulfill: (value: T) => U, onReject?: (...reasons) => U, onProgress?: (...progression) => any): JQueryPromise<U>;    

    then<U>(onFulfill: (value: T) => JQueryGenericPromise<U>, onReject?: (...reasons) => U, onProgress?: (...progression) => any): JQueryPromise<U>;  
    
    then<U>(onFulfill: (value: T) => U, onReject?: (...reasons) => JQueryGenericPromise<U>, onProgress?: (...progression) => any): JQueryPromise<U>;

    then<U>(onFulfill: (value: T) => JQueryGenericPromise<U>, onReject?: (...reasons) => JQueryGenericPromise<U>, onProgress?: (...progression) => any): JQueryPromise<U>;
}
The second and the fourth are the ones I'm interested in.

Consider this example:
function example() {
    $.Deferred(dfd => { dfd.resolve(5); })
        .then(result => {
            return addTwelve(result);
        })
        .done(result => alert(result));  //Prints 12 after 3 seconds
}

function addTwelve(num: number): JQueryDeferred<number> {
    return $.Deferred(dfd => {
        setTimeout(() => {
            dfd.resolve(num + 12);

        }, 3000);
    });
}

example();
You would expect the return type of then to be JQueryPromise<number>, however, the first overload is chosen instead and the return type is inferred as JQueryPromise<JQueryDeferred<number>>.

If you rearrange the overload list so definition 2 and 4 are on top, and insert a cast, this issue goes away:
interface JQueryPromise<T>{
    then<U>(onFulfill: (value: T) => JQueryGenericPromise<U>, onReject?: (...reasons) => U, onProgress?: (...progression) => any): JQueryPromise<U>;    

    then<U>(onFulfill: (value: T) => U, onReject?: (...reasons) => U, onProgress?: (...progression) => any): JQueryPromise<U>;    

    then<U>(onFulfill: (value: T) => JQueryGenericPromise<U>, onReject?: (...reasons) => JQueryGenericPromise<U>, onProgress?: (...progression) => any): JQueryPromise<U>;    

    then<U>(onFulfill: (value: T) => U, onReject?: (...reasons) => JQueryGenericPromise<U>, onProgress?: (...progression) => any): JQueryPromise<U>;
}

function example() {
    $.Deferred(dfd => { dfd.resolve(5); })
        .then(result => {
            return <JQueryGenericPromise<number>> addTwelve(result);
        })
        .done(result => alert(result));  //Prints 12 after 3 seconds
}

function addTwelve(num: number): JQueryDeferred<number> {
    return $.Deferred(dfd => {
        setTimeout(() => {
            dfd.resolve(num + 12);
        
        }, 3000);
    });
}

example();
Now then is correctly inferred as returning JQueryPromise<number>.

Two questions:
  • Overload resolution seems to require that more specific overloads are defined first. Is this intentional? It seems like it shouldn't matter.
  • Even when defined first, overload resolution is not preferring the more specific compatible type to the more general type. Perhaps there's a counterargument, but this seems like an issue that should be addressed. Trying to call a complex overload like this shouldn't require knowledge of the definition file and generic overload resolution in order to know the right cast to get it to work.
Jul 9, 2013 at 8:44 PM
Oh, THAT fixed it? I wrote these originally after typing Q. When I was initially trying to type promises in v0.9 I was getting all sorts of nasty errors and assumed I could only define them in the order of most generic to most specific. v0.9.0.1 fixed a whole bunch of stuff and I never tried re-arranging them. You should submit a pull request if you get the chance.
Jul 9, 2013 at 9:07 PM
The fact that you needed to cast seems odd... JQueryDeferred implements the JQueryGenericPromise interface implicitly, and so shouldn't the compiler know to use the JQueryGenericPromise version?
Coordinator
Jul 10, 2013 at 3:08 PM
Yes, the ones you want to have preference go first. We went back and forth on this one, and it just became easier to work out (and explain to other people) function resolution rules if we had a final "in the case of ambiguity, the first one wins" rather than coming up with an ordering.

For the second point, it sounds like a bug if the first one matches but still isn't chosen.