Merge pull request #13905 from noomorph/master

[angular] enhancement: stricter and more precise type inference in ng.IPromise<T> and ng.IQService
This commit is contained in:
Bowden Kelly
2017-02-03 18:17:36 -08:00
committed by GitHub
2 changed files with 149 additions and 48 deletions
+135 -43
View File
@@ -271,18 +271,28 @@ angular.module('qprovider-test', [])
let foo: ng.IPromise<number>;
foo.then((x) => {
// x is inferred to be a number
x.toFixed();
return 'asdf';
}).then((x) => {
// x is inferred to be string
const len = x.length;
return 123;
}, (e) => {
return anyOf2([123], toPromise([123])); // IPromise<T> | T, both are good for the 2nd arg of .then()
}).then((x) => {
// x is infered to be a number
const fixed = x.toFixed();
// x is infered to be a number or number[]
if (Array.isArray(x)) {
x[0].toFixed();
} else {
x.toFixed();
}
return;
}).then((x) => {
// x is infered to be void
// Typescript will prevent you to actually use x as a local variable
}).catch(e => {
return foo || 123; // IPromise<T> | T, both are good for .catch()
}).then(x => {
// x is infered to be void | number
x && x.toFixed();
// Typescript will prevent you to actually use x as a local variable before you check it is not void
// Try object:
return { a: 123 };
}).then((x) => {
@@ -290,7 +300,8 @@ foo.then((x) => {
x.a = 123;
//Try a promise
var y: ng.IPromise<number>;
return y;
var condition: boolean;
return condition ? y : x.a; // IPromise<T> | T, both are good for the 1st arg of .then()
}).then((x) => {
// x is infered to be a number, which is the resolved value of a promise
x.toFixed();
@@ -307,14 +318,22 @@ namespace TestQ {
e: number;
f: boolean;
}
interface TOther {
g: string;
h: number;
}
var tResult: TResult;
var promiseTResult: angular.IPromise<TResult>;
var tValue: TValue;
var promiseTValue: angular.IPromise<TValue>;
var tOther: TOther;
var promiseTOther: angular.IPromise<TOther>;
var $q: angular.IQService;
var promiseAny: angular.IPromise<any>;
const assertPromiseType = <T>(arg: angular.IPromise<T>) => arg;
// $q constructor
{
let result: angular.IPromise<TResult>;
@@ -349,13 +368,20 @@ namespace TestQ {
{
let result: angular.IDeferred<TResult>;
result = $q.defer<TResult>();
result.resolve(tResult);
var anyValue: any;
result.reject(anyValue);
result.promise.then(result => {
return $q.resolve<TResult>(result);
});
}
// $q.reject
{
let result: angular.IPromise<any>;
let result: angular.IPromise<never>;
result = $q.reject();
result = $q.reject('');
result.catch(() => 5).then(x => x.toFixed());
}
// $q.resolve
@@ -367,6 +393,8 @@ namespace TestQ {
let result: angular.IPromise<TResult>;
result = $q.resolve<TResult>(tResult);
result = $q.resolve<TResult>(promiseTResult);
result = $q.resolve<TResult | TOther>(Math.random() > 0.5 ? tResult : promiseTOther);
result = $q.resolve(Math.random() > 0.5 ? tResult : promiseTOther);
}
// $q.when
@@ -376,6 +404,8 @@ namespace TestQ {
}
{
let result: angular.IPromise<TResult>;
let resultOther: angular.IPromise<TOther>;
result = $q.when<TResult>(tResult);
result = $q.when<TResult>(promiseTResult);
@@ -384,16 +414,20 @@ namespace TestQ {
result = $q.when<TResult, TValue>(tValue, (result: TValue) => tResult, (any) => any, (any) => any);
result = $q.when<TResult, TValue>(promiseTValue, (result: TValue) => tResult);
result = $q.when<TResult, TValue>(promiseTValue, (result: TValue) => tResult, (any) => any);
result = $q.when<TResult, TValue>(promiseTValue, (result: TValue) => tResult, (any) => any, (any) => any);
result = resultOther = $q.when<TResult, TOther, TValue>(promiseTValue, (result: TValue) => tResult, (any) => tOther);
result = resultOther = $q.when<TResult, TOther, TValue>(promiseTValue, (result: TValue) => tResult, (any) => tOther, (any) => any);
result = resultOther = $q.when<TResult, TOther, TValue>(promiseTValue, (result: TValue) => tResult, (any) => promiseTOther);
result = resultOther = $q.when<TResult, TOther, TValue>(promiseTValue, (result: TValue) => tResult, (any) => promiseTOther, (any) => any);
result = $q.when<TResult, TValue>(tValue, (result: TValue) => promiseTResult);
result = $q.when<TResult, TValue>(tValue, (result: TValue) => promiseTResult, (any) => any);
result = $q.when<TResult, TValue>(tValue, (result: TValue) => promiseTResult, (any) => any, (any) => any);
result = $q.when<TResult, TValue>(promiseTValue, (result: TValue) => promiseTResult);
result = $q.when<TResult, TValue>(promiseTValue, (result: TValue) => promiseTResult, (any) => any);
result = $q.when<TResult, TValue>(promiseTValue, (result: TValue) => promiseTResult, (any) => any, (any) => any);
result = resultOther = $q.when<TResult, TOther, TValue>(promiseTValue, (result: TValue) => promiseTResult, (any) => tOther);
result = resultOther = $q.when<TResult, TOther, TValue>(promiseTValue, (result: TValue) => promiseTResult, (any) => tOther, (any) => any);
result = resultOther = $q.when<TResult, TOther, TValue>(promiseTValue, (result: TValue) => promiseTResult, (any) => promiseTOther);
result = resultOther = $q.when<TResult, TOther, TValue>(promiseTValue, (result: TValue) => promiseTResult, (any) => promiseTOther, (any) => any);
}
}
@@ -466,20 +500,26 @@ namespace TestInjector {
// Promise signature tests
namespace TestPromise {
let result: any;
var any: any;
interface TResult {
kind: 'result';
a: number;
b: string;
c: boolean;
}
interface TOther {
kind: 'other';
d: number;
e: string;
f: boolean;
}
function isTResult(x: TResult | TOther): x is TResult {
return x.kind === 'result';
}
var tresult: TResult;
var tresultPromise: ng.IPromise<TResult>;
var tresultHttpPromise: ng.IHttpPromise<TResult>;
@@ -489,45 +529,83 @@ namespace TestPromise {
var totherHttpPromise: ng.IHttpPromise<TOther>;
var promise: angular.IPromise<TResult>;
var $q: angular.IQService;
const assertPromiseType = <T>(arg: angular.IPromise<T>) => arg;
const reject = $q.reject();
// promise.then
result = promise.then((result) => any) as angular.IPromise<any>;
result = promise.then((result) => any, (any) => any) as angular.IPromise<any>;
result = promise.then((result) => any, (any) => any, (any) => any) as angular.IPromise<any>;
assertPromiseType<any>(promise.then((result) => any));
assertPromiseType<any>(promise.then((result) => any, (any) => any));
assertPromiseType<any>(promise.then((result) => any, (any) => any, (any) => any));
result = promise.then((result) => result) as angular.IPromise<TResult>;
result = promise.then((result) => result, (any) => any) as angular.IPromise<TResult>;
result = promise.then((result) => result, (any) => any, (any) => any) as angular.IPromise<TResult>;
result = promise.then((result) => tresultPromise) as angular.IPromise<TResult>;
result = promise.then((result) => tresultPromise, (any) => any) as angular.IPromise<TResult>;
result = promise.then((result) => tresultPromise, (any) => any, (any) => any) as angular.IPromise<TResult>;
result = promise.then((result) => tresultHttpPromise) as angular.IPromise<ng.IHttpPromiseCallbackArg<TResult>>;
result = promise.then((result) => tresultHttpPromise, (any) => any) as angular.IPromise<ng.IHttpPromiseCallbackArg<TResult>>;
result = promise.then((result) => tresultHttpPromise, (any) => any, (any) => any) as angular.IPromise<ng.IHttpPromiseCallbackArg<TResult>>;
assertPromiseType<never>(promise.then((result) => reject));
assertPromiseType<never>(promise.then((result) => reject, (any) => reject));
assertPromiseType<never>(promise.then((result) => reject, (any) => reject, (any) => any));
result = promise.then((result) => tother) as angular.IPromise<TOther>;
result = promise.then((result) => tother, (any) => any) as angular.IPromise<TOther>;
result = promise.then((result) => tother, (any) => any, (any) => any) as angular.IPromise<TOther>;
result = promise.then((result) => totherPromise) as angular.IPromise<TOther>;
result = promise.then((result) => totherPromise, (any) => any) as angular.IPromise<TOther>;
result = promise.then((result) => totherPromise, (any) => any, (any) => any) as angular.IPromise<TOther>;
result = promise.then((result) => totherHttpPromise) as angular.IPromise<ng.IHttpPromiseCallbackArg<TOther>>;
result = promise.then((result) => totherHttpPromise, (any) => any) as angular.IPromise<ng.IHttpPromiseCallbackArg<TOther>>;
result = promise.then((result) => totherHttpPromise, (any) => any, (any) => any) as angular.IPromise<ng.IHttpPromiseCallbackArg<TOther>>;
assertPromiseType<TResult>(promise.then((result) => result));
assertPromiseType<TResult>(promise.then((result) => tresult));
assertPromiseType<TResult>(promise.then((result) => tresultPromise));
assertPromiseType<TResult>(promise.then((result) => result, (any) => any));
assertPromiseType<TResult>(promise.then((result) => result, (any) => any, (any) => any));
assertPromiseType<TResult>(promise.then((result) => result, (any) => reject, (any) => any));
assertPromiseType<TResult>(promise.then((result) => anyOf2(reject, result)));
assertPromiseType<TResult>(promise.then((result) => anyOf3(result, tresultPromise, reject)));
assertPromiseType<TResult>(promise.then(
(result) => anyOf3(reject, result, tresultPromise),
(reason) => anyOf3(reject, tresult, tresultPromise)
));
assertPromiseType<ng.IHttpPromiseCallbackArg<TResult>>(promise.then((result) => tresultHttpPromise));
assertPromiseType<TResult | TOther>(promise.then((result) => result, (any) => tother));
assertPromiseType<TResult | TOther>(promise.then(
(result) => anyOf3(reject, result, totherPromise),
(reason) => anyOf3(reject, tother, tresultPromise)
));
assertPromiseType<TResult | TOther>(promise.then<TResult | TOther, TResult>(
(result) => anyOf3(tresultPromise, result, totherPromise)
));
assertPromiseType<TResult | TOther>(promise.then((result) => result, (any) => tother, (any) => any));
assertPromiseType<TResult | TOther>(promise.then((result) => tresultPromise, (any) => totherPromise));
assertPromiseType<TResult | TOther>(promise.then((result) => tresultPromise, (any) => totherPromise, (any) => any));
assertPromiseType<ng.IHttpPromiseCallbackArg<TResult | TOther>>(promise.then((result) => tresultHttpPromise, (any) => totherHttpPromise));
assertPromiseType<ng.IHttpPromiseCallbackArg<TResult | TOther>>(promise.then((result) => tresultHttpPromise, (any) => totherHttpPromise, (any) => any));
assertPromiseType<TOther>(promise.then((result) => tother));
assertPromiseType<TOther>(promise.then((result) => tother, (any) => any));
assertPromiseType<TOther>(promise.then((result) => tother, (any) => any, (any) => any));
assertPromiseType<TOther>(promise.then((result) => totherPromise));
assertPromiseType<TOther>(promise.then((result) => totherPromise, (any) => any));
assertPromiseType<TOther>(promise.then((result) => totherPromise, (any) => any, (any) => any));
assertPromiseType<ng.IHttpPromiseCallbackArg<TOther>>(promise.then((result) => totherHttpPromise));
assertPromiseType<ng.IHttpPromiseCallbackArg<TOther>>(promise.then((result) => totherHttpPromise, (any) => any));
assertPromiseType<ng.IHttpPromiseCallbackArg<TOther>>(promise.then((result) => totherHttpPromise, (any) => any, (any) => any));
assertPromiseType<boolean>(promise.then((result) => tresult, (any) => tother).then(ambiguous => isTResult(ambiguous) ? ambiguous.c : ambiguous.f));
// promise.catch
result = promise.catch((err) => any) as angular.IPromise<any>;
result = promise.catch((err) => tresult) as angular.IPromise<TResult>;
result = promise.catch((err) => tresultPromise) as angular.IPromise<TResult>;
result = promise.catch((err) => tresultHttpPromise) as angular.IPromise<ng.IHttpPromiseCallbackArg<TResult>>;
result = promise.catch((err) => tother) as angular.IPromise<TOther>;
result = promise.catch((err) => totherPromise) as angular.IPromise<TOther>;
result = promise.catch((err) => totherHttpPromise) as angular.IPromise<ng.IHttpPromiseCallbackArg<TOther>>;
assertPromiseType<any>(promise.catch((err) => err));
assertPromiseType<any>(promise.catch((err) => any));
assertPromiseType<TResult>(promise.catch((err) => tresult));
assertPromiseType<TResult>(promise.catch((err) => anyOf2(tresult, reject)));
assertPromiseType<TResult>(promise.catch((err) => anyOf3(tresult, tresultPromise, reject)));
assertPromiseType<TResult>(promise.catch((err) => tresultPromise));
assertPromiseType<ng.IHttpPromiseCallbackArg<TResult>>(promise.catch((err) => tresultHttpPromise));
assertPromiseType<TResult | TOther>(promise.catch((err) => tother));
assertPromiseType<TResult | TOther>(promise.catch((err) => totherPromise));
assertPromiseType<TResult | ng.IHttpPromiseCallbackArg<TOther>>(promise.catch((err) => totherHttpPromise));
assertPromiseType<boolean>(promise.catch((err) => tother).then(ambiguous => isTResult(ambiguous) ? ambiguous.c : ambiguous.f));
// promise.finally
result = promise.finally(() => any) as angular.IPromise<TResult>;
result = promise.finally(() => tresult) as angular.IPromise<TResult>;
result = promise.finally(() => tother) as angular.IPromise<TResult>;
assertPromiseType<TResult>(promise.finally(() => any));
assertPromiseType<TResult>(promise.finally(() => tresult));
assertPromiseType<TResult>(promise.finally(() => tother));
}
function test_angular_forEach() {
@@ -1212,3 +1290,17 @@ function testIHttpParamSerializerJQLikeProvider() {
a: 'b'
});
}
function anyOf2<T1, T2>(v1: T1, v2: T2) {
return Math.random() < 1/2 ? v1 : v2;
}
function anyOf3<T1, T2, T3>(v1: T1, v2: T2, v3: T3) {
const rnd = Math.random();
return rnd < 1/3 ? v1 : rnd < 2/3 ? v2 : v3;
}
function toPromise<T>(val: T): ng.IPromise<T> {
var p: ng.IPromise<T>;
return p;
}
+14 -5
View File
@@ -1044,13 +1044,14 @@ declare namespace angular {
*
* @param reason Constant, message, exception or an object representing the rejection reason.
*/
reject(reason?: any): IPromise<any>;
reject(reason?: any): IPromise<never>;
/**
* Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
*
* @param value Value or a promise
*/
resolve<T>(value: IPromise<T>|T): IPromise<T>;
resolve<T1, T2>(value: IPromise<T1>|T2): IPromise<T1|T2>;
/**
* Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
*/
@@ -1061,7 +1062,10 @@ declare namespace angular {
* @param value Value or a promise
*/
when<T>(value: IPromise<T>|T): IPromise<T>;
when<TResult, T>(value: IPromise<T>|T, successCallback: (promiseValue: T) => IPromise<TResult>|TResult, errorCallback?: (reason: any) => any, notifyCallback?: (state: any) => any): IPromise<TResult>;
when<T1, T2>(value: IPromise<T1>|T2): IPromise<T1|T2>;
when<TResult, T>(value: IPromise<T>|T, successCallback: (promiseValue: T) => IPromise<TResult>|TResult): IPromise<TResult>;
when<TResult, T>(value: T, successCallback: (promiseValue: T) => IPromise<TResult>|TResult, errorCallback: null | undefined | ((reason: any) => any), notifyCallback?: (state: any) => any): IPromise<TResult>;
when<TResult, TResult2, T>(value: IPromise<T>, successCallback: (promiseValue: T) => IPromise<TResult>|TResult, errorCallback: (reason: any) => TResult2 | IPromise<TResult2>, notifyCallback?: (state: any) => any): IPromise<TResult | TResult2>;
/**
* Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
*/
@@ -1090,15 +1094,20 @@ declare namespace angular {
interface IPromise<T> {
/**
* Regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available. The callbacks are called with a single argument: the result or rejection reason. Additionally, the notify callback may be called zero or more times to provide a progress indication, before the promise is resolved or rejected.
* The successCallBack may return IPromise<void> for when a $q.reject() needs to be returned
* The successCallBack may return IPromise<never> for when a $q.reject() needs to be returned
* This method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback. It also notifies via the return value of the notifyCallback method. The promise can not be resolved or rejected from the notifyCallback method.
*/
then<TResult>(successCallback: (promiseValue: T) => IPromise<TResult>|TResult, errorCallback?: (reason: any) => any, notifyCallback?: (state: any) => any): IPromise<TResult>;
then<TResult>(successCallback: (promiseValue: T) => IPromise<TResult>|TResult, errorCallback?: null | undefined, notifyCallback?: (state: any) => any): IPromise<TResult>;
then<TResult1, TResult2>(successCallback: (promiseValue: T) => IPromise<TResult1>|TResult2, errorCallback?: null | undefined, notifyCallback?: (state: any) => any): IPromise<TResult1 | TResult2>;
then<TResult, TCatch>(successCallback: (promiseValue: T) => IPromise<TResult>|TResult, errorCallback: (reason: any) => IPromise<TCatch>|TCatch, notifyCallback?: (state: any) => any): IPromise<TResult | TCatch>;
then<TResult1, TResult2, TCatch1, TCatch2>(successCallback: (promiseValue: T) => IPromise<TResult1>|TResult2, errorCallback: (reason: any) => IPromise<TCatch1>|TCatch2, notifyCallback?: (state: any) => any): IPromise<TResult1 | TResult2 | TCatch1 | TCatch2>;
/**
* Shorthand for promise.then(null, errorCallback)
*/
catch<TResult>(onRejected: (reason: any) => IPromise<TResult>|TResult): IPromise<TResult>;
catch<TCatch>(onRejected: (reason: any) => IPromise<TCatch>|TCatch): IPromise<T | TCatch>;
catch<TCatch1, TCatch2>(onRejected: (reason: any) => IPromise<TCatch1>|TCatch2): IPromise<T | TCatch1 | TCatch2>;
/**
* Allows you to observe either the fulfillment or rejection of a promise, but to do so without modifying the final value. This is useful to release resources or do some clean-up that needs to be done whether the promise was rejected or resolved. See the full specification for more information.