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

Decorators #2249

Closed
rbuckton opened this Issue Mar 7, 2015 · 139 comments

Comments

Projects
None yet
@rbuckton
Member

rbuckton commented Mar 7, 2015

ES7 proposal

The ES7 proposal for decorators can be found here: https://github.com/wycats/javascript-decorators
The ES7 proposal serves as the base of this proposal. Below are notes about how the type system

Decorator targets:

Class constructor

@F("color")
@G
class Foo {
}

desugars to:

var Foo = (function () {
    function Foo() {
    }
    Foo = __decorate([F("color"), G], Foo);
    return Foo;
})();

Methods

class Foo {
  @F(color)
  @G
  bar() { }
}

desugars to:

var Foo = (function () {
    function Foo() {
    }
    Foo.prototype.bar = function () {
    };
    Object.defineProperty(Foo.prototype, "bar", __decorate([F(color), G], Foo.prototype, "bar", Object.getOwnPropertyDescriptor(Foo.prototype, "bar")));
    return Foo;
})();

Static method

class Foo {
    @F("color")
    @G
    static sMethod() {}
}

desugars to:

var Foo = (function () {
    function Foo() {
    }
    Foo.sMethod = function () {
    };
    Object.defineProperty(Foo, "sMethod", __decorate([F("color"), G], Foo, "sMethod", Object.getOwnPropertyDescriptor(Foo, "sMethod")));
    return Foo;
})();

Properties

class Foo {
    @F("color")
    @G
    prop: number;
}

desugars to:

var Foo = (function () {
    function Foo() {
    }
    __decorate([F("color"), G], Foo.prototype, "prop");
    return Foo;
})();

Method/Accessor formal parameter

class Foo {
    method(@G a, @F("color") b) {}
}

desugars to:

var Foo = (function () {
    function Foo() {
    }
    Foo.prototype.method = function (a, b) {
    };
    __decorate([G], Foo.prototype, "method", 0);
    __decorate([F("color")], Foo.prototype, "method", 1);
    return Foo;
})();

Where the __decorate is defined as:

var __decorate = this.__decorate || function (decorators, target, key, value) {
    var kind = typeof (arguments.length == 2 ? value = target : value);
    for (var i = decorators.length - 1; i >= 0; --i) {
        var decorator = decorators[i];
        switch (kind) {
            case "function": value = decorator(value) || value; break;
            case "number": decorator(target, key, value); break;
            case "undefined": decorator(target, key); break;
            case "object": value = decorator(target, key, value) || value; break;
        }
    }
    return value;
};

Decorator signatures:

A valid decorator should be:

  1. Assignable to one of the Decorator types (ClassDecorator | PropertyDecorator | MethodDecorator | ParameterDecorator) as described below.
  2. Return a value (in the case of class decorators and method decorator) that is assignable to the decorated value.
declare type ClassDecorator = <TFunction extends Function>(target: TFunction) => TFunction | void;
declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void;
declare type MethodDecorator = <T>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<T> | void;
declare type ParameterDecorator = (target: Function, propertyKey: string | symbol, parameterIndex: number) => void;

Notes:

  • Decorating a function declaration is not allowed as it will block hoisting the function to the top of the scope, which is a significant change in semantics.
  • Decorating function expressions and arrow functions are not supported. The same effect can be achived by applying the decorator function as var x = dec(function () { });
  • Decorating function formal parameters is currently not part of the ES7 proposal.
  • Decorators are not allowed when targeting ES3

@rbuckton rbuckton added the Spec label Mar 7, 2015

@rbuckton rbuckton self-assigned this Mar 7, 2015

@fdecampredon

This comment has been minimized.

Show comment
Hide comment
@fdecampredon

fdecampredon Mar 7, 2015

Excuse me from what I understand of the spec, we won't be able to do:

@F
function test() {
}

Am I right ?

fdecampredon commented Mar 7, 2015

Excuse me from what I understand of the spec, we won't be able to do:

@F
function test() {
}

Am I right ?

@ivogabe

This comment has been minimized.

Show comment
Hide comment
@ivogabe

ivogabe Mar 7, 2015

Contributor

How does type serialization work with rest arguments?

@F()  
class Foo {  
    constructor(...args: string[]) {  
    }  
}  

function F(@paramterTypes types?: Function[]) {  
    return function (target) {  
        target.paramterTypes = types; // ???  
    }  
}
Contributor

ivogabe commented Mar 7, 2015

How does type serialization work with rest arguments?

@F()  
class Foo {  
    constructor(...args: string[]) {  
    }  
}  

function F(@paramterTypes types?: Function[]) {  
    return function (target) {  
        target.paramterTypes = types; // ???  
    }  
}
@MgSam

This comment has been minimized.

Show comment
Hide comment
@MgSam

MgSam Mar 7, 2015

Using decorators seems straightforward enough, but I found the sections about declaring them to be confusing. C.4 says decorators need to be annotated with @decorator, but not a single one of the examples actually shows this happening.

Are decorator factories intended to be classes that implement the interfaces found in B?

MgSam commented Mar 7, 2015

Using decorators seems straightforward enough, but I found the sections about declaring them to be confusing. C.4 says decorators need to be annotated with @decorator, but not a single one of the examples actually shows this happening.

Are decorator factories intended to be classes that implement the interfaces found in B?

@JsonFreeman

This comment has been minimized.

Show comment
Hide comment
@JsonFreeman

JsonFreeman Mar 7, 2015

Contributor

What is the rule for refining the interpretation of CoverMemberExpressionSquareBracketsAndComputedPropertyName?

Contributor

JsonFreeman commented Mar 7, 2015

What is the rule for refining the interpretation of CoverMemberExpressionSquareBracketsAndComputedPropertyName?

@JsonFreeman

This comment has been minimized.

Show comment
Hide comment
@JsonFreeman

JsonFreeman Mar 7, 2015

Contributor

I noticed many of the typings have Function | Object at various points, but these will degenerate to Object at type check time. What is the reason to have Function there?

Contributor

JsonFreeman commented Mar 7, 2015

I noticed many of the typings have Function | Object at various points, but these will degenerate to Object at type check time. What is the reason to have Function there?

@JsonFreeman

This comment has been minimized.

Show comment
Hide comment
@JsonFreeman

JsonFreeman Mar 7, 2015

Contributor

I am not crazy about the terms DecoratorFunction vs DecoratorFactory. I'd much rather follow the nomenclature of generators, which has Generator and GeneratorFunction. With this scheme, we would rename DecoratorFunction to Decorator, and DecoratorFactory to DecoratorFunction.

Contributor

JsonFreeman commented Mar 7, 2015

I am not crazy about the terms DecoratorFunction vs DecoratorFactory. I'd much rather follow the nomenclature of generators, which has Generator and GeneratorFunction. With this scheme, we would rename DecoratorFunction to Decorator, and DecoratorFactory to DecoratorFunction.

@JsonFreeman

This comment has been minimized.

Show comment
Hide comment
@JsonFreeman

JsonFreeman Mar 7, 2015

Contributor

For the decorated exports, what is [lookahead ≠ @] for? Can HoistableDeclaration and ClassDeclaration actually start with a @?

Contributor

JsonFreeman commented Mar 7, 2015

For the decorated exports, what is [lookahead ≠ @] for? Can HoistableDeclaration and ClassDeclaration actually start with a @?

@jayphelps

This comment has been minimized.

Show comment
Hide comment
@jayphelps

jayphelps Mar 8, 2015

This is a dup of #1557

jayphelps commented Mar 8, 2015

This is a dup of #1557

@JsonFreeman

This comment has been minimized.

Show comment
Hide comment
@JsonFreeman

JsonFreeman Mar 8, 2015

Contributor

It's not really a dupe, as #1557 was for a different design. This issue is for the decorators design being implemented now.

Contributor

JsonFreeman commented Mar 8, 2015

It's not really a dupe, as #1557 was for a different design. This issue is for the decorators design being implemented now.

@jayphelps

This comment has been minimized.

Show comment
Hide comment
@jayphelps

jayphelps Mar 8, 2015

My mistake.

jayphelps commented Mar 8, 2015

My mistake.

@andreypopp andreypopp referenced this issue Mar 8, 2015

Closed

Decorators #974

@fdecampredon

This comment has been minimized.

Show comment
Hide comment
@fdecampredon

fdecampredon Mar 9, 2015

For decorator on function expression, could we not do something like :

@F("color") @G 
function myFunc() {
   doSomething();
}

transformed in :

var _t = function() {
   doSomething();
}
_t = F("color")(_t = G(_t) || _t) || _t;  

function myFunc() {
  return _t.apply(this, arguments)
}

It's a bit bother some to have to right every function like :

const myFunc = function () {}

You loose hoisting, and function.name

fdecampredon commented Mar 9, 2015

For decorator on function expression, could we not do something like :

@F("color") @G 
function myFunc() {
   doSomething();
}

transformed in :

var _t = function() {
   doSomething();
}
_t = F("color")(_t = G(_t) || _t) || _t;  

function myFunc() {
  return _t.apply(this, arguments)
}

It's a bit bother some to have to right every function like :

const myFunc = function () {}

You loose hoisting, and function.name

@mhegazy

This comment has been minimized.

Show comment
Hide comment
@mhegazy

mhegazy Mar 25, 2015

Contributor

Implementation added in PR #2399

Contributor

mhegazy commented Mar 25, 2015

Implementation added in PR #2399

@mhegazy

This comment has been minimized.

Show comment
Hide comment
@mhegazy

mhegazy Mar 25, 2015

Contributor

Updates: Proposal updated, and added link to the @wycats ES7 JavaScript decorators.

Contributor

mhegazy commented Mar 25, 2015

Updates: Proposal updated, and added link to the @wycats ES7 JavaScript decorators.

@fdecampredon

This comment has been minimized.

Show comment
Hide comment
@fdecampredon

fdecampredon Mar 26, 2015

saddened that it became a class only thing ...
Also what about ambiant decorator did they get out of the scope ?

fdecampredon commented Mar 26, 2015

saddened that it became a class only thing ...
Also what about ambiant decorator did they get out of the scope ?

@JsonFreeman

This comment has been minimized.

Show comment
Hide comment
@JsonFreeman

JsonFreeman Mar 26, 2015

Contributor

@fdecampredon with your proposal for functions, seems like you still lose the hoisting.

Contributor

JsonFreeman commented Mar 26, 2015

@fdecampredon with your proposal for functions, seems like you still lose the hoisting.

@fdecampredon

This comment has been minimized.

Show comment
Hide comment
@fdecampredon

fdecampredon Mar 26, 2015

@JsonFreeman why ? if you insert _t at the top of the file ?

import something from 'something';

myFunc(something.something());

@F("color") @G 
function myFunc() {
  doSomething()
}
import something from 'something';

var _t = function() {
   doSomething();
}
_t = F("color")(_t = G(_t) || _t) || _t;  

myFunc(something.something());

function myFunc() {
  return _t.apply(this, arguments)
}

Also even if my proposal has a lot of issues, I would seriously like to be able to use decorators on function (even if I have to use variable assigned function) and lose hoisting.
Case like this gist seems a pretty good decorator use case for both function and class (especially coupled with ambient decorators if they still end up being in the scope)

fdecampredon commented Mar 26, 2015

@JsonFreeman why ? if you insert _t at the top of the file ?

import something from 'something';

myFunc(something.something());

@F("color") @G 
function myFunc() {
  doSomething()
}
import something from 'something';

var _t = function() {
   doSomething();
}
_t = F("color")(_t = G(_t) || _t) || _t;  

myFunc(something.something());

function myFunc() {
  return _t.apply(this, arguments)
}

Also even if my proposal has a lot of issues, I would seriously like to be able to use decorators on function (even if I have to use variable assigned function) and lose hoisting.
Case like this gist seems a pretty good decorator use case for both function and class (especially coupled with ambient decorators if they still end up being in the scope)

@mhegazy

This comment has been minimized.

Show comment
Hide comment
@mhegazy

mhegazy Mar 26, 2015

Contributor

@fdecampredon this does not work for the general case, as the decorators are expressions themselves. e.g.

myFunc();  // assumes function declaration is hoisted

var dec = (t) => t; // defininig a decorator

@dec
function myFunc() {}

if you hoist the function declaration and application of the decorator then you break the decorator. if you only hoist the function declaration, but not the decorator application you can witness the function in an undecorated state. no appealing solutions here.

this is the same issue as with class declaration extend clause, which in ES6 is an expression. the result was not hoisting the class declaration, just the symbol (akin to var declaration, but not function declarations)

Contributor

mhegazy commented Mar 26, 2015

@fdecampredon this does not work for the general case, as the decorators are expressions themselves. e.g.

myFunc();  // assumes function declaration is hoisted

var dec = (t) => t; // defininig a decorator

@dec
function myFunc() {}

if you hoist the function declaration and application of the decorator then you break the decorator. if you only hoist the function declaration, but not the decorator application you can witness the function in an undecorated state. no appealing solutions here.

this is the same issue as with class declaration extend clause, which in ES6 is an expression. the result was not hoisting the class declaration, just the symbol (akin to var declaration, but not function declarations)

@fdecampredon

This comment has been minimized.

Show comment
Hide comment
@fdecampredon

fdecampredon Mar 26, 2015

Oups didn't think about it thank you @mhegazy.
However why the function part have completely abandoned the original @jonathandturner proposal had the rule :

decorated function declarations cannot be hoisted to the containing scope

Loosing hoisting is sure a drawback, but I find it damageable to transform it into an class only feature when it would have use case for other construct.

fdecampredon commented Mar 26, 2015

Oups didn't think about it thank you @mhegazy.
However why the function part have completely abandoned the original @jonathandturner proposal had the rule :

decorated function declarations cannot be hoisted to the containing scope

Loosing hoisting is sure a drawback, but I find it damageable to transform it into an class only feature when it would have use case for other construct.

@JsonFreeman

This comment has been minimized.

Show comment
Hide comment
@JsonFreeman

JsonFreeman Mar 26, 2015

Contributor

Let's see what the desired set of constraints seem to imply:

  • decorated function should be hoisted
  • function should be decorated as soon as it is available - therefore decorator application must be hoisted
  • decorator must be defined before it is applied - therefore decorator definition itself (or all entities referenced by the decorator expression) must be hoisted
  • decorator expression gets evaluated at the place it is lexically applied - therefore the application cannot be hoisted

The only resolution I can see for this is the following: For function decorators, we only allow something of the form @identifier. We do not allow a left hand side expression. In addition, the identifier must be a direct reference to a function declaration (including a decorated function). All function decorations that take place in the scope must be emitted at the top of the scope, in the order that they were applied.

Contributor

JsonFreeman commented Mar 26, 2015

Let's see what the desired set of constraints seem to imply:

  • decorated function should be hoisted
  • function should be decorated as soon as it is available - therefore decorator application must be hoisted
  • decorator must be defined before it is applied - therefore decorator definition itself (or all entities referenced by the decorator expression) must be hoisted
  • decorator expression gets evaluated at the place it is lexically applied - therefore the application cannot be hoisted

The only resolution I can see for this is the following: For function decorators, we only allow something of the form @identifier. We do not allow a left hand side expression. In addition, the identifier must be a direct reference to a function declaration (including a decorated function). All function decorations that take place in the scope must be emitted at the top of the scope, in the order that they were applied.

@mhegazy

This comment has been minimized.

Show comment
Hide comment
@mhegazy

mhegazy Mar 27, 2015

Contributor

The problem breaking hoisting rules is that it is surprising. If you are writing javascript for a while you expect function declarations to be available before they are lexically declared; now by adding a seemingly simple syntactic marker (the decorator) this fundamental nature of function declaration is altered.

Having said that, the ES7 proposal is still in its initial stages, so I expect it to evolve and expand; so it is conceivable that the final proposal would include functions in some form.

Contributor

mhegazy commented Mar 27, 2015

The problem breaking hoisting rules is that it is surprising. If you are writing javascript for a while you expect function declarations to be available before they are lexically declared; now by adding a seemingly simple syntactic marker (the decorator) this fundamental nature of function declaration is altered.

Having said that, the ES7 proposal is still in its initial stages, so I expect it to evolve and expand; so it is conceivable that the final proposal would include functions in some form.

@JsonFreeman

This comment has been minimized.

Show comment
Hide comment
@JsonFreeman

JsonFreeman Mar 27, 2015

Contributor

I do think they should be hoisted. I am saying that the only decorations we allow on functions are decorations that are definitely themselves hoisted. Namely identifiers that reference function declarations.

But there is another problem here. It is actually impossible to simultaneously hoist all decorated functions and ensure that they are not observed in their undecorated state. The problem can be seen with a decorator cycle.

@dec1
function dec2(target: Function) {
   // Do stuff
}

@dec2
function dec1(target: Function) {
   // Do stuff
}

Even if both functions are hoisted, which one gets decorated first? If dec2 gets decorated first, then dec1 will not itself be decorated by the time it is applied to dec2.

So we would have to choose between the following:

  • The functions are not hoisted
  • The undecorated functions are hoisted, but the decorator applications don't get hoisted with them.

While I don't like either of these, I don't even think anything else is possible.

Contributor

JsonFreeman commented Mar 27, 2015

I do think they should be hoisted. I am saying that the only decorations we allow on functions are decorations that are definitely themselves hoisted. Namely identifiers that reference function declarations.

But there is another problem here. It is actually impossible to simultaneously hoist all decorated functions and ensure that they are not observed in their undecorated state. The problem can be seen with a decorator cycle.

@dec1
function dec2(target: Function) {
   // Do stuff
}

@dec2
function dec1(target: Function) {
   // Do stuff
}

Even if both functions are hoisted, which one gets decorated first? If dec2 gets decorated first, then dec1 will not itself be decorated by the time it is applied to dec2.

So we would have to choose between the following:

  • The functions are not hoisted
  • The undecorated functions are hoisted, but the decorator applications don't get hoisted with them.

While I don't like either of these, I don't even think anything else is possible.

@mhegazy

This comment has been minimized.

Show comment
Hide comment
@mhegazy

mhegazy Mar 27, 2015

Contributor

This is the JS proposal. so the engine would not know if the expression refers to a function declaration or not, with static analysis we could tell though. consider this:

@dec1
function dec2(target: Function) {
   // Do stuff
}

dec2 = undefined;

@dec2
function dec1(target: Function) {
   // Do stuff
}
Contributor

mhegazy commented Mar 27, 2015

This is the JS proposal. so the engine would not know if the expression refers to a function declaration or not, with static analysis we could tell though. consider this:

@dec1
function dec2(target: Function) {
   // Do stuff
}

dec2 = undefined;

@dec2
function dec1(target: Function) {
   // Do stuff
}
@fletchsod-developer

This comment has been minimized.

Show comment
Hide comment
@fletchsod-developer

fletchsod-developer Apr 2, 2015

Ugh! Javascript not getting any simplier anymore. :-)

fletchsod-developer commented Apr 2, 2015

Ugh! Javascript not getting any simplier anymore. :-)

@ivogabe

This comment has been minimized.

Show comment
Hide comment
@ivogabe

ivogabe Apr 2, 2015

Contributor

It would be easier to enable it only on function expressions:

const foo = @decorator () => {
    // ...   
}
const bar = @decorator function() {
    // ...
}

Function expressions or lambda's aren't hoisted.

Contributor

ivogabe commented Apr 2, 2015

It would be easier to enable it only on function expressions:

const foo = @decorator () => {
    // ...   
}
const bar = @decorator function() {
    // ...
}

Function expressions or lambda's aren't hoisted.

@cybrown

This comment has been minimized.

Show comment
Hide comment
@cybrown

cybrown Apr 4, 2015

Is it possible to have parameters like this in a decorator in typescript 1.5.0-alpha ?

@dec1({key1: value1, key2, value2})
function dec2(target: Function) {
   // Do stuff
}

cybrown commented Apr 4, 2015

Is it possible to have parameters like this in a decorator in typescript 1.5.0-alpha ?

@dec1({key1: value1, key2, value2})
function dec2(target: Function) {
   // Do stuff
}
@cybrown

This comment has been minimized.

Show comment
Hide comment
@cybrown

cybrown Apr 4, 2015

Ok nevermind, just create a factory which takes the parameters and return the actual decorator function.

For example, a class decorator with a string parameter:

function decoratorWithString(param: string) { // Decorator factory
    return function(target) { // Actual decorator
        // Do stuff with target and string parameter
    }
}

// Usage
@decoratorWithString('foobar')
class Foo {

}

cybrown commented Apr 4, 2015

Ok nevermind, just create a factory which takes the parameters and return the actual decorator function.

For example, a class decorator with a string parameter:

function decoratorWithString(param: string) { // Decorator factory
    return function(target) { // Actual decorator
        // Do stuff with target and string parameter
    }
}

// Usage
@decoratorWithString('foobar')
class Foo {

}
@cmichaelgraham

This comment has been minimized.

Show comment
Hide comment
@cmichaelgraham

cmichaelgraham Apr 5, 2015

greetings.

i am trying to figure out how to write a decorator that will pick up the types declared in the constructor.

here's some code that illustrates what i'm trying to do, but it is hardwired. i need it to respond to the constructor declaration in class D

class A {
  public message = "identity: class A";
}

class B {
  public message = "identity: class B";
}

@decoTest
class D {
  static metadata:Array<Function> = [];
  constructor(a: A, b: B) {
  }
}
describe("decorators", function() {
  it("should inject constructor types", function() {
    var d = new D(new A(), new B());
    expect(D.metadata.length).toBe(2);
  });
});


function decoTest<T>(target: T, ...rest) {
  target["metadata"].push(A, B); // how do i get this based on constructor ???
  return target;
}

cmichaelgraham commented Apr 5, 2015

greetings.

i am trying to figure out how to write a decorator that will pick up the types declared in the constructor.

here's some code that illustrates what i'm trying to do, but it is hardwired. i need it to respond to the constructor declaration in class D

class A {
  public message = "identity: class A";
}

class B {
  public message = "identity: class B";
}

@decoTest
class D {
  static metadata:Array<Function> = [];
  constructor(a: A, b: B) {
  }
}
describe("decorators", function() {
  it("should inject constructor types", function() {
    var d = new D(new A(), new B());
    expect(D.metadata.length).toBe(2);
  });
});


function decoTest<T>(target: T, ...rest) {
  target["metadata"].push(A, B); // how do i get this based on constructor ???
  return target;
}
@cybrown

This comment has been minimized.

Show comment
Hide comment
@cybrown

cybrown Apr 5, 2015

I am afraid that type information is out of scope of the decorator feature.
You can use ParameterDecorator on each parameter to add that kind of information.

cybrown commented Apr 5, 2015

I am afraid that type information is out of scope of the decorator feature.
You can use ParameterDecorator on each parameter to add that kind of information.

@cybrown

This comment has been minimized.

Show comment
Hide comment
@cybrown

cybrown Apr 5, 2015

The typing or implementation for ParameterDecorator is not quite correct, from the typings the target is a function, but while using typescript, it is the prototype object.
I must cast the target parameter in order to get the right type:

function MyParameterDecorator (_target: Function, methodName: string, index: number) {
    const target = <InterfaceForMyUseCase><anyt>_target;
    // do stuff
}

Instead of:

function MyParameterDecorator (target: InterfaceForMyUseCase, methodName: string, index: number) {
    // do stuff
}

cybrown commented Apr 5, 2015

The typing or implementation for ParameterDecorator is not quite correct, from the typings the target is a function, but while using typescript, it is the prototype object.
I must cast the target parameter in order to get the right type:

function MyParameterDecorator (_target: Function, methodName: string, index: number) {
    const target = <InterfaceForMyUseCase><anyt>_target;
    // do stuff
}

Instead of:

function MyParameterDecorator (target: InterfaceForMyUseCase, methodName: string, index: number) {
    // do stuff
}
@cmichaelgraham

This comment has been minimized.

Show comment
Hide comment
@cmichaelgraham

cmichaelgraham Apr 6, 2015

wow - parameter decorators rule !!!!!!!!!!!!!!! see this repo

here's the output from running the tests:

LOG: 'injectMe:'
LOG: '  type: class A'
LOG: '  type: class B'
LOG: '  some key'

and the code:

module ParameterDecorators {
  class A {
    static typeName:string = "type: class A";
    public instanceTypeName = "instance: class A";
  }

  class B {
    static typeName:string = "type: class B";
    public instanceTypeName = "instance: class B";
  }

  @injectTest(A, B, "some key")
  class C {
    static injectMe: Array<any> = [];
    constructor(a: A, b: B) {
    }
  }

  function injectTest(...rest) {
    return function(target): void {
      target["injectMe"] = rest;
    }
  }

  describe("decorators", function() {
    it("should inject dependency-injection keys", function() {
      var c = new C(new A(), new B());
      console.log("injectMe:");
      for (let parm of C.injectMe) {
        if (typeof(parm) === "function") {
          console.log("\t" + parm["typeName"]);
        } else {
          console.log("\t" + parm)
        }
      }
    });
  });
}

cmichaelgraham commented Apr 6, 2015

wow - parameter decorators rule !!!!!!!!!!!!!!! see this repo

here's the output from running the tests:

LOG: 'injectMe:'
LOG: '  type: class A'
LOG: '  type: class B'
LOG: '  some key'

and the code:

module ParameterDecorators {
  class A {
    static typeName:string = "type: class A";
    public instanceTypeName = "instance: class A";
  }

  class B {
    static typeName:string = "type: class B";
    public instanceTypeName = "instance: class B";
  }

  @injectTest(A, B, "some key")
  class C {
    static injectMe: Array<any> = [];
    constructor(a: A, b: B) {
    }
  }

  function injectTest(...rest) {
    return function(target): void {
      target["injectMe"] = rest;
    }
  }

  describe("decorators", function() {
    it("should inject dependency-injection keys", function() {
      var c = new C(new A(), new B());
      console.log("injectMe:");
      for (let parm of C.injectMe) {
        if (typeof(parm) === "function") {
          console.log("\t" + parm["typeName"]);
        } else {
          console.log("\t" + parm)
        }
      }
    });
  });
}
@cybrown

This comment has been minimized.

Show comment
Hide comment
@cybrown

cybrown Apr 6, 2015

I've made a wrapper around express (but any web framework could be supported, an adapter interface is defined) with a decorator based API: https://github.com/cybrown/web-decorators

I am using ClassDecorator, ParameterDecorator and MethodDecorator.

cybrown commented Apr 6, 2015

I've made a wrapper around express (but any web framework could be supported, an adapter interface is defined) with a decorator based API: https://github.com/cybrown/web-decorators

I am using ClassDecorator, ParameterDecorator and MethodDecorator.

@sccolbert

This comment has been minimized.

Show comment
Hide comment
@sccolbert

sccolbert Aug 15, 2015

Is there a reason to treat property decorators differently than method decorators with respect to return value? (Babel doesn't).

What I mean is that, if I want to define functionality for a property via decorator I have to do this:

function decorator(proto, name) {
    Object.defineProperty(proto, name, { value: 42 });
}

class Foo {
    @decorator
    a: number;
}

Whereas Babel requires the descriptor to be returned:

function decorator(proto, name) {
    return { value: 42 };
}

class Foo {
    @decorator
    a;
}

This makes it difficult to write a decorator in TypeScript which is part of a library which will be used from Babel.

I would propose that a property decorator be treated identically to method decorator, and the return value of the decorator should be applied via Object.defineProperty. This would also allow for multiple decorators on a single property, just like a method.

A workaround for now (for Babel compatibility) is to return the descriptor from the decorator in addition to setting the property, but that seems unnecessarily wasteful:

function decorator(proto, name) {
    var d = { value: 42 };
    Object.defineProperty(proto, name, d);
    return d;
}

sccolbert commented Aug 15, 2015

Is there a reason to treat property decorators differently than method decorators with respect to return value? (Babel doesn't).

What I mean is that, if I want to define functionality for a property via decorator I have to do this:

function decorator(proto, name) {
    Object.defineProperty(proto, name, { value: 42 });
}

class Foo {
    @decorator
    a: number;
}

Whereas Babel requires the descriptor to be returned:

function decorator(proto, name) {
    return { value: 42 };
}

class Foo {
    @decorator
    a;
}

This makes it difficult to write a decorator in TypeScript which is part of a library which will be used from Babel.

I would propose that a property decorator be treated identically to method decorator, and the return value of the decorator should be applied via Object.defineProperty. This would also allow for multiple decorators on a single property, just like a method.

A workaround for now (for Babel compatibility) is to return the descriptor from the decorator in addition to setting the property, but that seems unnecessarily wasteful:

function decorator(proto, name) {
    var d = { value: 42 };
    Object.defineProperty(proto, name, d);
    return d;
}
@sccolbert

This comment has been minimized.

Show comment
Hide comment
@sccolbert

sccolbert Sep 3, 2015

@mhegazy would you happen to have any insight on my comment above?

sccolbert commented Sep 3, 2015

@mhegazy would you happen to have any insight on my comment above?

@rbuckton

This comment has been minimized.

Show comment
Hide comment
@rbuckton

rbuckton Sep 3, 2015

Member

@sccolbert In TypeScript we opted disallow the use of property descriptors for decorators on data properties, as it can lead to issues at runtime due to the fact that any "value" specified by the descriptor will be set on the prototype and not on the instance. While your example above would generally not be an issue, consider the following:

function decorator(proto, name) {
  return { value: new Point(0, 0); }
}

class Foo {
  @decorator
  p: Point;

  setX(x) { this.p.x = 1; }
}

let a = new Foo();
let b = new Foo();
console.log(a.p.x); // 0
b.setX(10);
console.log(a.p.x); // 10 (!)

There is a proposal for a future version of ES to support an "initializer" property on the descriptor, which would be evaluated during the constructor. That would give you the ability to control whether the value is set on the prototype or allocated per-instance.

For now you can work around this limitation by calling Object.defineProperty directly, as per the example in your comment. We will continue to investigate whether to relax this restriction in the future.

Member

rbuckton commented Sep 3, 2015

@sccolbert In TypeScript we opted disallow the use of property descriptors for decorators on data properties, as it can lead to issues at runtime due to the fact that any "value" specified by the descriptor will be set on the prototype and not on the instance. While your example above would generally not be an issue, consider the following:

function decorator(proto, name) {
  return { value: new Point(0, 0); }
}

class Foo {
  @decorator
  p: Point;

  setX(x) { this.p.x = 1; }
}

let a = new Foo();
let b = new Foo();
console.log(a.p.x); // 0
b.setX(10);
console.log(a.p.x); // 10 (!)

There is a proposal for a future version of ES to support an "initializer" property on the descriptor, which would be evaluated during the constructor. That would give you the ability to control whether the value is set on the prototype or allocated per-instance.

For now you can work around this limitation by calling Object.defineProperty directly, as per the example in your comment. We will continue to investigate whether to relax this restriction in the future.

@sccolbert

This comment has been minimized.

Show comment
Hide comment
@sccolbert

sccolbert Sep 3, 2015

@rbuckton thanks! Are you guys in talks with Babel to converge on the same semantics? I think that's the most important thing.

sccolbert commented Sep 3, 2015

@rbuckton thanks! Are you guys in talks with Babel to converge on the same semantics? I think that's the most important thing.

@JsonFreeman

This comment has been minimized.

Show comment
Hide comment
@JsonFreeman

JsonFreeman Sep 4, 2015

Contributor

Why would it be useful to use a decorator in order to specify a value for a particular instance? Isn't that what the property initializer is for?

Contributor

JsonFreeman commented Sep 4, 2015

Why would it be useful to use a decorator in order to specify a value for a particular instance? Isn't that what the property initializer is for?

@sccolbert

This comment has been minimized.

Show comment
Hide comment
@sccolbert

sccolbert Sep 4, 2015

My use case is more complicated that the example. I'm actually using the decorator to define a getter which returns an object bound to the this context of the property, so that methods on said object have access to the instance on which it was defined.

You can think of this as emulating the descriptor protocol in Python, where accessing method bar defined on class Foo through instance foo (i.e. foo.bar) invokes the __get__ method of the function which returns a BoundMethod. When that object is called, the underlying function is invoked with self as the first argument, which in this case is foo. Javascript doesn't have this concept, which is why we have to pass around thisArg and call function.bind all over the place.

For my case, I'm not defining methods, but a type-safe signal. Here is the decorator:
https://github.com/phosphorjs/phosphor-signaling/blob/1.0.1/src/index.ts#L144

When the property is accessed, it returns an implementation of ISignal which is bound to the this context of the owner. This allows the signal to refer back to the instance on which it was defined:
https://github.com/phosphorjs/phosphor-signaling/blob/1.0.1/src/index.ts#L263

So in effect, I'm using the decorator as a shortcut for this verbose equivalent:

class Foo {
  valueChanged: ISignal<number>;
}

defineSignal(Foo.prototype, 'valueChanged');

sccolbert commented Sep 4, 2015

My use case is more complicated that the example. I'm actually using the decorator to define a getter which returns an object bound to the this context of the property, so that methods on said object have access to the instance on which it was defined.

You can think of this as emulating the descriptor protocol in Python, where accessing method bar defined on class Foo through instance foo (i.e. foo.bar) invokes the __get__ method of the function which returns a BoundMethod. When that object is called, the underlying function is invoked with self as the first argument, which in this case is foo. Javascript doesn't have this concept, which is why we have to pass around thisArg and call function.bind all over the place.

For my case, I'm not defining methods, but a type-safe signal. Here is the decorator:
https://github.com/phosphorjs/phosphor-signaling/blob/1.0.1/src/index.ts#L144

When the property is accessed, it returns an implementation of ISignal which is bound to the this context of the owner. This allows the signal to refer back to the instance on which it was defined:
https://github.com/phosphorjs/phosphor-signaling/blob/1.0.1/src/index.ts#L263

So in effect, I'm using the decorator as a shortcut for this verbose equivalent:

class Foo {
  valueChanged: ISignal<number>;
}

defineSignal(Foo.prototype, 'valueChanged');
@Koloto

This comment has been minimized.

Show comment
Hide comment
@Koloto

Koloto Sep 4, 2015

@rbuckton

Decorators are not allowed when targeting ES3

Why? I can't see anything that would prevent the use of __decorate in ES3.

Koloto commented Sep 4, 2015

@rbuckton

Decorators are not allowed when targeting ES3

Why? I can't see anything that would prevent the use of __decorate in ES3.

@cybrown

This comment has been minimized.

Show comment
Hide comment
@cybrown

cybrown Sep 4, 2015

It uses the property descriptor, which is not available in ES3
Le 4 sept. 2015 2:03 PM, "Koloto" notifications@github.com a écrit :

@rbuckton https://github.com/rbuckton

Decorators are not allowed when targeting ES3

Why? I can't see anything that would prevent the use of __decorate in ES3.


Reply to this email directly or view it on GitHub
#2249 (comment)
.

cybrown commented Sep 4, 2015

It uses the property descriptor, which is not available in ES3
Le 4 sept. 2015 2:03 PM, "Koloto" notifications@github.com a écrit :

@rbuckton https://github.com/rbuckton

Decorators are not allowed when targeting ES3

Why? I can't see anything that would prevent the use of __decorate in ES3.


Reply to this email directly or view it on GitHub
#2249 (comment)
.

@DavidSouther

This comment has been minimized.

Show comment
Hide comment
@DavidSouther

DavidSouther Sep 4, 2015

Contributor

@sccolbert You might do better for that with ES6 proxies rather than property decorators.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

Contributor

DavidSouther commented Sep 4, 2015

@sccolbert You might do better for that with ES6 proxies rather than property decorators.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy

@sccolbert

This comment has been minimized.

Show comment
Hide comment
@sccolbert

sccolbert Sep 4, 2015

@DavidSouther browsers don't support Proxies yet :(

sccolbert commented Sep 4, 2015

@DavidSouther browsers don't support Proxies yet :(

@Koloto

This comment has been minimized.

Show comment
Hide comment
@Koloto

Koloto Sep 4, 2015

@cybrown Yes, it uses the property descriptor for methods and accessors. I tested on plain properties (fields w/o accessors) only. But it seems that decorators can be allowed in ES3 for properties (w/o accessors) and classes. It would be helpful.

Koloto commented Sep 4, 2015

@cybrown Yes, it uses the property descriptor for methods and accessors. I tested on plain properties (fields w/o accessors) only. But it seems that decorators can be allowed in ES3 for properties (w/o accessors) and classes. It would be helpful.

@Koloto

This comment has been minimized.

Show comment
Hide comment
@Koloto

Koloto Sep 4, 2015

And a fake property descriptor can be used for methods when targeting ES3. Something like { writable: true, enumerable: true, configurable: true }. So I can't see any reason to not support ES3.

Koloto commented Sep 4, 2015

And a fake property descriptor can be used for methods when targeting ES3. Something like { writable: true, enumerable: true, configurable: true }. So I can't see any reason to not support ES3.

@JsonFreeman

This comment has been minimized.

Show comment
Hide comment
@JsonFreeman

JsonFreeman Sep 5, 2015

Contributor

@sccolbert I see. That makes sense. Incidentally, TypeScript is working on improved this typing for functions and methods. I wonder if that would be of any help here. Although I suppose typing is not the issue for you, it's runtime semantics.

Contributor

JsonFreeman commented Sep 5, 2015

@sccolbert I see. That makes sense. Incidentally, TypeScript is working on improved this typing for functions and methods. I wonder if that would be of any help here. Although I suppose typing is not the issue for you, it's runtime semantics.

@sccolbert

This comment has been minimized.

Show comment
Hide comment
@sccolbert

sccolbert Sep 5, 2015

@JsonFreeman Improved this typing sounds intriguing for some of my other use cases. Do you have any more info on that?

sccolbert commented Sep 5, 2015

@JsonFreeman Improved this typing sounds intriguing for some of my other use cases. Do you have any more info on that?

@JsonFreeman

This comment has been minimized.

Show comment
Hide comment
@JsonFreeman

JsonFreeman Sep 5, 2015

Contributor

I think the most developed discussion on this typing is at #3694.

Contributor

JsonFreeman commented Sep 5, 2015

I think the most developed discussion on this typing is at #3694.

@sccolbert

This comment has been minimized.

Show comment
Hide comment
@sccolbert

sccolbert commented Sep 5, 2015

Cheers!

@TakoLittle

This comment has been minimized.

Show comment
Hide comment
@TakoLittle

TakoLittle Sep 19, 2015

error TS1207: Decorators cannot be applied to multiple get/set accessors of the same name.

@get
public get myValue():any{...}

@set
public set myValue(value:any){...}

code above is not allowed even it make more sense compare to

@get
@set
public get myValue():any{...}

public set myValue(value:any){...}

TakoLittle commented Sep 19, 2015

error TS1207: Decorators cannot be applied to multiple get/set accessors of the same name.

@get
public get myValue():any{...}

@set
public set myValue(value:any){...}

code above is not allowed even it make more sense compare to

@get
@set
public get myValue():any{...}

public set myValue(value:any){...}
@mhegazy

This comment has been minimized.

Show comment
Hide comment
@mhegazy

mhegazy Sep 19, 2015

Contributor

Getter and setter are defined in one call to Obect.defineProperty. This rather a js quirk, the declaration of set and get though separate, really are the same property declaration. The error check in the compiler is to alert users when thinking of them separately; the decorators are applied only once to the property descriptor.

Contributor

mhegazy commented Sep 19, 2015

Getter and setter are defined in one call to Obect.defineProperty. This rather a js quirk, the declaration of set and get though separate, really are the same property declaration. The error check in the compiler is to alert users when thinking of them separately; the decorators are applied only once to the property descriptor.

@TakoLittle

This comment has been minimized.

Show comment
Hide comment
@TakoLittle

TakoLittle Sep 21, 2015

just wondering since the compiler can sense the get and set with same name and combine into single Object.defineProperty, why not also combine the decorator?
or perhaps an option flag to tell compiler to combine them, and leave a warning message instead of throwing error.

thank you

TakoLittle commented Sep 21, 2015

just wondering since the compiler can sense the get and set with same name and combine into single Object.defineProperty, why not also combine the decorator?
or perhaps an option flag to tell compiler to combine them, and leave a warning message instead of throwing error.

thank you

@rbuckton

This comment has been minimized.

Show comment
Hide comment
@rbuckton

rbuckton Sep 21, 2015

Member

@TakoLittle: The reason we don't do this today partially stems from how decorators are composed. Decorators follow the same principals as Mathematical function composition, where (fg)(x) is composed as f(g(x)). In the same sense, it can be thought that:

@F
@G
class X {}

Is approximately:

F(G(X))

The compositionality of decorators breaks down when you decorate both the getter and the setter:

class C {
  @F
  set X(value) {}

  @G
  get X() {}
}

How do F and G compose here? Is it based purely on document order (i.e. F(G(X)))? Are each set of decorators for the getter and the setter discrete, and then executed in document order (i.e. G(F(X)))? Do get and set imply any specific ordering (i.e. is the get always before the set or vice versa)? Until we're 100% certain the most consistent approach that doesn't surprise users, or have a well documented approach that is part of the decorators proposal with at least stage 2 or better acceptance within ECMA-262, we feel it is best to be more restrictive and error here as it allows us to relax that restriction at a later date without introducing a breaking change that could easily go unnoticed and possibly result in unexpected behaviors at runtime.

Member

rbuckton commented Sep 21, 2015

@TakoLittle: The reason we don't do this today partially stems from how decorators are composed. Decorators follow the same principals as Mathematical function composition, where (fg)(x) is composed as f(g(x)). In the same sense, it can be thought that:

@F
@G
class X {}

Is approximately:

F(G(X))

The compositionality of decorators breaks down when you decorate both the getter and the setter:

class C {
  @F
  set X(value) {}

  @G
  get X() {}
}

How do F and G compose here? Is it based purely on document order (i.e. F(G(X)))? Are each set of decorators for the getter and the setter discrete, and then executed in document order (i.e. G(F(X)))? Do get and set imply any specific ordering (i.e. is the get always before the set or vice versa)? Until we're 100% certain the most consistent approach that doesn't surprise users, or have a well documented approach that is part of the decorators proposal with at least stage 2 or better acceptance within ECMA-262, we feel it is best to be more restrictive and error here as it allows us to relax that restriction at a later date without introducing a breaking change that could easily go unnoticed and possibly result in unexpected behaviors at runtime.

@TakoLittle

This comment has been minimized.

Show comment
Hide comment
@TakoLittle

TakoLittle Sep 21, 2015

@rbuckton thank you so much for detailed explanation
TS team great work!! ^^d

TakoLittle commented Sep 21, 2015

@rbuckton thank you so much for detailed explanation
TS team great work!! ^^d

@shiwano shiwano referenced this issue Oct 31, 2015

Closed

Support for new features of TypeScript. #4

11 of 11 tasks complete

@mhegazy mhegazy added the Committed label Dec 9, 2015

@mhegazy mhegazy added ES Next and removed ES7 labels Feb 4, 2016

@mhegazy mhegazy added Fixed and removed Spec labels Feb 20, 2016

@mhegazy mhegazy closed this Feb 20, 2016

@omeid

This comment has been minimized.

Show comment
Hide comment
@omeid

omeid Feb 20, 2016

Where is the documentation for this? and care to link the implementation commit?

Thanks.

omeid commented Feb 20, 2016

Where is the documentation for this? and care to link the implementation commit?

Thanks.

@EisenbergEffect

This comment has been minimized.

Show comment
Hide comment
@EisenbergEffect

EisenbergEffect Feb 20, 2016

@mhegazy What is the status on the implementation of the latest version of the spec. I understand there are some changes there.

EisenbergEffect commented Feb 20, 2016

@mhegazy What is the status on the implementation of the latest version of the spec. I understand there are some changes there.

@mhegazy

This comment has been minimized.

Show comment
Hide comment
@mhegazy

mhegazy Feb 22, 2016

Contributor

This issue tracked the original version of the proposal. since this is completed we are closing this issue. for any updates to the spec, we will log new issues and outline all the breaking changes. I do not think the proposal is at a place now to be ready for us to jump on it. We are working closely with @wycats on the new proposal.

Contributor

mhegazy commented Feb 22, 2016

This issue tracked the original version of the proposal. since this is completed we are closing this issue. for any updates to the spec, we will log new issues and outline all the breaking changes. I do not think the proposal is at a place now to be ready for us to jump on it. We are working closely with @wycats on the new proposal.

@mhegazy

This comment has been minimized.

Show comment
Hide comment
Contributor

mhegazy commented Feb 22, 2016

@EisenbergEffect

This comment has been minimized.

Show comment
Hide comment
@EisenbergEffect

EisenbergEffect Feb 23, 2016

@mhegazy Thank you for the update. I'd love to stay informed. When you create the new issue for the spec update, please link it here so I can be notified and follow. The Aurelia community makes heavy use of decorators and we'll want to synchronize with both TypeScript and Babel on the update. Again, thanks for the great work the TS team is doing!

EisenbergEffect commented Feb 23, 2016

@mhegazy Thank you for the update. I'd love to stay informed. When you create the new issue for the spec update, please link it here so I can be notified and follow. The Aurelia community makes heavy use of decorators and we'll want to synchronize with both TypeScript and Babel on the update. Again, thanks for the great work the TS team is doing!

@whitecolor

This comment has been minimized.

Show comment
Hide comment
@whitecolor

whitecolor Aug 27, 2016

Function decoration is need of course.
Are there also plans for decorating of other objects in the code?

whitecolor commented Aug 27, 2016

Function decoration is need of course.
Are there also plans for decorating of other objects in the code?

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.