google / closure-compiler

A JavaScript checker and optimizer.
https://developers.google.com/closure/compiler/
Apache License 2.0
7.3k stars 1.15k forks source link

Allow invokable type #946

Open lgandras opened 9 years ago

lgandras commented 9 years ago

Hi,

Trying to generate externs for knockoutjs, found out that currently types (classes) can not be invoked. The next best thing is to define a typedef of a function and add properties:

/** @typedef function(?=):? */
ko.Observable;
/**
 * @param {function(?)} callback
 * @param {Object=}     opt_scope
 * @param {string=}     opt_event
 */
ko.Observable.subscribe = function(callback, opt_scope, opt_event) {};

But two problems arise:

  1. The subscribe function is not recognized in compiling code.
  2. Can't have generic types (the parameter and returned value of ko.Observable are the same type).

This is for knockoutjs observables which are integrated pretty straight forward with the closure library and compiler with the exception of the generic condition of them.

I belive the main concept not grasped about ko.observable is mixing functions with objects.

What I'm doing right now is defining ko.Observable as a class with a call method instead of the typedef above. And instead of invoking the function: observable() I'm calling the observable.call() method, which I'm defining in the externs.

Is it possible to solve this with mixins? Is it better to define an invokable type? Does it exist already an option to type hint the compiler about ko.observable?

thank you

concavelenz commented 9 years ago

I have a rough proposal for this, that I'm hoping will get done over the next few months. Specifically we want to: (a) make interfaces match structurally (b) introduce a "ICallable" which would allow a definition like this:

/** @interface @extends ICallable<function(?):?> */
ko.Observable = function() {};
/**
 * @param {function(?)} callback
 * @param {Object=}     opt_scope
 * @param {string=}     opt_event
 */
ko.Observable.subscribe = function(callback, opt_scope, opt_event) {};
ChadKillingsworth commented 9 years ago

Is there a reason this won't work?

/**
 * @constructor
 * @returns {!ko.Observable}
 */
ko.Observable = function() {};

/**
 * @param {function(?)} callback
 * @param {Object=}     opt_scope
 * @param {string=}     opt_event
 */
ko.Observable.prototype.subscribe = function(callback, opt_scope, opt_event) {};
lgandras commented 9 years ago

I tried adding @returns {!ko.Observable} but I still get:

ko.Observable<string> expressions are not callable

And even if it works, I still can't make the return value of ko.Observable generic. FYI here's what I've got right now:

Externs

/**
 * @constructor
 * @param {!T=} opt_v
 * @returns {!ko.Observable}
 * @template T
 */
ko.Observable = function(opt_v) {};
/**
 * @param {function(T)} callback
 * @param {Object=}     opt_scope
 * @param {string=}     opt_event
 */
ko.Observable.prototype.subscribe = function(callback, opt_scope, opt_event) {};
/**
 * @param {!T=} opt_v
 * @return {T}
 */
ko.Observable.prototype.call = function(opt_v) {};

Regular JS file:

/** @type {T} */
var s1 = myObservable();// ko.Observable<T> expressions are not callable
/** @type {T} */
var s2 = myObservable.call();// best working shot

Kind regards

lgandras commented 9 years ago

Oh, and by the way. To change the value (how .call + ko.Observable work):

/** @type {T} */
var s = myObservable.call(null, "new value");