Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Bounded generic types using closure compiler templates #576

Closed
ghost opened this issue Aug 11, 2014 · 17 comments
Closed

Bounded generic types using closure compiler templates #576

ghost opened this issue Aug 11, 2014 · 17 comments
Assignees
Labels
enhancement internal-issue-created An internal Google issue has been created to track this GitHub issue triage-done Has been reviewed by someone on triage rotation. Types

Comments

@ghost
Copy link

ghost commented Aug 11, 2014

It'd be nice to have bounded generic types, e.g.

/**
 * @param {T} a
 * @template T implements X
 */
function f(a) {
  ...
}

as in http://docs.oracle.com/javase/tutorial/java/generics/bounded.html. This is particularily useful for containers, which asume some basic behaviour of the children without knowing everyhing about them, and yet they restrict which kind of children can be added.

@concavelenz
Copy link
Contributor

Yes, this would be nice. I thought we had an issue for this filed previously.

@brianwestphal
Copy link

+1

@myphysicslab
Copy link
Contributor

+1
This would allow eliminating several type casts. It also ensures that the correct types are used together. Here is how I would change some existing classes to use this:

/**
* @interface
*/
myphysicslab.lab.model.Collision = function() {};

/**
* @param {!myphysicslab.lab.engine2D.RigidBody} body
* @param {!myphysicslab.lab.engine2D.RigidBody} normalBody
* @constructor
* @struct
* @implements {myphysicslab.lab.model.Collision}
*/
myphysicslab.lab.engine2D.RigidBodyCollision = function(body, normalBody) {...}


/**
* @interface
* @extends {myphysicslab.lab.model.ODESim}
* @template T implements {myphysicslab.lab.model.Collision}
*/
myphysicslab.lab.model.CollisionSim = function() {

    /** 
    @method
    @param {!Array.<number>} x  the current array of state variables
    @param {number} stepSize the size of the current time step
    @return {!Array.<!T>}  list of current Collisions
    */
    CollisionSim.prototype.findCollisions;

    /** 
    @method
    @param {!Array.<!T>} collisions the list of current collisions
    @return {boolean} true if was able to handle the collision, changing state of
        simulation.
    */
    CollisionSim.prototype.handleCollisions;

};

/**
* @param {!myphysicslab.lab.model.SimList=} simList_opt
* @constructor
* @struct
* @implements {myphysicslab.lab.model.CollisionSim.<myphysicslab.lab.engine2D.RigidBodyCollision>}
*/
myphysicslab.lab.engine2D.RigidBodySim = function(simList_opt) {...};

All the above could be done with the current closure compiler generics. But I have a class CollisionAdvance that works on a CollisionSim and it's Collisions without knowing anything other than they implement those interfaces. If I set things up as above, I would have to do a bunch of type casts inside of CollisionAdvance. Here are parts of CollisionAdvance as currently written:

* @param {!myphysicslab.lab.model.CollisionSim} sim
* @param {!myphysicslab.lab.model.DiffEqSolver=} diffEqSolver_opt
* @constructor
* @struct
* @implements {myphysicslab.lab.model.AdvanceStrategy}
*/
myphysicslab.lab.model.CollisionAdvance = function(sim, diffEqSolver_opt) {
    /**
    * @type {!myphysicslab.lab.model.CollisionSim}
    * @private
    */
    this.sim_ = sim;

    /** the current set of collisions; includes joints, contacts, imminent collisions
    * @type {!Array.<!myphysicslab.lab.model.Collision>}
    * @private
    */
    this.collisions_ = new Array();
};

Here is an example of code in CollisionAdvance that the generic types would affect:

  this.collisions_ = this.sim_.findCollisions(this.sim_.getVarsArray().getVars(),
      stepSize);
  goog.array.sort(this.collisions_,
    function(/** !myphysicslab.lab.model.Collision*/c1,
            /** !myphysicslab.lab.model.Collision*/c2) {
    var est1 = c1.getEstimatedTime();
    var est2 = c2.getEstimatedTime();
    // sort NaN at back
    if (isNaN(est1))
      return 1;
    else if (isNaN(est2))
      return -1;
    else if (est1 < est2)
      return -1;
    else if (est1 > est2)
      return 1;
    else
      return 0;
    });
  goog.array.forEach(this.collisions_, function(c) {
    c.setNeedsHandling(c.isColliding());
  });

@supersteves
Copy link
Contributor

+1

I'm aiming for 100% typed, but can't because some of my @template generic functions e.g. accept and return T where T needs a "base type" to remove these unknown types within its body.

In addition to the current forms:

/** @template T */
/** @template K, V */

I suggest these forms for tighter consistency with other JSDoc such as @param:

/** @template {} T */
/** @template T {} */
/** @template {} K, {} V */
/** @template K {}, V {} */

The optional {} type attribute would define a "base type" for the template, and all cases using it would need to be the same type or a refinement (exactly like how a return type for a function can be in subclass overrides).

@pauldraper
Copy link
Contributor

+1

@concavelenz
Copy link
Contributor

upper bounds should actually be fairly isolated if anyway wants to try (basically just reject anything that isn't a subtype when matching)

@chrismiddleton
Copy link
Contributor

chrismiddleton commented Apr 18, 2016

+1

In my case, I'm doing stacking (with z indexes) of elements and there can be stacks of stacks (i.e. groups of elements which should either be above or below each other). Having bounded generics would allow me to satisfy my Stackable interface and also provide type-checking of what's actually in the stack to calling code. Ideally, it would look like this:

/**
 * @interface
**/
function Stackable () {}

// methods needed for managing the stack
Stackable.prototype.getMinZIndex = function () {};

// more methods omitted

// !! Note the template in the StackStack class below

/**
 * @constructor
 * @template T implements Stackable
 * @implements {Stackable}
**/
function StackStack () {
    /** @type {Array<T!>!} */ this._stacks = [];
}

/**
 * @param {T}
**/
StackStack.prototype.add = function (stack) {
    this._stacks.push(stack);
    // LATER methods from Stackable are called on stack, so stack MUST be a Stackable
    // hence the need for the generic constraint
};

/**
 * @param {function(T!)}
**/
StackStack.prototype.forEach = function (func) {
    this._stacks.forEach(func);
};

// more methods omitted

/**
 * @constructor
 * @implements {Stackable}
**/
function FooStack () {}

// more methods omitted

// in calling code
/** @type {StackStack<FooStack!>!} */ var fooStack = new StackStack();
fooStack.forEach(function (fooStack) {
    // do something with fooStack here knowing for sure that it's a FooStack
});

@dimvar
Copy link
Contributor

dimvar commented Apr 18, 2016

FYI, we are finally able to prioritize work on bounded generics :) They should be available for the new type checker sometime this quarter. https://github.com/google/closure-compiler/wiki/Using-NTI-(new-type-inference)

@chrismiddleton
Copy link
Contributor

chrismiddleton commented Apr 18, 2016

Woo! That's awesome to hear; I look forward to it. I hope sometime in the future I have the time to actually contribute to Closure, but in the meantime I can only say thank you for all that you guys do. Using the compiler has made my JS coding 1000x more pleasant.

@bkputnam
Copy link

@dimvar, just checking in, were you able to get bounded generics into NTI? The link you posted doesn't mention them at all.

@dimvar
Copy link
Contributor

dimvar commented Sep 26, 2017

No, sorry, didn't happen after all :-/ We still want to do it but it's currently not on the immediate agenda.

@bkputnam
Copy link

No worries, thanks for the update!

@dietergeerts
Copy link

For consistency, maybe it should be implemented like TS does? microsoft/TypeScript#24600

@blickly
Copy link
Contributor

blickly commented Mar 29, 2019

This corresponds to internal issue http://b/35241823

@blickly blickly added triage-done Has been reviewed by someone on triage rotation. internal-issue-created An internal Google issue has been created to track this GitHub issue labels Mar 29, 2019
@SamB
Copy link

SamB commented Jul 16, 2019

Also see https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html#template for the current TypeScript documentation for the corresponding feature. (Variance is weird there, as always).

@al3623
Copy link
Contributor

al3623 commented Aug 23, 2019

Bounded generic types are now supported! They are available in the head compiler and will be in the next release. Please take a look at the updated documentation for details on the syntax and typing semantics of bounded generics.

@al3623 al3623 closed this as completed Aug 23, 2019
@AdobeScripter
Copy link

The "updated documentation", referred to in the comment above, now says "Bounded generic types are currently not supported". So… which is it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement internal-issue-created An internal Google issue has been created to track this GitHub issue triage-done Has been reviewed by someone on triage rotation. Types
Projects
None yet
Development

No branches or pull requests