Closed rbuckton closed 8 years ago
Excuse me from what I understand of the spec, we won't be able to do:
@F
function test() {
}
Am I right ?
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; // ???
}
}
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?
What is the rule for refining the interpretation of CoverMemberExpressionSquareBracketsAndComputedPropertyName?
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?
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.
For the decorated exports, what is [lookahead ≠ @]
for? Can HoistableDeclaration and ClassDeclaration actually start with a @
?
This is a dup of #1557
It's not really a dupe, as #1557 was for a different design. This issue is for the decorators design being implemented now.
My mistake.
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
Implementation added in PR #2399
Updates: Proposal updated, and added link to the @wycats ES7 JavaScript decorators.
saddened that it became a class only thing ... Also what about ambiant decorator did they get out of the scope ?
@fdecampredon with your proposal for functions, seems like you still lose the hoisting.
@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 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)
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.
Let's see what the desired set of constraints seem to imply:
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.
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.
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:
While I don't like either of these, I don't even think anything else is possible.
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
}
Ugh! Javascript not getting any simplier anymore. :-)
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.
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
}
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 {
}
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;
}
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.
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
}
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)
}
}
});
});
}
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 I just updated the signature for ParameterDecorator
in #2635, which is now in master.
@rbuckton Thank you, I'll update that tomorow in my project.
Is it possible to have the name of the parameter in a ParameterDecorator ? This may be useful because I think that the name of the parameter might be half of the time the first argument of a ParameterDecorator. Moreover, it might be a solution to parameter name mangling by minifiers.
i've been asked to accomplish this:
@inject(A, B, "some key")
class C {
static injectMe: Array<any> = [];
constructor(a: A, b: B) {
}
}
function inject(...rest) {
return function(target): void {
target["inject"] = rest;
}
}
but without specifying the keys in the inject decorator, but instead having the decorators pick up the constructor's class functions automatically with something along these lines:
inject(@parameterTypes types:Function[]){ ... }
i'm looking for how to do what @jonathandturner describes here
@cmichaelgraham Yes having runtime type information (rtti as described in what was AtScript) would be awesome for that kind of usage, even in a completely separated feature such as introspection.
@cmichaelgraham look at https://github.com/Microsoft/TypeScript/pull/2589
@cmichaelgraham We are working on a proposal for ES7 to add a metadata API that will work together with decorators. #2589 adds experimental support for that proposal, but requires a separate polyfill be present to read the metadata.
@rbuckton @cmichaelgraham There was originally a design for special a TypeScript decorator that would tell the compiler to inject types into the decorator, based on the target being decorated. I think it was @parameterTypes
Am I correct in thinking that that feature is being removed in favor of the more general metadata api? If so, I would support that.
Can you talk a bit about the status of that with respect to TypeScript. Is that planned for the release of 1.5? If so, what will the compiler options look like? One thing that would be useful is to have the compiler automatically generate the type metadata for constructor signatures only.
@EisenbergEffect Decorators are part of the 1.5, you can start using it with our latest release 1.5-alpha.
As for meta data support. The desing has changed since the orignal proposal for @paramtypes
. the new desing is using Reflect.metada proposal. See #2589 for more details on how it works. also @rbuckton has a pollyfill to consume the metadata here: https://github.com/rbuckton/reflectDecorators
@mhegazy I know that. See here: http://blog.durandal.io/2015/04/09/aurelia-update-with-decorators-ie9-and-more/ :smile:
I'm also familiar with the metadata proposal. I've been providing feedback on that. I just wanted to know if the original @parameterTypes
idea was being removed.
thanks @EisenbergEffect for sharing. :+1:
Yes. the only issue with @paramtypes
is it makes emit directed by the type system, and requiring global program information. This breaks scenarios for single-module transpilation (see #2499). The other option was to put it on call sites, which adds a lot of work on the decorator users instead of authors. So we went back to the drawing board and landed on the Reflect.metadata approach instead.
if you also looked at earlier versions of the proposal, we had to remove the ambient/design-time decorators for the same reason.
Thanks for clarifying. That all makes sense. Any idea if the Reflect-based approach will land in 1.5?
yes. it is currently in master. it should be available in the next release. it is currently an opt-in feature using the experimental flag --emitDecoratorMetadata
; and it only adds metadata to decorated entities.
"it only adds metadata to decorated entities" Can you expand on that idea? Is a special decorator involved? Or does it add it to anything with any decorator? In other words, if a developer uses Aurelia's inject
decorator, then will that trigger the compiler to generate the metadata?
if a developer uses Aurelia's inject decorator, then will that trigger the compiler to generate the metadata?
yes.
what i meant is:
@inject
class Foo {
constructor(a: number, b: string) {}
}
class Bar {
constructor(a: number, b: string) {}
}
with --emitDecoratorMetadata
the compiler will emit type metadata (i.e. call to reflect.metadata('desing:paramtypes', [Number, String])
) for Foo
but not Bar
.
That will work for us. We'll prepare support for the Reflect.metadata api for our next release, most likely next week. Thanks for the clarification!
so what is emitted for this?
@inject
class Foo {
constructor(a: A, b: B) {}
}
class Bar {
constructor(a: number, b: B) {}
}
would it be (for Foo):
reflect.metadata('desing:paramtypes', [A, B])
@cmichaelgraham Yes, that is roughly what is generated.
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 () { });