Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Normative: Add initializer callback for side effects #165

Merged
merged 7 commits into from
Nov 8, 2018
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 57 additions & 12 deletions spec.html
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ <h1>The ElementDescriptor Specification Type</h1>
<tr> <th>Field Name</th> <th>Value</th> </tr>
</thead>
<tbody>
<tr> <td>[[Kind]]</td> <td>One of `"method"` or `"field"`</td> </tr>
<tr> <td>[[Kind]]</td> <td>One of `"method"`, `"field"` or `"initializer"`</td> </tr>
<tr> <td>[[Key]]</td> <td>A Property Key or %PrivateName% object</td> </tr>
<tr> <td>[[Descriptor]]</td> <td>A Property Descriptor</td> </tr>
<tr> <td>[[Placement]]</td> <td>One of `"static"`, `"prototype"`, or `"own"`</td> </tr>
Expand Down Expand Up @@ -188,6 +188,16 @@ <h1>The ElementDescriptor Specification Type</h1>
<li>_element_.[[Descriptor]].[[Configurable]] is *false*.</li>
</ul>
</li>
<li>
If _element_.[[Kind]] is `"initializer", then
<ul>
<li>_element_.[[Key]] absent.</li>
<li>_element_.[[Descriptor]] is absent.</li>
<li>_element_.[[Placement]] is `"own"`.</li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might consider allowing "static" as the decorator way of doing https://github.com/tc39/proposal-class-static-block

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree; static should be allowed here since they also can have initializers

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see this as a way towards a static block. This is just a way that a decorator can add extra code to run. But decorators are already able to run code at the end of when a class is executing, in the finisher callback. We could allow this out of consistency (it would have slightly different timing from finishers), but I don't see a use case.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A use case is the same as for "own" - being able to invoke a setter on a superclass (in this case, on a superclass's constructor that had, say, static set foo(v) {})

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ljharb What would be the problem with using a finisher for that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Certainly you could use a finisher for that - but then you'd be forced to use a class-level decorator to change the semantics of a specific field, so instead of:

class C {
  @useSet static a = x;
  static b = y;
  @useSet static c = z;
}

you'd need to have:

@useSet('a')
@useSet('c')
class C {
  static a = x;
  static b = y;
  static c = z;
}

or:

@useSet('a', 'c')
class C {
  static a = x;
  static b = y;
  static c = z;
}

both of which are decidedly less ergonomic and elegant.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see why that would be the case. You can use a finisher on a static field declaration too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ljharb, wait, but you can use finisher with a field or a method decorator. It is not required to use class-level decorators to activate finishers.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh, that wasn't clear to me. In that case - instead of this "initializer" approach, is there a reason not to allow finishers on "own" fields, to provide an "instance finisher"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ljharb yeah, I came to the same idea in my comment below :)

<li>_element_.[[Initializer]] is present.</li>
<li>_element_.[[Decorators]] is absent.</li>
</ul>
</li>
</ul>
</p>
</emu-clause>
Expand Down Expand Up @@ -408,6 +418,27 @@ <h1>CoalesceClassElements ( _elements_ )</h1>
<emu-note>In the case of public class elements, coalescing corresponds in semantics to ValidateAndApplyPropertyDescriptor. Note that this algorithm only coalesces method and accessor declarations, and it leaves field declarations as is.</emu-note>
</emu-clause>

<emu-clause id="initialize-public-instance-elements" aoid="InitializeInstanceFields">
<h1>InitializeInstanceElements ( _O_, _constructor_ )</h1>

<emu-alg>
1. Assert: Type ( _O_ ) is Object.
1. Assert: Assert _constructor_ is an ECMAScript function object.
1. Let _elements_ be the value of _F_'s [[Elements]] internal slot.
1. For each item _element_ in order from _elements_,
1. If _element_.[[Placement]] is `"own"` and _element_.[[Kind]] is `"method"`,
1. Perform ? DefineClassElement(_O_, _element_).
1. For each item _element_ in order from _elements_,
1. If _element_.[[Placement]] is `"own"` and _element_.[[Kind]] is `"field"`,
1. Assert: _element_.[[Descriptor]] does not have a [[Value]], [[Get]] or [[Set]] slot.
1. Perform ? DefineClassElement(_O_, _element_).
1. <ins>If _element_.[[Kind]] is `"initializer"`,</ins>
1. <ins>Assert: _element_.[[Placement]] is `"own"`.</ins>
1. <ins>Perform ? Call(_element_.[[Initializer]], _O_).</ins>
1. Return.
</emu-alg>
</emu-clause>

</emu-clause>


Expand Down Expand Up @@ -563,7 +594,7 @@ <h1>Element Descriptors</h1>
<p>An <dfn>element descriptor</dfn> describes an element of a class or object literal and has the following shape:</p>
<pre><code class=typescript>
interface ElementDescriptor {
kind: "method" or "field"
kind: "method", "initializer" or "field"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
kind: "method", "initializer" or "field"
kind: "method", "initializer", or "field"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

key: String, Symbol or Private Name,
placement: "static", "prototype", or "own"
descriptor: PropertyDescriptor,
Expand Down Expand Up @@ -689,7 +720,7 @@ <h1>DecorateConstructor ( _elements_, _decorators_ )</h1>
1. Append _elementsAndFinisher_.[[Finisher]] to _finishers_.
1. If _elementsAndFinisher_.[[Elements]] is not *undefined*,
1. Set _elements_ to the concatenation of _elementsAndFinisher_.[[Elements]] and _privateElements_.
1. If there are two class elements _a_ and _b_ in _elements_ such that _a_.[[Key]] is _b_.[[Key]] and _a_.[[Placement]] is _b_.[[Placement]], throw a *TypeError* exception.
1. If there are two class elements _a_ and _b_ in _elements_ such that _a_.[[Kind]] is not `"initializer"` and _b_.[[Kind]] is not `"initializer"`, _a_.[[Key]] is _b_.[[Key]], and _a_.[[Placement]] is _b_.[[Placement]], throw a *TypeError* exception.
Copy link
Member

@nicolo-ribaudo nicolo-ribaudo Nov 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be made more readable? (I don't know if it valid from a spec-grammar point of view)

1. If there are two class elements _a_ and _b_ in _elements_ such that all the following conditions are true:
  1. _a_.[[Kind]] is not `"initializer"`
  1. _b_.[[Kind]] is not `"initializer"`
  1. _a_.[[Key]] is _b_.[[Key]]
  1. _a_.[[Placement]] is _b_.[[Placement]]
1. Then throw a *TypeError* exception.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, done

1. Return the Record { [[Elements]]: _elements_, [[Finishers]]: _finishers_ }.
</emu-alg>
</emu-clause>
Expand Down Expand Up @@ -744,12 +775,14 @@ <h1>FromElementDescriptor ( _element_ )</h1>
1. Let _desc_ be PropertyDescriptor{ [[Value]]: `"Descriptor"`, [[Writable]]: *false*, [[Enumerable]]: *false*, [[Configurable]]: *true* }.
1. Perform ! DefinePropertyOrThrow(_obj_, @@toStringTag, _desc_).
1. Perform ! CreateDataPropertyOrThrow(_obj_, `"kind"`, _element_.[[Kind]]).
1. Let _key_ be _element_.[[Key]].
1. If _key_ is a Private Name, set _key_ to ? PrivateNameObject(_key_).
1. Perform ! CreateDataPropertyOrThrow(_obj_, `"key"`, _key_).
1. If _element_.[[Kind]] is `"method"` or `"field"`,
1. Let _key_ be _element_.[[Key]].
1. If _key_ is a Private Name, set _key_ to ? PrivateNameObject(_key_).
1. Perform ! CreateDataPropertyOrThrow(_obj_, `"key"`, _key_).
1. Perform ! CreateDataPropertyOrThrow(_obj_, `"placement"`, _element_.[[Placement]]).
1. Perform ! CreateDataPropertyOrThrow(_obj_, `"descriptor"`, ! FromPropertyDescriptor(_element_.[[Descriptor]])).
1. If _element_.[[Kind]] is `"field"`,
1. If _element_.[[Kind]] is `"method"` or `"field"`,
1. Perform ! CreateDataPropertyOrThrow(_obj_, `"descriptor"`, ! FromPropertyDescriptor(_element_.[[Descriptor]])).
1. If _element_.[[Kind]] is `"field"` or `"initializer"`,
1. Let _initializer_ be _element_.[[Initializer]].
1. If _initializer_ is ~empty~, set _initializer_ to *undefined*.
1. Perform ! CreateDataPropertyOrThrow(_obj_, `"initializer"`, _initializer_).
Expand Down Expand Up @@ -784,23 +817,35 @@ <h1>ToElementDescriptor ( _elementObject_ )</h1>
1. If _kind_ is not one of `"method"` or `"field"`, throw a *TypeError* exception.
1. Let _key_ be ? Get(_elementObject_, `"key"`).
1. If _key_ has a [[PrivateName]] internal slot, set _key_ to _key_.[[PrivateName]].
1. If _kind_ is `"initializer"`,
1. If _key_ is not *undefined*, throw a *TypeError* exception.
littledan marked this conversation as resolved.
Show resolved Hide resolved
1. Otherwise, set _key_ to ? ToPropertyKey(_key_).
1. Let _placement_ be ? ToString(? Get(_elementObject_, `"placement"`)).
1. If _placement_ is not one of `"static"`, `"prototype"`, or `"own"`, throw a *TypeError* exception.
1. Let _descriptor_ be ? ToPropertyDescriptor(? Get(_elementObject_, `"descriptor"`)).
1. If _kind_ is `"initializer"`,
1. If _placement_ is not `"own"`, throw a *TypeError* exception.
1. Let _descriptor_ be ? Get(_elementObject_, `"descriptor"`)
1. If _kind_ is `"initializer"`,
1. If _descriptor_ is not *undefined*, throw a *TypeError* exception.
1. Otherwise,
1. Set _descriptor_ to ? ToPropertyDescriptor(_descriptor_).
1. Let _initializer_ be ? Get(_elementObject_, `"initializer"`).
1. Let _elements_ be ? Get(_elementObject_, `"elements"`).
1. If _elements_ is not *undefined*, throw a *TypeError* exception.
1. If _kind_ not `"field"`,
1. If _kind_ not `"field"` or `"initializer"`,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: "If kind is not"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

1. If _initializer_ is not *undefined*, throw a *TypeError* exception.
1. If _key_ is a Private Name,
1. If _descriptor_.[[Enumerable]] is *true*, throw a *TypeError* exception.
1. If _descriptor_.[[Configurable]] is *true*, throw a *TypeError* exception.
1. If _placement_ is `"prototype"`, throw a *TypeError* exception.
1. If _kind_ is `"field"`,
1. If _descriptor_ has a [[Get]], [[Set]] or [[Value]] internal slot, throw a *TypeError* exception.
1. Let _element_ be the ElementDescriptor { [[Kind]]: _kind_, [[Key]]: _key_, [[Placement]]: _placement_, [[Descriptor]]: _descriptor_ }.
1. If _kind_ is `"field"`, set _element_.[[Initializer]] to _initializer_.
1. Let _element_ be the ElementDescriptor { [[Kind]]: _kind_, [[Placement]]: _placement_ }.
1. If _kind_ is `"method"` or `"field",
1. Set _element_.[[Key]] to _key_.
1. Set _element_.[[Descriptor]] to _descriptor_.
1. If _kind_ is `"field"` or `"initializer"`,
1. Set _element_.[[Initializer]] to _initializer_.
1. Return _element_.
</emu-alg>
</emu-clause>
Expand Down