Add circle ci build badge
Similarly, class references within the class scope should be separate from class references in the declared scope (this not specific to statics or initializers).
class C {
static getter () { return C }
}
const { getter } = C
C = null
console.assert(getter() !== null)
Actual:
Assertion error
Expected:
(no error)
This a collateral error with the fix about the access to the class from members decorator. It is very complicate covert both situación, we need descompuse the class into the Experimental Transpiler and it is a very hard process. We need time for research what is the solution/cost balance.
Typescript does it using an alias for all internal references to the class. And by moving the definition of static initializers outside of the class body, they can access that alias from the declaration scope. They do something like:
let C = C_alias = class C {
// static field removed from class body
// other class members would also refer to the alias
getter () {
return C_alias; // was: `return C;`
}
};
C.fieldC = C_alias; // uses alias, not original class name
C = decorate(...) // if decorated, happens after
// ...
Well, we have been working on a different approach to provide a solution to class access from decorators before the class definition (member decorators) and after their definition (class and static decorators).
In the process of code analyzing for transpilation, we can identify whether the call to the decorator includes any reference to the class, either as a parameter or as part of the body of a inline function or as part of an expression. In these cases the transpilation process inserts an exception in the transpiled code.
Now, this code launch an error:
function getClass (fn) {
console.log(fn().name)
return function(value, context) {}
}
class A {
@getClass (() => A)
a() {}
}
but this code work perfectly:
function getClass (fn) {
console.log(fn().name)
return function(value, context) {}
}
class A {
@getClass (() => A)
static a() {}
}
Please, check our solution and give us your point of view.
The exception should only occur on access of that identifier prior to its initialization. Referring to it alone is not a problem. It's only when you try to evaluate the identifier that, if done before the binding is made, an error is thrown.
So for the first example, the error should occur inside the decorator (wrapper) function:
function getClass (fn) {
console.log(fn().name) // ERROR: An error thrown here (evaluating A in () => A)
return function(value, context) {}
}
class A {
@getClass (() => A)
a() {}
}
However, this should not throw
function getClass (fn) {
// A never accessed, no error
return function(value, context) {}
}
class A {
@getClass (() => A)
a() {}
}
Similarly, for statics, while this should be fine (second example)
function getClass (fn) {
console.log(fn().name) // A is initialized by this point, no error
return function(value, context) {}
}
class A {
@getClass (() => A)
static a() {}
}
This should throw
function getClass (fn) {
console.log(fn.name)
return function(value, context) {}
}
class A {
@getClass (A) // ERROR: an error thrown here (evaluating A)
static a() {}
}
This is because decorators are evaluated before they're called. Evaluation of member decorators (both static and instance) happen before the binding of the class to the class name identifier. For instance decorators (non-static), they're also called before that binding, but static decorators are not, only called after the class name binding is made. This is an ordering I was outlining in: https://github.com/tc39/proposal-decorators/issues/425#issuecomment-896963752
Another consideration is class expressions. These do not seem to work at all in the transpiler.
function noop () {}
customElements.define(
'my-element',
class MyElement extends HTMLElement {
@noop
static a() {}
}
)
Results in:
"Uncaught SyntaxError: Invalid or unexpected token"
In this case specifically the class name is not part of the outer scope so that local identifier is needed to refer to the class directly.
The exception should only occur on access of that identifier prior to its initialization. Referring to it alone is not a problem. It's only when you try to evaluate the identifier that, if done before the binding is made, an error is thrown.
Yes, is a mistake in the new implementation.
This is because decorators are evaluated before they're called. Evaluation of member decorators (both static and instance) happen before the binding of the class to the class name identifier. For instance decorators (non-static), they're also called before that binding, but static decorators are not, only called after the class name binding is made. This is an ordering I was outlining in: tc39/proposal-decorators#425 (comment)
I'm not sure about the order for static decorators. In my humble opinion the static decorator is called after the class build. I will try to clarify this point with @pzuraq.
I'm not sure about the order for static decorators. In my humble opinion the static decorator is called after the class build. I will try to clarify this point with @pzuraq.
Right, called after the class is defined, but not evaluated. All member decorators, instance and static, are evaluated at the same time before the class is accessible. Instance decorators are also called before the class is accessible, but static are called after. From the readme:
- Decorator expressions (the thing after the @) are evaluated interspersed with computed property names.
- Decorators are called (as functions) during class definition, after the methods have been evaluated but before the constructor and prototype have been put together.
Statics need access to the class for use cases like
class A {
static instance = new A()
}