Support constructor API
tmattio opened this issue · comments
For instance in lib.es2015.promise.d.ts
:
interface PromiseConstructor {
readonly prototype: Promise<any>;
new <T>(executor: (resolve: (value: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
...
}
declare var Promise: PromiseConstructor;
The global namespace Promise
is assigned to PromiseConstructor
. However, in the generated file, this translates to val promise: _PromiseConstructor [@@js.global "Promise"]
. It should generate a module with the global values instead. Example:
module Promise : sig
include module type of struct
include Promise
end
val create
: (resolve:('T -> unit) -> reject:('E -> unit) -> unit)
-> 'T Promise.t
[@@js.new "Promise"]
end
I'm wondering whether it's good or bad not to strictly follow the definitions in the original .d.ts (although this "constructor pattern" is used very frequently).
If we keep the PromiseConstructor
module, adding the create
and other global functions to Promise
does actually follow the d.ts
, since Promise
is extended by the PromiseConstructor
interface with declare var Promise: PromiseConstructor;
. So I would say technically, we're not following the spec at the moment. For instance, in ts, this is possible:
Promise.create((resolve, reject) => ...)
But not with the generated bindings, where this would look like:
let ctor = PromiseConstructor.create Promise.promise (fun ~resolve ~reject -> ...)
But more generally, to give my two cents on your question, I'd like the bindings to be as close as possible to the spec. If there is a need for more opinionated changes, users can always create a layer of abstraction.
I added two options --simplify-immediate-instance
and --simplify-immediate-constructor
.
The former simplifies the following pattern
interface Foo = {
someMethod(value: number): void;
...
}
declare var Foo: Foo;
to
module [@js.scope "Foo"] Foo : sig
val someMethod: float -> unit [@js.global "someMethod"]
...
end
and the latter simplifies the following pattern:
interface Foo {
someMethod(value: number): void;
...
}
interface FooConstructor {
new(...): Foo;
someStaticMethod(value: number): void;
...
}
declare var Foo: FooConstructor;
to
module [@js.scope "Foo"] Foo : sig
type t = ...
val someMethod: t -> float -> unit [@js.call "someMethod"]
...
val create: ... -> t [@@js.create]
val someStaticMethod: float -> unit [@js.global "someStaticMethod"]
...
end