Skip to content

Commit

Permalink
[js] fix type inference for Promise callback arguments (closes #6790)
Browse files Browse the repository at this point in the history
  • Loading branch information
nadako committed Nov 26, 2018
1 parent 26f9041 commit 4dc5fcc
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 25 deletions.
55 changes: 30 additions & 25 deletions std/js/Promise.hx
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,21 @@ package js;
import haxe.extern.EitherType;

/**
The Promise object represents the eventual completion (or failure) of an
The Promise object represents the eventual completion (or failure) of an
asynchronous operation and its resulting value.
Documentation [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) by [Mozilla Contributors](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise$history), licensed under [CC-BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/).
**/
@:native("Promise")
extern class Promise<T>
{
/**
Returns a Promise object that is resolved with the given value. If the
value is Thenable, the returned promise will "follow" that
thenable, adopting its eventual state;
otherwise the returned promise will be fulfilled with the value.
Generally, when it's unknown when value is a promise or not,
use `Promise.resolve(value)` instead and work with the return value as
Returns a Promise object that is resolved with the given value. If the
value is Thenable, the returned promise will "follow" that
thenable, adopting its eventual state;
otherwise the returned promise will be fulfilled with the value.
Generally, when it's unknown when value is a promise or not,
use `Promise.resolve(value)` instead and work with the return value as
a promise.
**/
@:overload(function<T>(promise : Promise<T>) : Promise<T> {})
Expand All @@ -52,20 +52,20 @@ extern class Promise<T>
static function reject<T>( ?reason : Dynamic ) : Promise<T>;

/**
Returns a promise that either fulfills when all of the promises in the
Returns a promise that either fulfills when all of the promises in the
iterable argument have fulfilled or rejects as soon as one of the
promises in the iterable argument rejects. If the returned promise
fulfills, it is fulfilled with an array of the values from the
fulfilled promises in the same order as defined in the iterable.
If the returned promise rejects, it is rejected with the reason from
the first promise in the iterable that rejected. This method can be
promises in the iterable argument rejects. If the returned promise
fulfills, it is fulfilled with an array of the values from the
fulfilled promises in the same order as defined in the iterable.
If the returned promise rejects, it is rejected with the reason from
the first promise in the iterable that rejected. This method can be
useful for aggregating results of multiple promises.
**/
static function all( iterable : Array<Dynamic> ) : Promise<Array<Dynamic>>;

/**
Returns a promise that fulfills or rejects as soon as one of the
promises in the iterable fulfills or rejects, with the value or reason
Returns a promise that fulfills or rejects as soon as one of the
promises in the iterable fulfills or rejects, with the value or reason
from that promise.
**/
static function race( iterable : Array<Dynamic> ) : Promise<Dynamic>;
Expand All @@ -74,26 +74,31 @@ extern class Promise<T>
function new( init : (resolve : (value : T) -> Void, reject: (reason : Dynamic) -> Void) -> Void ) : Void;

/**
Appends fulfillment and rejection handlers to the promise and returns a
new promise resolving to the return value of the called handler, or to
its original settled value if the promise was not handled
Appends fulfillment and rejection handlers to the promise and returns a
new promise resolving to the return value of the called handler, or to
its original settled value if the promise was not handled
(i.e. if the relevant handler onFulfilled or onRejected is not a function).
**/
function then<TOut>( fulfillCallback : Null<PromiseCallback<T, TOut>>, ?rejectCallback : EitherType<Dynamic -> Void, PromiseCallback<Dynamic, TOut>> ) : Promise<TOut>;
function then<TOut>( fulfillCallback : Null<PromiseHandler<T, TOut>>, ?rejectCallback : PromiseHandler<Dynamic, TOut> ) : Promise<TOut>;

/**
Appends a rejection handler callback to the promise, and returns a new
promise resolving to the return value of the callback if it is called,
Appends a rejection handler callback to the promise, and returns a new
promise resolving to the return value of the callback if it is called,
or to its original fulfillment value if the promise is instead fulfilled.
**/
@:native("catch")
function catchError<TOut>( rejectCallback : EitherType<Dynamic -> Void, PromiseCallback<Dynamic, TOut>> ) : Promise<TOut>;
function catchError<TOut>( rejectCallback : PromiseHandler<Dynamic, TOut> ) : Promise<TOut>;
}

/**
Callback for the Promise object.
Handler type for the Promise object.
**/
typedef PromiseCallback<T, TOut> = EitherType<T -> TOut, T -> Promise<TOut>>;
abstract PromiseHandler<T,TOut>(T->Dynamic) // T->Dynamic, so the compiler always knows the type of the argument and can infer it for then/catch callbacks
from T->Promise<TOut> // direct casts have priority, so we use that to prioritize the Promise<TOut> return type
{
// function casts are checked after direct ones, so we place T->TOut here to make it have lower priority than T->Promise<TOut>
@:from static inline function fromNonPromise<T,TOut>(f:T->TOut):PromiseHandler<T,TOut> return cast f;
}

/**
A value with a `then` method.
Expand Down
24 changes: 24 additions & 0 deletions tests/misc/projects/Issue6790/Main.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import js.Promise;

class Main {
static function main() {
var p = new Promise<String>(null);

$type(p.then(function(x) $type(x)));
$type(p.then(function(x) $type(x), function(e) $type(e)));

$type(p.then(function(x) {$type(x); return 1;}));
$type(p.then(function(x) {$type(x); return Promise.resolve(1);}));

$type(p.then(null,function(x) {$type(x); return 1;}));
$type(p.then(null,function(x) {$type(x); return Promise.resolve(1);}));

$type(p.then(function(x) {$type(x); return 1;}, function(e) {$type(e); return 1;}));
$type(p.then(function(x) {$type(x); return Promise.resolve(1);}, function(e) {$type(e); return 1;}));
$type(p.then(function(x) {$type(x); return 1;}, function(e) {$type(e); return Promise.resolve(1);}));
$type(p.then(function(x) {$type(x); return Promise.resolve(1);}, function(e) {$type(e); return Promise.resolve(1);}));

$type(p.catchError(function(x) {$type(x);}));
$type(p.catchError(function(x) {$type(x); return Promise.resolve(1);}));
}
}
8 changes: 8 additions & 0 deletions tests/misc/projects/Issue6790/Mismatch.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import js.Promise;

class Mismatch {
static function main() {
var p = new Promise<String>(null);
p.then(x -> 10, e -> "");
}
}
3 changes: 3 additions & 0 deletions tests/misc/projects/Issue6790/compile-fail.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-main Mismatch
-js whatever.js
--no-output
2 changes: 2 additions & 0 deletions tests/misc/projects/Issue6790/compile-fail.hxml.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Mismatch.hx:6: characters 19-26 : (e : Unknown<0>) -> String should be Null<js.PromiseHandler<Dynamic, Int>>
Mismatch.hx:6: characters 19-26 : For optional function argument 'rejectCallback'
3 changes: 3 additions & 0 deletions tests/misc/projects/Issue6790/compile.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-main Main
-js whatever.js
--no-output
29 changes: 29 additions & 0 deletions tests/misc/projects/Issue6790/compile.hxml.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
Main.hx:7: characters 34-35 : Warning : String
Main.hx:7: characters 9-37 : Warning : js.Promise<Void>
Main.hx:8: characters 34-35 : Warning : String
Main.hx:8: characters 56-57 : Warning : Unknown<0>
Main.hx:8: characters 9-59 : Warning : js.Promise<Void>
Main.hx:10: characters 35-36 : Warning : String
Main.hx:10: characters 9-50 : Warning : js.Promise<Int>
Main.hx:11: characters 35-36 : Warning : String
Main.hx:11: characters 9-67 : Warning : js.Promise<Int>
Main.hx:13: characters 40-41 : Warning : Unknown<0>
Main.hx:13: characters 9-55 : Warning : js.Promise<Int>
Main.hx:14: characters 40-41 : Warning : Unknown<0>
Main.hx:14: characters 9-72 : Warning : js.Promise<Int>
Main.hx:16: characters 35-36 : Warning : String
Main.hx:16: characters 70-71 : Warning : Unknown<0>
Main.hx:16: characters 9-85 : Warning : js.Promise<Int>
Main.hx:17: characters 35-36 : Warning : String
Main.hx:17: characters 87-88 : Warning : Unknown<0>
Main.hx:17: characters 9-102 : Warning : js.Promise<Int>
Main.hx:18: characters 35-36 : Warning : String
Main.hx:18: characters 70-71 : Warning : Unknown<0>
Main.hx:18: characters 9-102 : Warning : js.Promise<Int>
Main.hx:19: characters 35-36 : Warning : String
Main.hx:19: characters 87-88 : Warning : Unknown<0>
Main.hx:19: characters 9-119 : Warning : js.Promise<Int>
Main.hx:21: characters 41-42 : Warning : Unknown<0>
Main.hx:21: characters 9-46 : Warning : js.Promise<Void>
Main.hx:22: characters 41-42 : Warning : Unknown<0>
Main.hx:22: characters 9-73 : Warning : js.Promise<Int>

0 comments on commit 4dc5fcc

Please sign in to comment.