Closed rbuckton closed 8 years ago
It seems it is not currently possible to decorate parameter properties. Can they be supported?
Parameter properties are thing like constructor(private prop: Type)
, where a property (field) and a parameter is declared together. The problem is both of them can be decoreated, so some imaginative syntax may have to be invented.
Question: Can the decorators be used to modify the behavior of a plain object method, like...
var isCreatorUser = () => { /* code that verifies if user is the creator */ },
isAdminUser = () => { /* code that verifies if user is admin */ },
var routeConfig = {
get() {},
@isCreatorUser patch() {},
@isAdminUser @isCreatorUser delete() {}
};
... and if not, why? It will be really useful.
Not yet, but we are considering it. @rbuckton might have more details.
May I ask why decorator cannot change the decorated result type from the type being decorated?
For example, MethodDecorator can be alternatively proposed as below:
declare type MethodDecorator = <T, R>(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor<T>) => TypedPropertyDescriptor<R> | void;
So decorator can be more flexible to use and works somewhat like a macro. The type of decorated class / field can be updated correspondingly to decorator returning type, so that type safety is still kept.
I know that https://github.com/jonathandturner/brainstorming/blob/master/README.md#c4-defining-a-decorator has stated the returning type should be the same. But type validity
isn't a valid argument here, IMHO. First, decorators in JavaScript can alter the class/field type, so there is no compatibility issue between TS and JS. Secondly, decorators are applied statically, TS compiler can reason about the original field type and decorated field type at define site.
@HerringtonDarkholme I would say this needs to be tracked in a different suggestion. The main reason is the way the compiler reasons about types is through declarations. once a type is declared it can not change. adding type mutators in the mix means we need to resolve these as we are resolving the declaration which can get hairy.
@mhegazy so, can I understand this as a compromise for implementation ease, rather than a deliberate design?
According to this proposal, decorators restore the ability to run code at design time, while maintaining a declarative syntax. So the following code shall be valid in future's JavaScript, but not valid in today's TypeScript (however this might be accepted by TS one day, right?).
function SafeCtor(target) {
var safe = function(...args) {
var instance = Object.create(target.prototype)
target.call(instance, ...args)
return instance
}
safe.prototype = target.prototype
return safe
}
@SafeCtor
class Snake {
constructor(name) {
this.name = name
}
}
var snake = Snake('python')
alert(snake instanceof Snake)
I know the chances for supporting this feature is small. But I want to confirm this is not a similar story like structural
vs nominal
typing.
The current check is that you have to return something from your decorator that is assignable to the target. the missing part is we are not capturing any additions to the type. I do not think we have an idea on how to do that, but we have not really thought about it either. so i think it is a fair suggestion, but the cost to implement it may be very high.
Ideally, I think the type returned by the decorator should be identical to the target. There's no telling whether you will use the decorated type in a source or target position in an assignment.
Although I guess really we should merge the decorator return type and the target, kind of like a mixin.
Just some additional functionality idea. Since TypeScript compiler output is JavaScript, there are some similar languages for Java, like Xtend
You can learn about some interesting ideas on this project. Look for Active Annotations "Active annotations allow developers to participate in the translation process of Xtend source code to Java code via library"
Controlling the TypeScript output via decorators will be a really nice feature!
Decorators for interfaces, should be possible.
@shumy The problem is that interfaces do not exist at runtime in any way, and decorators run purely at runtime.
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;
}
@mhegazy would you happen to have any insight on my comment above?
@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.
@rbuckton thanks! Are you guys in talks with Babel to converge on the same semantics? I think that's the most important thing.
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?
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');
@rbuckton
Decorators are not allowed when targeting ES3
Why? I can't see anything that would prevent the use of __decorate
in ES3.
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 https://github.com/Microsoft/TypeScript/issues/2249#issuecomment-137717517 .
@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
@DavidSouther browsers don't support Proxies yet :(
@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.
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.
@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.
@JsonFreeman Improved this typing sounds intriguing for some of my other use cases. Do you have any more info on that?
I think the most developed discussion on this typing is at #3694.
Cheers!
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){...}
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.
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: 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 (f ∘ g)(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.
@rbuckton thank you so much for detailed explanation TS team great work!! ^^d
Where is the documentation for this? and care to link the implementation commit?
Thanks.
@mhegazy What is the status on the implementation of the latest version of the spec. I understand there are some changes there.
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.
@omeid, you can find documentation at https://github.com/Microsoft/TypeScript-Handbook/blob/master/pages/Decorators.md
@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!
Function decoration is need of course. Are there also plans for decorating of other objects in the code?
desugars to:
Methods
desugars to:
Static method
desugars to:
Properties
desugars to:
Method/Accessor formal parameter
desugars to:
Where the __decorate is defined as:
Decorator signatures:
A valid decorator should be:
Notes:
var x = dec(function () { });