diferenciate get and set and decorator
pabloalmunia opened this issue · 8 comments
We are founding a very special and curious case with getter/setter decorators. Let's suppose we have a general decorator for methods (ie a @trace
). We want to be able to apply this decorator only over the getter or the setter. Nevertheless, we don't kwon if the user programmer put our decorator over the setter or getter.
class T {
get x() {}
@trace
set x() {}
}
We have not any manner to find out where is placed the decorator, over the set
or over the get
, and as a result there is not mean to apply rightly the decorator.
Is it possible to get a indicator or another flag to kwon where the decorator is placed, whether over the getter or over the setter? The feature would be very useful in some cases.
I can see that the new behavior would be a regression for you. Can you give a little more context about why this situation comes up?
I can see that the new behavior would be a regression for you.
Into new README.md we can read:
@wrap
may be used on getters or setters, and applies to these individually.
As a result, our problem is resolved with the the @wrap
decorator.
class C {
@wrap(f) set prop(v) { }
}
is roughly equivalent to the following:
class C {
set prop(v) { }
}
const descr = Object.getOwnPropertyDescriptor(C.prototype, 'prop');
descr.set = f(C.prototype.method);
Object.defineProperty(C.prototype, 'prop', descr);
But, @register
is different and
class C {
@register(f) set prop(v) { }
}
is roughly equivalent to
class C {
set prop(v) { }
}
f(C, "prop");
In this case, we don't kwon if the decorator intentionally write over the setter or over the getter.
If we want to decorate the getter / setter pair with @wrap
, then two calls will occur, one for each method, but if we do the same with @register
the result is a bit strange.
class C {
@register(f) set prop(v) { }
@register(f) get prop() { }
}
is roughly equivalent to
class C {
set prop(v) { }
get prop() {}
}
f(C, "prop");
f(C, "prop");
Can you give a little more context about why this situation comes up?
Some generic decorators can be applied with full meaning to the getter and/or setter methods. It is the programmer who decides whether to apply this decorator on both or on only one of them. For example: @log
, @trace
, @performance
, @catch
, etc.
Other generic decorators only make sense about the getter method or about the setter method. @validate
or @throttle
applies to the setter method, but does not make any sense about the getter.
Finally, other generic decoratos has sense about the property and need the pair getter/setter. For example, @readonly
only about a setter method or only about a getter method does not make sense (we can not mark a getter or a setter as read-only, we do not have a specific descriptor for each of these methods that we can configure as' writable: false '). This decorator only makes sense if it deletes or locks the call to the setter and keeps access to the getter.
Therefore, we have all the possible cases and combinations and they all make sense.
It seems reasonable that any decorator applied to a getter or a setter both has access to the coalesced pair, and knows on which of the two it is applied.
That is definitely a possible design, but coalescing was a rather complicated part of the specification, and the use cases were unclear. In the January 2019 TC39 meeting, there was a general concern expressed that the decorators proposal was too big and complicated, so eliminating this aspect was an attempt to respond to that feedback. Note that a coalesced getter/setter in a class is just a concept that doesn't exist before this proposal. This issue is useful to understand some cases; any more times when you all can think of this coming up would be useful.
Note that a coalesced getter/setter in a class is just a concept that doesn't exist before this proposal.
If that's the case, then why make it so in this proposal? It's fairly clear that applying a decorator to the handlers of a property separately has merit.
I'd be interested to hear how the new decorators proposal works for this aspect of these use cases.
In particular, the new proposal lets the decorator know, in the context object, whether it was placed over a getter or setter. I think that should resolve this issue, right?
For the most part, if I'm understanding the current README.md, this should work. There are some curiosities though. In the comments about @set
not being possible, there is this:
- If a setter is inherited, it is possible to write a decorator for a field which specifically calls super getters and setters, rather than using the underlying storage.
I'm a little perplexed by this. If the original class definition looks like so:
class Base {
#a = "some constant";
get a() { return this.#a; }
set a(v) { this.#a = v; }
}
class Ex extends Base {
@inherited a = 2;
}
According to the new proposal, Ex::a
gets wrapped. If Ex::a
were a function, I'd understand this. However, if the purpose of @inherited
is to ensure that Ex::a
is bound to Base::a
, then it would mean replacing Ex::a
with an accessor. However, that also means that there is no longer any "underlying storage" that exists in Ex
save for what was inherited from Base
. This is not a wrapping but a replacement. So how do you go about writing such a function and ensure that the initialization value is passed along properly given that you can't have both value
and get/set
in a property declaration? Remember, the expected intention is that the initialization value is passed to the Base's accessor methods.