Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Incorrect inference of type when using promise.then(...) #13117

Closed
awalgarg opened this issue Dec 22, 2016 · 0 comments · Fixed by #13487
Closed

Incorrect inference of type when using promise.then(...) #13117

awalgarg opened this issue Dec 22, 2016 · 0 comments · Fixed by #13487
Assignees
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue

Comments

@awalgarg
Copy link

awalgarg commented Dec 22, 2016

TypeScript Version: 2.1.1 / nightly (2.2.0-dev.201xxxxx)

Code

interface A { m(): Promise<any> }
interface B { m(): Promise<number> }
function a(o: A): Promise<number> {
    return Promise.resolve()
        .then(_ => o.m());
}
function adash(o: A): Promise<number> {
    return o.m();
}
function b(o: B): Promise<number> {
    return Promise.resolve()
        .then(_ => o.m());
}

Expected behavior:
Should compile.

Actual behavior:
function a doesn't compile. The return type of the return statement is inferred as Promise<void>, while it should be inferred as Promise<any>, which is compatible with the intended return type (Promise<number>) of the parent function (a), just like type is correctly inferred as Promise<number> in the function b.

The offending part here is the library definitions for the Promise interface, at https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es2015.promise.d.ts#L31. The linked definition of then is matched on the result of Promise.resolve(), which is a Promise<void>, and the return type is inferred as Promise<void> only. Apparently order matters, and the correct matching for that expression should be https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es2015.promise.d.ts#L47 instead, so the return type is inferred as Promise<any> instead.

Simply re-ordering those definitions of .then solves the issue. But I am not sure what the further implications are.

diff --git a/src/lib/es2015.promise.d.ts b/src/lib/es2015.promise.d.ts
index e27da2c..63fef0f 100644
--- a/src/lib/es2015.promise.d.ts
+++ b/src/lib/es2015.promise.d.ts
@@ -8,7 +8,7 @@ interface Promise<T> {
      * @param onrejected The callback to execute when the Promise is rejected.
      * @returns A Promise for the completion of which ever callback is executed.
      */
-    then(onfulfilled?: ((value: T) => T | PromiseLike<T>) | undefined | null, onrejected?: ((reason: any) => T | PromiseLike<T>) | undefined | null): Promise<T>;
+    then<TResult>(onfulfilled: ((value: T) => T | PromiseLike<T>) | undefined | null, onrejected: (reason: any) => TResult | PromiseLike<TResult>): Promise<T | TResult>;
 
     /**
      * Attaches callbacks for the resolution and/or rejection of the Promise.
@@ -16,7 +16,7 @@ interface Promise<T> {
      * @param onrejected The callback to execute when the Promise is rejected.
      * @returns A Promise for the completion of which ever callback is executed.
      */
-    then<TResult>(onfulfilled: ((value: T) => T | PromiseLike<T>) | undefined | null, onrejected: (reason: any) => TResult | PromiseLike<TResult>): Promise<T | TResult>;
+    then<TResult>(onfulfilled: (value: T) => TResult | PromiseLike<TResult>, onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<TResult>;
 
     /**
      * Attaches callbacks for the resolution and/or rejection of the Promise.
@@ -24,7 +24,7 @@ interface Promise<T> {
      * @param onrejected The callback to execute when the Promise is rejected.
      * @returns A Promise for the completion of which ever callback is executed.
      */
-    then<TResult>(onfulfilled: (value: T) => TResult | PromiseLike<TResult>, onrejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null): Promise<TResult>;
+    then<TResult1, TResult2>(onfulfilled: (value: T) => TResult1 | PromiseLike<TResult1>, onrejected: (reason: any) => TResult2 | PromiseLike<TResult2>): Promise<TResult1 | TResult2>;
 
     /**
      * Attaches callbacks for the resolution and/or rejection of the Promise.
@@ -32,7 +32,7 @@ interface Promise<T> {
      * @param onrejected The callback to execute when the Promise is rejected.
      * @returns A Promise for the completion of which ever callback is executed.
      */
-    then<TResult1, TResult2>(onfulfilled: (value: T) => TResult1 | PromiseLike<TResult1>, onrejected: (reason: any) => TResult2 | PromiseLike<TResult2>): Promise<TResult1 | TResult2>;
+    then(onfulfilled?: ((value: T) => T | PromiseLike<T>) | undefined | null, onrejected?: ((reason: any) => T | PromiseLike<T>) | undefined | null): Promise<T>;
 
     /**
      * Attaches a callback for only the rejection of the Promise.
@@ -251,4 +251,4 @@ interface PromiseConstructor {
     resolve(): Promise<void>;
 }
@mhegazy mhegazy added this to the TypeScript 2.2 milestone Dec 22, 2016
@mhegazy mhegazy added the Bug A bug in TypeScript label Dec 22, 2016
@mhegazy mhegazy added the Fixed A PR has been merged for this issue label Feb 15, 2017
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
# for free to subscribe to this conversation on GitHub. Already have an account? #.
Labels
Bug A bug in TypeScript Fixed A PR has been merged for this issue
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants