Decorator field/accessor initializer order
Chris Hewell Garrett
2023-05
- Field and accessor initializers run from innermost decorator to outermost decorator
- Getters/setters/methods are replaced from innermost decorator to outermost decorator
- This means that getters/setters/methods execute from outermost to innermost
- This makes it impossible currently to have initial values match assigned value for certain accessors
function minusTwo({ set }) {
return {
set(v) {
set.call(this, v - 2)
},
init(v) {
return v - 2;
}
}
}
function timesFour({ set }) {
return {
set(v) {
set.call(this, v * 4)
},
init(v) {
return v * 4;
}
}
}
class Foo {
@minusTwo @timesFour accessor bar = 5;
}
const foo = new Foo();
console.log(foo.bar); // 18
foo.bar = 5;
console.log(foo.bar); // 12
Why is this an issue now?
- Previously, TypeScript and Babel legacy did run initializers in the current order, innermost -> outermost
- However, they also assigned the value with [[Set]] semantics to the field, thus running it through all of the setters for the field
- The switch to [[Define]] semantics for fields + the redesign of accessors led to this being an issue that wasn't realized until it had been used in the ecosystem for some time
Proposed Solution 1
Reverse the order of initializers, run them from outermost -> innermost
- Conceptually, we are running the initializers as if the value is being [[Set]] on initialization, so it runs through them in the same order as method calls.
- This means initialization and method calls always execute from outermost to innermost
- Decorators still EVALUATE from innermost -> outermost. That does not change.
- Allows users to distinguish between initial value and updated value, for auto-accessors
class Foo {
@minusTwo @timesFour accessor bar = 5;
}
// 5 - 2 * 4 = 12
const foo = new Foo();
console.log(foo.bar); // 12
foo.bar = 5;
console.log(foo.bar); // 12
Proposed Solution 2
Restore the previous behavior, have setters called with initial value for auto-accessors only
- Most similar to existing behavior in the ecosystem, so less churn overall
- Can still distinguish between initial and updated value, but requires a flag to do so (e.g. via WeakSet)
- Only affects auto-accessors, field/method behavior remains the same
class Foo {
@minusTwo @timesFour accessor bar = 5;
}
// Initial value = 4 * 5 - 2 = 18
// Set value = Initial value - 2 * 4 = 64
const foo = new Foo();
console.log(foo.bar); // 64
foo.bar = 5;
console.log(foo.bar); // 12
Decorator field/accessor initializer order
By pzuraq
Decorator field/accessor initializer order
- 548