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

Class Expressions #497

Closed
sophiajt opened this issue Aug 21, 2014 · 3 comments
Closed

Class Expressions #497

sophiajt opened this issue Aug 21, 2014 · 3 comments
Labels
ES6 Relates to the ES6 Spec Needs More Info The issue still hasn't been fully clarified Suggestion An idea for TypeScript

Comments

@sophiajt
Copy link
Contributor

Introduction

Currently, in TypeScript, we support only one of the two styles of creating classes in ES6 - that of class declarations. This proposal adds support for the other style, class expressions. This will fill our support for classes in ES6 and help bring us in alignment with both styles.

Class Expressions vs Class Declarations

Class expressions work a little differently than class declarations. Here's a list of the highlights:

  • Class expressions have an optional 'BindingIdentifier' or class name. This allows them to create anonymous classes, or name them, as the programmer sees fit.
  • Unlike class declarations, class expressions with a name do not introduce this name to the lexical scope outside of the class. The class name of a class expression is only visible inside the body of the class expression.
  • Unlike class declarations, class expressions would not create named types in the lexical scope outside of the class. Instead, the named type would only be visible inside the class expression, bound to the class name. Outside of the class expression, they would have anonymous types with the shape of the constructor function.

Just as with class declarations, class expressions would create a constructor function that can be used to construct instances. Like class declarations, you may also declare property members and index members, as well as use the constructor parameters.

Examples

var Rect = class { 
  area: number;

  constructor(public length: number, public width: number) {
    this.area = this.length * this.width;
  }
}
var rect = new Rect(5, 10);
var MyNode = class Node {
  next: Node;

  set nextNode(node: Node) {
    this.next = node;
  }
  constructor() {  }
}
var node = new MyNode();
var nextNode = new MyNode();
node.nextNode = nextNode;

Emit

Class expressions, just like class declarations, create a constructor function. We would emit them similarly to how we do now, with the exception that they are not immediately bound to a variable. Instead, they create the function expression (IIFE) that builds the constructor function, and allows the developer to assign the result of this expression where appropriate.

Emit of .d.ts

The type emitted for class expressions is the object literal representation of the constructor functions, just as with other anonymous types.

Class expressions can also introduce recursion in the type literal. Anonymous types that have possibly recursive components (like the Node example above) will need special treatment, which should be part of a related spec (Edit: #517).

@RyanCavanaugh
Copy link
Member

Design Stress Test

We'll use this to explore some questions below
_node.ts_

function makeNode() {
  var c = class Node {
    self: Node;
    constructor(n: string);
    constructor(n: number);
    constructor(private value: any);
  }
  return c;
}

function getNodeA() {
  var n = makeNode();
  return new n('hello');
}

function getNodeB() {
  var n = makeNode();
  return new n(42);
}

Open questions

What is node.d.ts?
As Jonathan pointed above, this will require support for self-referential anonymous types. We'll need to address this separately as it introduces its own set of issues.

How do private fields work?
First, we have the option of simply disallowing private members in class expressions. This is unsatisfying. Assuming we allow them, this code becomes interesting:

var x = getNodeA();
var y = getNodeB();
x = y; // OK?

Our current rule for x.value and y.value is that they have to come from the "same declaration". This isn't a meaningful concept with class expressions -- we cannot trace the heritage of makeNode through getNodeA and getNodeB. Given that, I believe this assignment should be allowed. We cannot meaningfully determine where an anonymous type "came from", and thus should treat private members of anonymous types as interchangeable.

Fall-out from this would be that we need to emit the type of private fields, as well as allow annotating private fields in type literals.

Do we start decomposing non-exported classes?
TypeScript allows code like this:

module M {
  var x = { n: 3 };
  export var y = x;
}

The logic is that because x's type is known to be one that can be expressed with a type literal, it's not actually private and it's not an error to expose its type through the declaration of y.

This same rule would now apply to classes, if class expressions and class declarations are actually interchangeable. The above comments about compatibility of private members become more important if this is the case, as you might expose a declared class's constructor function as if it were a class expression.

@johnnyreilly
Copy link

This will be helpful for using TypeScript with AngularJS. Controllers are typically created as classes. Given the current lack of class expressions in TS this means a class declaration has to be separated from where it is passed to Angular. Not a massive hardship but it would be nice to go more idiomatic. 👍

@DanielRosenwasser
Copy link
Member

Something that just came to mind: will constructors get contextually typed?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
ES6 Relates to the ES6 Spec Needs More Info The issue still hasn't been fully clarified Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants