Skip to content
This repository has been archived by the owner on Jan 25, 2022. It is now read-only.

Evaluation order for static properties vs class declaration #40

Closed
loganfsmyth opened this issue Jun 23, 2016 · 8 comments
Closed

Evaluation order for static properties vs class declaration #40

loganfsmyth opened this issue Jun 23, 2016 · 8 comments

Comments

@loganfsmyth
Copy link

Raised over in loganfsmyth/babel-plugin-transform-decorators-legacy#17 but I figured I'd file a proper bug to get a solid answer. What is the expected behavior of

class Foo {
  static me = Foo;
}

with the current spec, it looks like this would be a TDZ error because the Foo property assignment would be evaluated as step 24.v.c.a of ClassDefinitionEvaluation, and the Foo binding does not get initialized until step 26.i.

Babel 6's class property implementation currently evaluates me = Foo after Foo has been initialized, but my transform-decorators-legacy plugin will result in Foo being uninitialized at this point, and the difference between the two can cause trouble for users since code like this will work with standard Babel 6 until they enable decorators, at which point it will break.

@uMaxmaxmaximus
Copy link

uMaxmaxmaximus commented Jun 23, 2016

class User {
  static schema = {lol: User} // User is undedined (is a bug)
}

It seems to me improper specification and should not follow it. And I think that in this way many believe. but where to go. I have to write a decorator such as:

@schema({
 foo: User
})
class User {}

but this not works

works just ugly way

class User {}
User.schema = {lol: User}

But what if I want to create a class in expression?

return (function(){
  class User {}
  User.schema = {lol: User}
  return User
})()

It is obvious that this is a bug, and the specification is not true
There is no reason to make such behavior.

@jeffmo
Copy link
Member

jeffmo commented Jun 23, 2016

Copying the following from the referenced issue for posterity:

The class properties spec states (Step 24.ii.a) that the initializer scope inherits from the class-body scope; And the ES6 spec states (Step 4) that the class body scope does have access to the class binding (no TDZ). So indeed, the code above should work as expected.

I am planning to write out proper spec text for the next TC39 meeting at the end of July, so hopefully this will reduce confusion around some of these things.

@jeffmo jeffmo closed this as completed Jun 23, 2016
@jeffmo
Copy link
Member

jeffmo commented Jun 23, 2016

Oh, and you're right in pointing out that 24.v.c.a is flawed!

Things have changed a bit such that static class property initializers don't actually evaluate until the end of the class definition evaluation process -- so the spec text over in the README is a little outdated there. That's my fault, apologies!

@loganfsmyth
Copy link
Author

@jeffmo I think you may be misunderstanding what Step 4 does. That creates the binding in the class body scope, absolutely, but it does not give it a value, it leaves it uninitialized. The value of the class constructor F isn't known until step 14. Attempting to access the value of the binding before it is initialized results in an exception. The step

  • If className is not undefined, then
    • Perform classScopeEnvRec.InitializeBinding(className, F).

is the one that initializes the binding, and currently in the spec text that happens after the static values are evaluated. That line would have to be moved to be above the static property evaluation for it to be able to access the class name binding.

@jeffmo
Copy link
Member

jeffmo commented Jun 23, 2016

That creates the binding in the class body scope, absolutely, but it does not give it a value, it leaves it uninitialized.

You're right and this is why I followed up with the clarification that we've moved the step described in 24.v.c.a to the end of the procedure (i.e. after Step 23 that you're pointing out). Apologies for taking for granted that you knew about that tweak to the proposal.

@loganfsmyth
Copy link
Author

Okie doke, cool. I couldn't quite tell from the post above, my bad.

@loganfsmyth
Copy link
Author

An edge case came to mind that I don't think the decorator spec specifies well, but I'm curious for your thoughts. Given a case like this:

@(cls => class New extends cls {})
class Base {
  static get current(){ return Base; }
  static original = Base;
}

Ignoring the class property for a second, the decorator spec doesn't make it clear if Base.current === Base (from outside). Clearly the Base binding outside the class declaration would resolve to the New child class, but is Base inside New or the original Base?

If the expectation is that Base inside the class body would be the decorator return value, this Base.current === Base, this would potentially mean that Base.original !== Base.current, because the class properties will have been evaluated before the decorator. This might be counter-intuitive to developers.

The other tough part there being that the current spec defines the internal class-name binding to be const, whereas this would require it to be set once and then updated again after decorator evaluation.

This is all moot if the decorator return value doesn't update the class body binding, but that has its own edge cases for users.

@jeffmo
Copy link
Member

jeffmo commented Jun 27, 2016

I think @wycats has some solutions for this scenario. He'd included general thoughts in the previous TC39 meeting, so I'll defer to him here for details

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants