You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Setting a bogus scope ID ScopeId::new(u32::MAX - 1) is going to cause trouble at some point, as trying to get e.g. scope flags for this scope will panic.
It's also not correct. f in example above does create a binding, and it is observable:
// Sloppy modeif(true)functionf(){returnf;}console.log(typeoff);// Logs 'function'constf2=f;f=123;console.log(typeoff2());// Logs 'function' NOT 'number'
is equivalent to this:
'use strict';varf;if(true)f=functionf(){returnf;};console.log(typeoff);// Logs 'function'constf2=f;f=123;console.log(typeoff2());// Logs 'function' NOT 'number'
i.e. There are TWO bindings for f in different scopes. f in return f refers to binding for f in function f.
We can represent this correctly as follows:
Create 1st binding f in outer scope.
Create 2nd binding f in the function declaration's scope (i.e. treat it like the id in a function expression).
Don't create that 2nd binding if function declaration also has either (a) a param also called f or (b) a var / let / const binding in body of function (same as function expressions behave).
Strictly speaking, we need a separate scope for the function body (oxc-project/oxc#5017) to handle this case where there are THREE bindings for f:
Binding 2 = f for function declaration's name (which f in f2 = f refers to).
Binding 3 = f in function body let f = 456.
General case
The above example is a specific case of sloppy mode function declaration hoisting, which we're not handling correctly in general.
The rules are a bit complex. I think this covers it:
Function declaration creates a binding in block which function is defined in.
Also creates a binding "hoisted up" to scope for body of enclosing function/top level.
The 2nd is similar to how var statements have their binding "hoisted up". Hoisted binding is not created if:
The enclosing function/top level scope already has a var, let or const binding with same name.
Any intermediate scope has a let or const binding with same name.
Enclosing function has a param with same name.
The difference from var statements is that cases (1) and (2) are a syntax error for var statements, but they're not for hoisted sloppy function declarations. The 2nd binding just doesn't get created, silently.
How to implement this
We can likely handle this using same machinery as we use for hoisting var statements.
Do we need to implement this?
The if (true) function f() {} example above is silly - no-one would write such code. But this isn't quite so ridiculous:
if(condition){functiontransform(){/* do something */}}else{functiontransform(){/* do something else */}}arr=arr.map(transform);
It would not surprise me if code like this exists in some old NPM packages.
Secondly, Oxc aims to be completely spec-compliant. And this is in the spec (Annex B).
Note: My observations above of the correct behavior is not based on reading the spec, but on running snippets of code in NodeJS and seeing what they do. I am assuming that V8 is spec-compliant.
We have some pretty wacky code to handle this case:
https://github.com/oxc-project/oxc/blob/f9e3a41dd2407bfadd050f3abe0d71238a083e19/crates/oxc_semantic/src/binder.rs#L166-L175
Setting a bogus scope ID
ScopeId::new(u32::MAX - 1)
is going to cause trouble at some point, as trying to get e.g. scope flags for this scope will panic.It's also not correct.
f
in example above does create a binding, and it is observable:Specific case
This code:
is equivalent to this:
i.e. There are TWO bindings for
f
in different scopes.f
inreturn f
refers to binding forf
infunction f
.We can represent this correctly as follows:
f
in outer scope.f
in the function declaration's scope (i.e. treat it like theid
in a function expression).f
or (b) avar
/let
/const
binding in body of function (same as function expressions behave).Strictly speaking, we need a separate scope for the function body (oxc-project/oxc#5017) to handle this case where there are THREE bindings for
f
:f
in top level scopef
for function declaration's name (whichf
inf2 = f
refers to).f
in function bodylet f = 456
.General case
The above example is a specific case of sloppy mode function declaration hoisting, which we're not handling correctly in general.
The rules are a bit complex. I think this covers it:
The 2nd is similar to how
var
statements have their binding "hoisted up". Hoisted binding is not created if:var
,let
orconst
binding with same name.let
orconst
binding with same name.The difference from
var
statements is that cases (1) and (2) are a syntax error forvar
statements, but they're not for hoisted sloppy function declarations. The 2nd binding just doesn't get created, silently.How to implement this
We can likely handle this using same machinery as we use for hoisting
var
statements.Do we need to implement this?
The
if (true) function f() {}
example above is silly - no-one would write such code. But this isn't quite so ridiculous:It would not surprise me if code like this exists in some old NPM packages.
Secondly, Oxc aims to be completely spec-compliant. And this is in the spec (Annex B).
Note: My observations above of the correct behavior is not based on reading the spec, but on running snippets of code in NodeJS and seeing what they do. I am assuming that V8 is spec-compliant.
Further info
oxc-project/oxc#5627 (comment)
babel/babel#14203 (comment)
overlookmotel/livepack#224
The text was updated successfully, but these errors were encountered: