Skip to content

Commit

Permalink
Update the spec based on user feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
wycats committed Sep 21, 2015
1 parent 8b0d8c2 commit 024cb93
Show file tree
Hide file tree
Showing 4 changed files with 1,233 additions and 11 deletions.
35 changes: 24 additions & 11 deletions README.md
@@ -1,3 +1,6 @@
> This file is under active development. Refer to `interop/reusability.md` for the
> most up to date description.
# Summary

Decorators make it possible to annotate and modify classes and properties at
Expand All @@ -13,8 +16,8 @@ A decorator is:

* an expression
* that evaluates to a function
* that takes the target, name, and property descriptor as arguments
* and optionally returns a property descriptor to install on the target object
* that takes the target, name, and decorator descriptor as arguments
* and optionally returns a decorator descriptor to install on the target object

Consider a simple class definition:

Expand Down Expand Up @@ -49,30 +52,40 @@ Now, before installing the descriptor onto `Person.prototype`, the engine first
invokes the decorator:

```js
let descriptor = {
value: specifiedFunction,
let description = {
type: 'method',
initializer: () => specifiedFunction,
enumerable: false,
configurable: true,
writable: true
};

descriptor = readonly(Person.prototype, 'name', descriptor) || descriptor;
Object.defineProperty(Person.prototype, 'name', descriptor);
description = readonly(Person.prototype, 'name', description) || description;
defineDecoratedProperty(Person.prototype, 'name', description);

function defineDecoratedProperty(target, { initializer, enumerable, configurable, writable }) {
Object.defineProperty(target, { value: initializer(), enumerable, configurable, writable });
}
```

The decorator has the same signature as `Object.defineProperty`, and has an
opportunity to intercede before the relevant `defineProperty` actually occurs.
The has an opportunity to intercede before the relevant `defineProperty` actually occurs.

A decorator that precedes syntactic getters and/or setters operates on the
accessor descriptor:
A decorator that precedes syntactic getters and/or setters operates on an accessor description:

```js
class Person {
@nonenumerable
get kidCount() { return this.children.length; }
}

function nonenumerable(target, name, descriptor) {
let description = {
type: 'accessor',
get: specifiedGetter,
enumerable: true,
configurable: true
}

function nonenumerable(target, name, description) {
descriptor.enumerable = false;
return descriptor;
}
Expand Down
3 changes: 3 additions & 0 deletions INITIALIZER_INTEROP.md → interop/initializer.md
@@ -1,3 +1,6 @@
> This file is under active development. Refer to `interop/reusability.md` for the
> most up to date description.
This document is a high-level explainer of how property decorators
interoperate with the [class properties][class-properties] proposal.

Expand Down
157 changes: 157 additions & 0 deletions interop/private-state.md
@@ -0,0 +1,157 @@
## Interoperability With Private Slots

In principle, this system could be retrofitted to work nicely with true
privates with one additional abstraction.

For this exploration, let's assume that the syntax for private
declarations is `#name [= initializer]` and the syntax for private usage
is `#name` or `receiver.#name`.

```js
class Person {
@reader #first, #last;

constructor(first, last) {
#first = first;
#last = last;
this._first = first;
this._last = last;
}

@reader get #fullName() {
return `${#first} ${#last}`;
}
}
```

#### Usage

```js
let person = new Person("Dmitry", "Lomov");
person.first // "Dmitry"
person.last // "Lomov"
person.fullName // "Dmitry Lomov"
```

#### Desugaring

```js
const PERSON_SLOTS = new WeakMap();

class Person {
constructor(first, last) {
let slots = { first: undefined, last: undefined };
slots.first = first;
slots.last = last;
PERSON_SLOTS.set(this, slots);
}
}

let target = Person.prototype;

decorate('slot', target, [reader], Slot(PERSON_SLOTS, 'first', null));
decoratePrivate(target, 'last', [reader], Slot(PERSON_SLOTS, 'last', null));
decoratePrivate(target, 'fullName', [reader], Slot(PERSON_SLOTS, { get: function() { /* ... */ }));
```
#### Decorator Descriptor Extensions
The decorator descriptors for fields, methods, and accessors are extended with an
additional `slot` boolean (true for slots, false otherwise).
```diff
interface FieldDecoratorDescriptor<Class> extends DecoratorDescriptor<Class> {
type: string, // 'field'
enumerable: boolean, // default: true
configurable: boolean, // default: true
writable: boolean, // default: true
property: DataProperty<Class>,

+ // true for private fields (#name = expr), false for public fields (name = expr)
+ slot: boolean
}

interface MethodDecoratorDescriptor<Class> extends DecoratorDescriptor<Class> {
type: string, // 'method'
enumerable: boolean, // default: false
configurable: boolean, // default: true
writable: boolean, // default: true
property: MethodProperty<Class>,

+ // true for private methods (#name() {}), false for public methods (name() {})
+ slot: boolean
}

interface AccessorDecoratorDescriptor {
type: string, // 'accessor'
hint: string, // 'getter', 'setter', 'both'
enumerable: boolean, // default: true
wrtiable: boolean, // default: true
property: AccessorProperty<Class>,

+ // true for private accessors (get #name() {}), false for public accessors (get name() {})
+ slot: boolean
}
```
#### Updated Decorator Definition
With the addition of true private fields, we can update our decorator
definition to make it support true private fields as well as
"underscored" fake privates.
```js
function reader(target, descriptor) {
let property = descriptor.property;
let { slot, name } = property;

let publicName = slot ? name : descriptor.name.slice(1);

Object.defineProperty(target, publicName, {
enumerable: descriptor.enumerable,
configurable: descriptor.configurable,
get: function() { return property.get(this); }
});

// special meaning for object literal: { @reader _first }
if (descriptor.shorthand) delete descriptor.initializer;
}
```
#### Private State Utilities
```js
function decoratePrivate(target, name, decorators, descriptor) {
decorators.reduce((desc, decorator) => decorate(decorator, target, name, desc), descriptor)
}

function PrivateFieldDecoratorDescriptor(slots, name, initializer) {
return {
type: 'field',
private: PrivateLookup(slots, name)
initializer: initializer
}
}

function PrivateMethodDecoratorDescriptor(slots, name, initializer) {
return {
type: 'method',
private: PrivateLookup(slots, name),
initializer: initializer
}
}

function PrivateAccessorDecoratorDescriptor(slots, name, { get, set }) {
let type;

if (get && set) type = 'accessor';
else if (get) type = 'getter';
else type = 'setter';

return { type, private: PrivateLookup(slots, name), get, set };
}

function PrivateLookup(slots, name) {
return { for: (self) => slots.get(self)[name] };
}
```

0 comments on commit 024cb93

Please sign in to comment.