codemix / babel-plugin-typecheck

Static and runtime type checking for JavaScript in the form of a Babel plugin.
MIT License
886 stars 44 forks source link

Support for `Class` and `Class<T>` annotations #73

Closed srounce closed 8 years ago

srounce commented 8 years ago

Currently we can only specify a naive check of Function or Function<T>. If we specify a type of Class the plugin treats it as a variable.

Given:

class A {
  _parent: A
  setParent(p: Parent): void {
    this._parent = p;
    // Do other stuff...
  }
}
class B extends A { }

It would be nice if one could express:

let factoryFn = (type: Class<A>, parent: A): A => {
  let instance = new type();
  instance.setParent(parent); // Some DI stuff happens here (not really important)
  return instance;
}

So factoryFn would:

Hope this makes sense, cheers!

phpnode commented 8 years ago

@srounce please could you specify what you'd like the compiled code to look like (roughly)? It'll help me figure out if we can do this.

srounce commented 8 years ago

Sure, I'd expect a class hierarchy check (Class<T>) to be something along the lines of:

if (!(A.isPrototypeOf(type))) 
  throw new TypeError("Value of argument 'type' violates contract: Not a subclass of 'A'");

The issue of whether a class was defined as a class or a function is slightly more complex (Class check). Seeing as the class keyword is just sugar over defining methods on a function's prototype chain, the definition will end up as a function. However, the class keyword can be detected via the class constructor's toString method and a little bit of string searching to end up with:

if (!(A.prototype.constructor.toString().indexOf('class')))
  throw new TypeError("Value of argument 'type' violates contract: Not a class");

As Babel will transpile them all to functions we cannot do this, the above comes rather unstuck (and where my current train of thought on this matter resides). Only thing that comes to mind (for this work at runtime), is modification of the syntax tree: adding tags to denote classes VS functions (not ideal).

@phpnode - It's been a while since I touched estree related stuff, how feasible does this sound to you?

Tl;dr, Class<T> is pretty solvable Class is a little trickier and needs more thought.

phpnode commented 8 years ago

Hmm, right now we don't actually validate type parameters in polymorphic functions, we just recognise that certain identifiers are type parameters and skip their checks. I would like to solve this properly but it can get ridiculously complicated so I'm wary.

As for differentiating between Class and Function, I don't think there's a sensible way to do that because of proliferation of classes which don't use the new syntax. We shouldn't discriminate here, it makes no sense to say that e.g. node's Buffer is not a class because it's not defined with class Buffer {}.

If Class (and therefore Class<T>) is a thing, we could emit a very simplistic check for this, basically typeof thing === 'function' && thing.prototype && thing.prototype.constructor === thing. I think that's the best we're going to get until the type parameters issue gets sorted.