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
Fix nested classes reference private fields #11405
Fix nested classes reference private fields #11405
Conversation
const body = path.get("body"); | ||
const loose = isLoose(this.file, feature); | ||
const privateNamesMap = new Map( | ||
privateNamesStack[privateNamesStack.length - 1], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since nodes can be re-queued and skipped, it's not guaranteed that they are visited in-order.
Instead of a stack, I think we could use a WeakMap
from class nodes to privateNamesMap
, and get the parent class using path.findParent(p => p.isClass())
.
@@ -30,6 +30,8 @@ export { FEATURES, injectInitialization }; | |||
const version = pkg.version.split(".").reduce((v, x) => v * 1e5 + +x, 0); | |||
const versionKey = "@babel/plugin-class-features/version"; | |||
|
|||
const privateNamesStack = [new Map()]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When using a WeakSet
, please put this in the createClassFeaturePluign
function so that when the plugin finishes running it can be garbage-collected, even if the AST was still retained.
I was wondering if it would be easier to fix it there. The Class(path) {
const { privateNamesMap } = this;
const body = path.get("body.body");
// First, evaluate the things in the outer scope.
path.traverse(privateNameInnerVisitor, this);
const visiblePrivateNames = new Map(privateNamesMap);
for (const prop of body) {
if (prop.isPrivate()) visiblePrivateNames.delete(prop.node.key.id.name);
}
path.traverse(privateNameVisitor, {
...this,
privateNamesMap: visiblePrivateNames,
ignoreUndeclared: true,
});
path.skip();
}, |
I was trying to avoid processing the class multiple times. But this is easier to do, given the issues with out-of-order requeueing. Updated. |
5d8118a
to
d450792
Compare
@@ -0,0 +1,15 @@ | |||
class Foo { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a case on redeclared private names with computed key reference? The following case should throw since #foo
is shadowed.
class Foo {
#foo = 1;
test() {
class Nested {
#foo = 2;
[this.#foo] = 3;
}
new Nested;
}
}
(new Foo).test();
Given that it is not directly related to this PR, we can fix it in a separate one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we wait till the other PR to add the test case? It's going to be much harder to fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I have a fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I meant we can add the new case and the fixes in a separate PR, my bad. As long as you come up with a fix, that's awesome! 😄
// This class redeclares some private field. We need to process the outer | ||
// environment with access to all the outer privates, then we can process | ||
// the inner environment with only the still-visible outer privates. | ||
path.traverse(privateNameNestedVisitor, this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JLHwung Maybe it would be enough to remove this line to fix it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so but I haven't tried. The nested private name is still allowed as long as it does not access outer class private names. I think we shall whitelist computed element names for proper resolving behaviour.
class Foo {
#foo = 1;
test() {
class Nested {
#foo = 2;
bar = this.#foo;
}
new Nested;
}
}
(new Foo).test();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
If a nested class's `superClass` redeclares the outer class's private field and access it in a computed key, that should fail. Follow up to babel#11405.
If a nested class's `superClass` redeclares the outer class's private field and access it in a computed key, that should fail. Follow up to #11405.
If a nested class's `superClass` redeclares the outer class's private field and access it in a computed key, that should fail. Follow up to babel#11405.
If a nested class's `superClass` redeclares the outer class's private field and access it in a computed key, that should fail. Follow up to babel#11405.
If a nested class's `superClass` redeclares the outer class's private field and access it in a computed key, that should fail. Follow up to #11405.
We failed to properly handle nested private field, when only a single private field was redeclared. Example
When we hit a nested class that redeclares a field, we skip the nested class. And when we finally traverse that nested class, we'll only have the privates that are declared by it. We've lost the ancestor's private fields!