-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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(es/hygiene): block scope #3219
Conversation
2fe7966
to
e6cb017
Compare
a0c7378
to
315903b
Compare
Hi @kdy1 ,
The pull request is not completed. But it is better to review it now. |
Why should we make
Why? We can use a helper mark, shared by helpers. |
If there are some codes as following: {
var foo = 1;
}
console.log(foo); Should we rename it? It depends. But if they are transformed from following: {
let foo = 1;
}
console.log(foo); Should we rename it? No, the How do we recognize them? We treat all In result we should never rename If there is naming conflicts in user input, it's better to report it from lint and leave it untouched. |
I mean, the helper mark is almost the same as All helpers are injected on top-level, and we can detect conflict from |
Private identifier is simply a symbol + unique |
ctxt, | ||
block_scoped: bs, | ||
.. | ||
}| ctxt == &id.1 && (block_scoped || *bs), |
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.
@kdy1
Do you mean that if we detect some specific SyntaxContext, we should treat it as private_ident
as well?
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, actually SyntaxContext
is the trick behind private_ident!
.
private_ident!
works by creating a unique SyntaxContext
(by SyntaxContext::empty(Mark::fresh(Mark::root()))
), and creating Ident
witth provided symbol and the SyntaxContext
we made.
Actual renaming is done by the hygiene
pass.
315903b
to
fa8cf1b
Compare
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.
Hi, @kdy1 ,
In the original code, The first Ident will be renamed.
let cur_scope_conflict = if ctxts_of_decls.iter().any(|(_, ctxt)| ctxt == &id.1) { |
For imported swcHelpers
, the first Ident
is what we import and should be renamed.
But it cannot handle these two cases at the same time
REPL
var a = 1;
{
let a = 1;
}
{
let a = 1;
}
var a = 1;
Notice: only the let a
should be renamed.
I handle these two cases by detect the block_scoped
.
It means I cannot determine which one should be renamed if two Ident
are in the same context without extra information.
})); | ||
return _foo.apply(this, arguments); | ||
} | ||
var swcHelpers = 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.
Hi, @kdy1 ,
I add a test case.
It seems to be a regression in my changes.
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 two identifier has same symbol and same syntax context, those are equal to each other.
If two different variables get the same syntax context, then it's bug of resolver
pass.
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.
in this REPL, we get the wrong result because we can't distinguish which one needs to be renamed.
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.
Hmm... Then we may remove the logic for retaining identifiers of top-level items?
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.
it is not related to top level. We should not touch the var
Ident.
See this swc playground and this Babel REPL.
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... You are right, although I'm not sure how can we fix this.
Scoping in rust is super-hard because of no two &mut to same data
.
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.
You could check the first commit in this pull request that I added some test cases about it.
Honestly I don't think adding a field to |
Without adding a field to the FYI, Babel store them in NodePath. |
If we can collect block-scoped |
Lines 267 to 299 in af53b94
Maybe I can pass a hashset to collect |
a5077ef
to
67e3362
Compare
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.
Thank you so much for the hard work!
@@ -11,7 +11,7 @@ use swc_ecma_transforms_testing::{compare_stdout, test, test_exec, Tester}; | |||
|
|||
test!( | |||
::swc_ecma_parser::Syntax::default(), | |||
|_| block_scoping(), | |||
|_| block_scoping(Default::default()), |
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.
swc_ecma_transforms_testing
will apply hygiene(Default::default())
.
I'm concerned a bit because usages of different values may make bug hidden.
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 think it's enough for other test suites, but I'm not sure if it's fine for tests for block_scoping
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.
You are right.
I just updated the test config to use resolver
and hygiene
.
@@ -136,6 +136,7 @@ fn simple() { | |||
} | |||
|
|||
#[test] | |||
#[ignore] |
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.
Why is this ignored?
9aac356
to
07d63ee
Compare
07d63ee
to
bdff6b9
Compare
@@ -313,8 +317,8 @@ fn for_loop() { | |||
}, | |||
" | |||
for(var a=1;;) {} | |||
for(var a1 of foo) {} | |||
for(var a2 = 3;;) {} | |||
for(var a of 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.
One of the main idea of this PR is to leave the user input vars
untouched.
But when updating this test file, I found many cases where the var
was intentionally changed.
Maybe I'm thinking in the wrong way.
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.
Sometimes it's inevitable, as var
and let
/const
have different scoping.
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 there are some cases where var
ident of different scopes are together and conflict with each other, this pull request is meaningless and I will close 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.
Those updates are mostly not intentional.
The renamed var
s in tests are renamed just because it works and it was required to pass the 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.
Oh, almost all renamings in his files are intentional.
They have different syntax contexts, so those are different variables.
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.
Tests in this file are extracted from other tests, and those are tests for injected identifiers.
}) | ||
.or_insert_with(|| self.ident_scope_kind); | ||
} | ||
|
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.
But seems like this does the exact thing collect_bindings
do.
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, we collect Bindings.
let a = 1,
[b, ...c] = [2, 3],
{ d: e } = {},
{ f = g } = {},
{ h: i = j } = {},
{ ...k } = {};
In this case, we got a
, b
, c
, e
, f
, i
and k
.
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.
Is there a reason you used another visitor instead of using collect_bindings
?
Each visitor uses more space in the final binary than expected
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, do you mean BindingCollector
, I just find 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 have tried the Maybe I misused it, I will try again.collect_decls
but got the wrong result.
Maybe I should use find_ids
, but I need to ignore key
in KeyValuePatProp
and value
in AssignPatProp
.
Just like what I do as mentioned:
swc/crates/swc_ecma_transforms_base/src/ident_scope.rs
Lines 101 to 109 in 3afcaba
/// let { x: y } = z; | |
fn visit_key_value_pat_prop(&mut self, n: &KeyValuePatProp) { | |
n.value.visit_with(self); | |
} | |
/// let { x = y } = z; | |
fn visit_assign_pat_prop(&mut self, n: &AssignPatProp) { | |
n.key.visit_with(self); | |
} |
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.
Ok, find_ids
works well.
another case input: if (true) {
const a = 1;
const b = 2;
function foo(a, b) {
use(a, b);
}
} else {
const a = 1;
const b = 2;
function bar(a, b) {
use(a, b);
}
} expect output: if (true) {
var foo = function foo(a, b) {
use(a, b);
};
var a = 1;
var b = 2;
} else {
var bar = function bar(a, b) {
use(a, b);
};
var a1 = 1;
var b1 = 2;
} main branch output(REPL): if (true) {
var foo = function foo(a3, b3) {
use(a3, b3);
};
var a2 = 1;
var b2 = 2;
} else {
var bar = function bar(a, b) {
use(a, b);
};
var a1 = 1;
var b1 = 2;
} this PR output (as same as main branch): if (true) {
var foo = function foo(a3, b3) {
use(a3, b3);
};
var a2 = 1;
var b2 = 2;
} else {
var bar = function bar(a, b) {
use(a, b);
};
var a1 = 1;
var b1 = 2;
} I will take a look at why I got this result. |
Closed due to outdated |
Description:
This pull request try to fix block scope issues.
Previous behavior:
Rename every ident if conflict.
Current behavior:
Only the idnet marked as
block_scoped
will be renamed.block_scoped
ident is an identifier which isvar
, but should behave likelet
/const
.There is a similar definition at Babel.
let
/const
transforms tovar
, we mark it asblock_scoped
ident.private_ident
isblock_scoped
ident.TODO:
There are too many cases whereprivae_ident
should be used but is not, which will result in name conflicts.Ident::new
or direct construction should be disabled. It is better only useprivate_ident
andquote_ident
to constructIdent
.BREAKING CHANGE:
Related issue (if exists):