tc39 / proposal-class-public-fields

Stage 2 proposal for public class fields in ECMAScript
https://tc39.github.io/proposal-class-public-fields/
487 stars 25 forks source link

Computed property name evaluation order #44

Open bakkot opened 8 years ago

bakkot commented 8 years ago

Consider

(class {
  [console.log(1)] = null;
  [console.log(2)](){}
  [console.log(3)] = null;
});

As spec'd, evaluating this class expression would print 2 1 3. This seems clearly wrong; computed property names should be evaluated in the order in which they appear.

bakkot commented 8 years ago

Also, we should be careful with the evaluation order of static properties. Consider:

class C {
  static [console.log('a')] = null;
  static a                  = console.log('b');
  static [console.log('c')] = null;
}

Should this print 'a', 'b', 'c', or 'a', 'c', 'b'? I believe the former (interleaving initializer evaluation with property name evaluation) is more correct, but whichever is chosen it should be deliberate.

allenwb commented 8 years ago

Consider:

class C {
  [console.log(1)] = console.log("during construction");
  static [console.log('a')] = console.log('b');
  [console.log(2)](){}
  static a                  = console.log('c');
  static [console.log(3)](){}
  static [console.log('d')] = null;
}

I believe the log order should be 1, 'a', 'b', 2, 'c', 3, 'd'. Which can be described as left-to-right/top-to-bottom, ignoring instance field initializers.

This can be accomplished in ClassDefinitionEvaluation by merging the logic of steps 24-32 into the loop of step 21. Basically, handle each ClassElement separately and in order.

You can probably simplify the conditional logic by defining PropertyDefinitonEvaluation logic for static and instance field ClassElements.

bterlson commented 8 years ago

Agree with OP, but IMO the model that makes most sense here (and presented to TC39 previously) is one where computed property keys are evaluated first, in order, followed by other evaluation steps such as evaluating static initializers. I hope we can make this true even for object literals (got tentative approval for this few meetings back pending investigations into the feasibility).

allenwb commented 8 years ago

Anything other than lexical order (left-to-right, top-to-bottom) seems completely arbitrary and hence much harder to learn and remember. Particularly since lexical order is the ordering that is applied almost everywhere else in JS. Such Inconsistencies is an indication of incoherent design.

What is the rationale for doing computed property keys out of that natural order?

bterlson commented 8 years ago

Computed property keys should be in order. Other expression positions may not be computed in order. This is obvious with instance field initializers which cannot be evaluated in order, so why would you expect static field initializers to be evaluated in order (edit: with respect to computed property keys, static field initializers should be initialized in order with respect to other static initializers)

allenwb commented 8 years ago

@bterlson

why would you expect static field initializers to be evaluated in order? Computed property keys and static field initializers are both computed during class definition time. So the natural ordering makes sense:

class C {
   [computedKey+console.log(1)];  //computing key part of class evaluation
   static A = console.log(2);  //computing static initializer value part of class evaluation
   static [computedKey+console.log(3)] = console.log(4);
                //both key and init value computation part of class eval
}

lexical ordering makes most sense (and is easiest to learn) for the above.

Only instance fields (and private) initializers are deferred until construction time so can't follow that order.

class C {
   [computedKey+console.log(1)];  //computing key part of class evaluation
   static A = console.log(2);  //computing static initializer value part of class evaluation
   [computedInstanceKey+console.log(3)  //computing key part of class evaluation
       = console.log("ctor-time 1");  //initializer evaluated during construction
   static [computedKey+console.log(4)] = console.log(5);
                //both key and init value computation part of class eval
   [computedInstanceKey+console.log(6)  //computing key part of class evaluation
       = console.log("ctor-time 2");  //initializer evaluated during construction
}

Any other ordering is more complicated and arbitrary. Hard to remember. Instance and instance keys interweaved or separate? Instance keys before static keys?? static keys before instance keys? static initializers before or after instance keys? What's the easy to learn remember rationale?

Left-to-right/top-to-bottom for separately for both class evaluation time and construction time is the simplest story.

bterlson commented 8 years ago

You have to remember something either way - either you remember that static initializers are evaluated in order interleaved with computed property evaluation unlike every other expression position in the class body, or you have to remember that, like any other expression position, the evaluation order is different from top-down-left-right (edit: and what that is).

IMO the proposal we discussed in TC39 earlier is easy to remember:

Step 1: the shape of the declaration is obtained by evaluating all computed property keys in order. Step 2: Static initializers are evaluated. Probably this can be worded even more generally to account for (hopefully possible) change to object literal evaluation order. Step 3...n: other expression positions are evaluated as needed/appropriate.

bakkot commented 8 years ago

@allenwb, while I personally agree with you, I understand that (part of) the motivation for evaluating static initializers after the rest of the class is so that they can refer to the class, e.g.:

class C {
  static singleton = new this;
  constructor() { ... }
  [prop] = val;
}

Personally I am opposed, and think that we shouldn't mess with the usual evaluation order without very strong reason.

@bterlson, what other expression positions are there / do you anticipate there being, in class bodies? Method bodies and field initializers are of course evaluated later, but I don't think that's surprising. Decorators would happen before the rest of the class, I suppose?

bterlson commented 8 years ago

@bakkot For decorators on methods/fields the decorator expression are evaluated in order interleaved with computed property expressions (it's part of the "create the shape of the class" step) but decorator functions are evaluated at various points depending on what you're decorating.