-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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: Traverse set correct scope for function declarations in sloppy mode #14203
base: main
Are you sure you want to change the base?
Conversation
7c0fc75
to
1e56a83
Compare
let scope = path.scope; | ||
if (scope.path === path) scope = scope.parent; | ||
|
||
const parent = scope.getBlockParent(); | ||
|
||
if ( | ||
path.isFunctionDeclaration({ async: false, generator: false }) && |
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.
nit: I prefer to move sloppyFunctionDeclarationStack
manipulation to the Function
visitor and then split BlockScoped
visitor, as the current logic is implicitly determined by the execution order of Function
and BlockScoped
visitor.
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.
Yes sure, I'll be happy to make these changes. Give me a day or two please.
// Must only be called on a scope attached to a program or function path. | ||
// Must only be called with functions defined in sloppy mode. | ||
// Must not be called with async or generator functions. | ||
_registerSloppyFunctionDeclarations( |
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.
We can use pure function if it is not supposed to be a NodePath
API.
} | ||
} | ||
|
||
_registerSloppyFunctionDeclaration(path: NodePath<t.FunctionDeclaration>) { |
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.
Nit: It took me really long do see the difference between this function name and the name of the other function. Could you rename the other one to something like _registerMultipleSloppyFunctionDeclarations
? 😅
I'm afraid I've discovered my assumptions were incorrect. I said above:
This is not correct. It should be:
There are some further complications if there are multiple function declarations. Here the inner function declaration is hoisted as a redeclaration: {
function f() { return 2; }
}
function f() { return 1; }
console.log(f()); // Logs 2 Whereas here the more deeply nested function here does create a separate binding in its own block: {
function f() { return 1; }
{
function f() { return 2; }
console.log(f()); // 2
}
}
console.log(f()); // 1 So unfortunately the implementation in my PR would need a lot of changes to be correct. Going to take some time or (to be honest) maybe never happen! |
(I hate annex b) |
MDN notes that there are differences between browsers in how they handle this: In NodeJS: console.log(f); // undefined
const getOuterF = () => f;
{
console.log(typeof f); // function
console.log(typeof getOuterF()); // undefined <-- what???
function f() {}
console.log(typeof f); // function
console.log(typeof getOuterF()); // function
f = 123;
console.log(typeof f); // number
console.log(typeof getOuterF()); // function <-- what???
}
console.log(typeof f); // function It appears the nested function declaration creates two bindings - one at top level, and one in the block statement. I couldn't find a passage in ECMA spec which specifies the correct behavior. But I find the spec pretty impenetrable, so I may just have missed it. Anyone know if this exists? |
It's specified here: https://tc39.es/ecma262/#sec-block-level-function-declarations-web-legacy-compatibility-semantics I can try to analyze how that example should work according to the spec. |
Thanks very much for swift reply. Some bedtime reading! It looks complicated, but more comprehensible than much of the spec. |
Btw, with your example I get the same result in Firefox and Node.js |
Ok, for that example you probably have to look at B.3.2.2 and 16.1.7. |
Fix for #13549.
I believe this captures all the edge cases.
const
,let
,param
ormodule
binding.A few notes about implementation:
The check for whether a function is strict mode here is fairly expensive. However, it's bypassed for functions which are declared at top level in program, or directly nested in another function - which covers the vast majority of cases. The check is only run if it really needs to be.
Because of the several conditions on correct input to
_registerSloppyFunctionDeclarations()
, I thought it best to prefix method name with_
, so it's not part of public API.The test I've modified in
@babel/plugin-transform-block-scoping
I think maintains the intent of the test - the laterfunction f() {}
is meant to be scoped to inside theswitch
block.I am not entirely clear how traversal starting from a node which is not the program root works. I think this section covers such cases, but would appreciate input on whether I have this right.