Private Static Fields Features: Stage 3#8205
Conversation
2824e86 to
1c43798
Compare
|
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/8935/ |
1c43798 to
b00fa81
Compare
|
We only handled private static fields for now, @tim-mc is going to help bring private static methods now. From there we'll be able to think about accessors... |
181e814 to
ff1203e
Compare
ff1203e to
69e5e2e
Compare
|
It feels like this PR is ready for review. I also would like some help for making that Travis build pass... |
|
|
||
| expect("bar" in Foo).toBe(false) | ||
| expect(Foo.test()).toBe(undefined) | ||
| expect(Foo.test()).toBe(undefined) |
There was a problem hiding this comment.
Some of these duplicated tests are weird to me expect(Foo.test()).toBe(undefined) - I know they were just copied over but is it supposed to be testing the static and instance methods or just remove it?
There was a problem hiding this comment.
Maybe it should be expect(() => Foo.test()).not.toThrow()?
There was a problem hiding this comment.
@hzoo, you're right, I'll go ahead and do a second review pass on those tests (was only checking for the output I was seeing). I'm also trying to run test-262 right now against it (having npm link issues) and I might add some relevant tests then
There was a problem hiding this comment.
Yeah a general thing we should do moving forward is run against test262, I know @leobalter/@rwaldron have done some work there. (@xtuc had https://github.com/xtuc/babel-test262 but we haven't used)
|
|
||
| helpers.classStaticPrivateFieldBase = () => template.program.ast` | ||
| export default function _classStaticPrivateFieldBase(receiver, classConstructor, privateClass) { | ||
| if (receiver !== classConstructor && receiver.constructor !== classConstructor) { |
There was a problem hiding this comment.
The receiver.constructor !== classConstructor shouldn't be necessary.
There was a problem hiding this comment.
Unfortunately, this seems necessary to be spec compliant. We should discuss that on slack.
| throw path.buildCodeFrameError( | ||
| "Static class fields are not spec'ed yet.", | ||
| ); | ||
| if (privateStaticNames.has(name)) { |
There was a problem hiding this comment.
Private names are shared between instance and static, ie, the following is an error:
class Example {
static #x;
#x;
}There was a problem hiding this comment.
Wasn't sure about this one, I'll make the change.
| var Foo = | ||
| /*#__PURE__*/ | ||
| function () { | ||
| "use strict"; |
There was a problem hiding this comment.
This is my mistake. This test case was meant to output class expressions, not compile classes to functions.
There was a problem hiding this comment.
That makes more sense indeed, I'll redo the whole test dir with that then!
There was a problem hiding this comment.
@jridgewell what should I do to convert tests to output class expressions?
| }; | ||
|
|
||
| // Traverses the class scope, handling private static name references. | ||
| const staticPrivatePropertyVisitor = { |
There was a problem hiding this comment.
We should be able to reuse privateNameVisitor. This is necessary because we have to use its inner traverser, too.
There was a problem hiding this comment.
Ok, I kept them separate to make sure I don't mess with your logic but indeed, it's better to change just a few things in your logic than to branch out an entirely new one!
| `; | ||
|
|
||
| helpers.classStaticPrivateFieldBase = () => template.program.ast` | ||
| export default function _classStaticPrivateFieldBase(receiver, classConstructor, privateClass) { |
There was a problem hiding this comment.
So there are a few issues here, and we can choose either of two ways to handle it:
- We can remove this
privateClassholder object forloosemode. - We can handle
get,set, andcallfor bothlooseandspecmode.
The first option would allow us to reuse the same code we already have for instance private fields.
If we continue to use a holder object, we have to handle get, set, and call operations on the holder, and use the receiver as the calling context for all those cases. This complicates the runtime for loose mode. But, we're gonna need to do that anyways for spec mode.
There was a problem hiding this comment.
This is super helpful, I had a hard time understanding the line between loose and spec.
| }; | ||
|
|
||
| const staticPrivatePropertyHandler = { | ||
| handle(member) { |
There was a problem hiding this comment.
So this doesn't handle get, set, and call operations. Given that all this is stored on a holder object, anything like ClassName.#x() will really have to be transformed to base(ClassName, ClassName, holder)['x'].call(ClassName). This currently just outputs base(ClassName, ClassName, holder)['x']().
There was a problem hiding this comment.
I think I just missed something about how the handlers are working and that made it way clearer. Thanks!
|
Thanks @jridgewell, I'm sorry I have a lot of work on the side of this but I'll try to get back to it this week. |
e9c8e56 to
83fbdbc
Compare
|
Todo:
|
2923f1b to
1c448ee
Compare
|
Hi @jridgewell, I could use some help changing the tests to not transform the classes. Otherwise, I think I'm almost done. |
jridgewell
left a comment
There was a problem hiding this comment.
Sorry for being flakey. This is looking good!
|
|
||
| helpers.classStaticPrivateFieldLooseBase = () => template.program.ast` | ||
| export default function _classStaticPrivateFieldLooseBase(receiver, classConstructor) { | ||
| if (receiver !== classConstructor && receiver.constructor !== classConstructor) { |
There was a problem hiding this comment.
What's the receiver.constructor !== classConstructor for?
There was a problem hiding this comment.
That's for when the static field gets called from this. In that case I'll probably have an instance of the class instead of the constructor itself. I do think that's what we expect from the spec but it's been a while since the last time I dived in the proposal.
There was a problem hiding this comment.
It has to throw a type error if it's called on an instance of the class. This check should just be the receiver !== classConstructor.
There was a problem hiding this comment.
I'm not sure, I think that if the instance is of the class type, access should work through this. I can check that again. However, in the meantime, we can restrict the access so it does not work on the instance while we figure that out.
| // Create a private static "host" object if it does not exist | ||
| privateClassId = path.scope.generateUidIdentifier(ref.name + "Statics"); | ||
| staticNodesToAdd.push( | ||
| template.statement`const PRIVATE_CLASS_ID = {};`({ |
There was a problem hiding this comment.
Nit: Should probably use a prototype-less object here.
| } | ||
| if (privateNames.has(name)) { | ||
| throw path.buildCodeFrameError("Duplicate private field"); | ||
| throw path.buildCodeFrameError("Duplicate static private field"); |
There was a problem hiding this comment.
Let's leave this as the old message.
| state, | ||
| privateClassId, | ||
| ); | ||
| staticNodesToAdd.forEach(node => staticNodes.push(node)); |
There was a problem hiding this comment.
Can just staticNodes.push(...staticNodesToAdd)
|
@jridgewell don't worry, we're in no rush. I'll just need some help setting up the tests correctly. |
e061a59 to
93e205a
Compare
|
I was trying to prevent the class transform itself but I managed to get that right in the plane |
|
It should be good for a second review |
jridgewell
left a comment
There was a problem hiding this comment.
Looking good!
Need tests for loose, and some that do a call operation on the private static.
| const { scope, parentPath } = path; | ||
| const { key, value } = path.node; | ||
| const { name } = key.id; | ||
| const privateId = scope.generateUidIdentifier(name); |
There was a problem hiding this comment.
Nit: So we actually don't need to generate a unique id here, we can just use name.
|
Loose tests have been removed while I changed my test methodology, they're back now. I also removed the privateId for spec mode but not loose mode (where the unique id prevents test breakage in loose mode) |
eb66be9 to
6dc2a28
Compare
|
Rebased the whole thing. Still need to test |
|
@jridgewell I think this covers all of the requested changes. Thanks! |
|
|
||
| } | ||
|
|
||
| Foo._foo = "foo"; |
There was a problem hiding this comment.
My last nit: This should be using Object.defineProperty with { enumerable: false, configurable: false }. We can do this in a follow PR if you'd like.
There was a problem hiding this comment.
That is a good point, I'll try to put that here before we merge
|
@jridgewell I made the change, would you like to have it in a separate helper though? |
jridgewell
left a comment
There was a problem hiding this comment.
This is all good. Need one more approval before we can merge.
|
Eh, let's just go for it. |
|
I'm sorry, I forgot to review this PR 😅 |
classStaticPrivateFieldBase