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
Show file tree
Hide file tree
Changes from all commits
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
16 changes: 16 additions & 0 deletions METAPROGRAMMING.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,22 @@ A class descriptor with the following properties:

Note: finishers run once per class (at class creation time), not once per instance.

### Initializers

The `extras` array can also contain stand-alone initializers, of the following form:

```js
{
kind: "initializer"
placement: "static", "prototype" or "own",
initializer() { /* ... */ }
}
```

The initializers added here are just like field initializers, except that they don't result in defining a field.

These can be used, e.g., to make a decorator which defines fields through `[[Set]]` rather than `[[DefineOwnProperty]]`, or to store the field in some other place.

## Examples

The three decorators from [README.md](README.md) could be defined as follows:
Expand Down
110 changes: 93 additions & 17 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,15 @@ <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_.[[Initializer]] is present.</li>
<li>_element_.[[Decorators]] is absent.</li>
</ul>
</li>
</ul>
</p>
</emu-clause>
Expand Down Expand Up @@ -408,6 +417,54 @@ <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"` and _element_.[[Placement]] is `"own"`,</ins>
1. <ins>Let _res_ be ? Call(_element_.[[Initializer]], _O_).</ins>
1. <ins>If _res_ is not *undefined*, throw a *TypeError* exception.</ins>
1. Return.
</emu-alg>
</emu-clause>

<emu-clause id="initialize-class-elements" aoid="InitializeClassElements">
<h1>InitializeClassElements(_F_, _proto_)</h1>

<emu-alg>
1. Assert: Type(_F_) is Object and Type(_proto_) is Object.
1. Assert: _F_ is an ECMAScript function object.
1. Assert: _proto_ is ! Get(_F_, `"prototype"`).
1. Let _elements_ be the value of _F_'s [[Elements]] internal slot.
1. For each item _element_ in order from _elements_,
1. Assert: If _element_.[[Placement]] is `"prototype"` <del>or `"static"`</del>, then _element_.[[Key]] is not a Private Name.
1. If _element_.[[Kind]] is `"method"`,
1. Let _receiver_ be _F_ if _element_.[[Placement]] is `"static"`, else let _receiver_ be _proto_.
1. Perform ? DefineClassElement(_receiver_, _element_).
1. For each item _element_ in order from _elements_,
1. If _element_.[[Kind]] is `"field"` and _element_.[[Placement]] is `"static"` or `"prototype"`,
1. Assert: _element_.[[Descriptor]] does not have a [[Value]], [[Get]] or [[Set]] slot.
1. Let _receiver_ be _F_ if _element_.[[Placement]] is `"static"`, else let _receiver_ be _proto_.
1. Perform ? DefineClassElement(_receiver_, _element_).
1. <ins>If _element_.[[Placement]] is `"prototype"` or `"static"` and _element_.[[Kind]] is `"initializer"`,</ins>
1. <ins>Let _receiver_ be _F_ if _element_.[[Placement]] is `"static"`, else let _receiver_ be _proto_.</ins>
1. <ins>Let _res_ be ? Call(_element_.[[Initializer]], _receiver_).</ins>
1. <ins>If _res_ is not *undefined*, throw a *TypeError* exception.</ins>
1. Return.
</emu-alg>
<emu-note type=editor>Value properties are added before initializers so that all methods are visible from all initializers</emu-note>
</emu-clause>

</emu-clause>


Expand Down Expand Up @@ -563,7 +620,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"
key: String, Symbol or Private Name,
placement: "static", "prototype", or "own"
descriptor: PropertyDescriptor,
Expand Down Expand Up @@ -689,7 +746,12 @@ <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 all of the following are true:
Copy link
Member

Choose a reason for hiding this comment

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

This reads a bit awkwardly; maybe it should be in an abstract operation?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is following @nicolo-ribaudo 's suggestion above. I think the factoring might be awkward too; let's work out these editorial issues during Stage 3.

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.
1. Return the Record { [[Elements]]: _elements_, [[Finishers]]: _finishers_ }.
</emu-alg>
</emu-clause>
Expand Down Expand Up @@ -744,12 +806,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 @@ -781,26 +845,38 @@ <h1>ToElementDescriptor ( _elementObject_ )</h1>
<emu-alg>
1. Assert: _elementObject_ is an ECMAScript language value.
1. Let _kind_ be ? ToString(? Get(_elementObject_, `"kind"`)).
1. If _kind_ is not one of `"method"` or `"field"`, throw a *TypeError* exception.
1. If _kind_ is not one of `"initializer"`, `"method"`, or `"field"`, throw a *TypeError* exception.
1. Let _key_ be ? Get(_elementObject_, `"key"`).
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. If _key_ has a [[PrivateName]] internal slot, set _key_ to _key_.[[PrivateName]].
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. 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 _initializer_ is not *undefined*, 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. 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 _initializer_ be ? Get(_elementObject_, `"initializer"`).
1. If _kind_ is `"initializer"`,
1. If _initializer_ is *undefined*, throw a *TypeError* exception
1. If _kind_ is `"method"`,
1. If _initializer_ is not *undefined*, throw a *TypeError* exception.
1. Let _elements_ be ? Get(_elementObject_, `"elements"`).
1. If _elements_ is not *undefined*, throw a *TypeError* exception.
Copy link
Member

Choose a reason for hiding this comment

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

Should this use a HasProperty, instead of a Get, since it’s just checking existence? Or is the intention to allow an explicit undefined property - and if so, that seems like it’d be an error, so why allow it?

Copy link
Member Author

Choose a reason for hiding this comment

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

The Get matches the use of Get when actually using the elements in a class decorator, per POLS.

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