fix(transformer/logical-assignment-operators): fix semantic errors (#5047)

Fix semantic error caused by `clone_in` causing `reference_id` to not exist.

In this PR, I try to use `move_expression` as much as possible instead of `expr.clone_in`. But in some cases, we need to reuse the same expression in multiple places. I have added a `clone_expression` to workaround it

I felt a bit painful we missing a [clone_in_scope](https://github.com/oxc-project/oxc/issues/4804) API
This commit is contained in:
Dunqing 2024-08-22 08:41:31 +00:00
parent deda6ac136
commit 3b353321ad
2 changed files with 99 additions and 378 deletions

View file

@ -164,13 +164,32 @@ impl<'a> Traverse<'a> for LogicalAssignmentOperators<'a> {
ctx.ast.simple_assignment_target_member_expression(assign_expr),
);
} else {
left_expr = Expression::from(MemberExpression::StaticMemberExpression(
static_expr.clone_in(ctx.ast.allocator),
));
// transform `obj.x ||= 1` to `obj.x || (obj.x = 1)`
let object = ctx.ast.move_expression(&mut static_expr.object);
// TODO: We should use static_expr.clone_in instead of cloning the properties,
// but currently clone_in will get rid of IdentifierReference's reference_id
let static_expr_cloned = ctx.ast.static_member_expression(
static_expr.span,
Self::clone_expression(&object, ctx),
static_expr.property.clone_in(ctx.ast.allocator),
static_expr.optional,
);
left_expr = ctx.ast.expression_member(
ctx.ast.member_expression_from_static(static_expr_cloned),
);
let member_expr_moved = ctx.ast.member_expression_static(
static_expr.span,
object,
static_expr.property.clone_in(ctx.ast.allocator),
static_expr.optional,
);
assign_target = AssignmentTarget::from(
ctx.ast.simple_assignment_target_member_expression(
member_expr.clone_in(ctx.ast.allocator),
),
ctx.ast
.simple_assignment_target_member_expression(member_expr_moved),
);
};
}
@ -229,31 +248,65 @@ impl<'a> Traverse<'a> for LogicalAssignmentOperators<'a> {
ctx.ast.member_expression_computed(SPAN, object, expression, false),
);
} else {
// transform `obj[++key] ||= 1` to `obj[_key = ++key] || (obj[_key] = 1)`
let property_ident =
self.maybe_generate_memoised(&computed_expr.expression, ctx);
let mut expr = computed_expr.clone_in(ctx.ast.allocator);
if let Some(property_ident) = &property_ident {
let left = AssignmentTarget::from(
ctx.ast.simple_assignment_target_from_identifier_reference(
property_ident.clone(),
),
);
let right = computed_expr.expression.clone_in(ctx.ast.allocator);
expr.expression =
ctx.ast.expression_assignment(SPAN, op, left, right);
}
left_expr =
Expression::from(MemberExpression::ComputedMemberExpression(expr));
let object = ctx.ast.move_expression(&mut computed_expr.object);
let mut expression =
ctx.ast.move_expression(&mut computed_expr.expression);
// TODO: ideally we should use computed_expr.clone_in instead of cloning the properties,
// but currently clone_in will get rid of IdentifierReference's reference_id
let new_compute_expr = ctx.ast.computed_member_expression(
computed_expr.span,
Self::clone_expression(&object, ctx),
{
// _key = ++key
if let Some(property_ident) = &property_ident {
let left = AssignmentTarget::from(
ctx.ast
.simple_assignment_target_from_identifier_reference(
ctx.clone_identifier_reference(
property_ident,
ReferenceFlags::Write,
),
),
);
ctx.ast.expression_assignment(
SPAN,
op,
left,
ctx.ast.move_expression(&mut expression),
)
} else {
Self::clone_expression(&expression, ctx)
}
},
computed_expr.optional,
);
left_expr = ctx.ast.expression_member(
ctx.ast.member_expression_from_computed(new_compute_expr),
);
// obj[_key] = 1
let new_compute_expr = ctx.ast.computed_member_expression(
computed_expr.span,
object,
{
if let Some(property_ident) = property_ident {
ctx.ast.expression_from_identifier_reference(property_ident)
} else {
expression
}
},
computed_expr.optional,
);
let mut expr = computed_expr.clone_in(ctx.ast.allocator);
if let Some(property_ident) = property_ident {
expr.expression =
ctx.ast.expression_from_identifier_reference(property_ident);
}
assign_target = AssignmentTarget::from(
ctx.ast.simple_assignment_target_member_expression(
MemberExpression::ComputedMemberExpression(expr),
ctx.ast.member_expression_from_computed(new_compute_expr),
),
);
};
@ -279,22 +332,31 @@ impl<'a> Traverse<'a> for LogicalAssignmentOperators<'a> {
}
impl<'a> LogicalAssignmentOperators<'a> {
/// Clone an expression
///
/// If it is an identifier, clone the identifier by [TraverseCtx::clone_identifier_reference], otherwise, use [CloneIn].
///
/// TODO: remove this until <https://github.com/oxc-project/oxc/issues/4804> is resolved.
pub fn clone_expression(expr: &Expression<'a>, ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
match expr {
Expression::Identifier(ident) => ctx.ast.expression_from_identifier_reference(
ctx.clone_identifier_reference(ident, ReferenceFlags::Read),
),
_ => expr.clone_in(ctx.ast.allocator),
}
}
pub fn maybe_generate_memoised(
&mut self,
expr: &Expression<'a>,
ctx: &mut TraverseCtx<'a>,
) -> Option<IdentifierReference<'a>> {
let name = match expr {
Expression::Super(_) | Expression::ThisExpression(_) => return None,
Expression::Identifier(ident) => ident.name.clone(),
Expression::StringLiteral(str) => str.value.clone(),
_ => {
return None;
}
};
if ctx.symbols().is_static(expr) {
return None;
}
let symbol_id =
ctx.generate_uid_in_current_scope(name.as_str(), SymbolFlags::FunctionScopedVariable);
let symbol_id = ctx
.generate_uid_in_current_scope_based_on_node(expr, SymbolFlags::FunctionScopedVariable);
let symbol_name = ctx.ast.atom(ctx.symbols().get_name(symbol_id));
// var _name;

View file

@ -1,6 +1,6 @@
commit: 12619ffe
Passed: 396/953
Passed: 397/953
# All Passed:
* babel-preset-react
@ -1276,349 +1276,8 @@ preset-env: unknown field `shippedProposals`, expected `targets` or `bugfixes`
preset-env: unknown field `shippedProposals`, expected `targets` or `bugfixes`
# babel-plugin-transform-logical-assignment-operators (4/6)
* logical-assignment/general-semantics/input.js
x Semantic Collector failed after transform
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:32:8]
31 |
32 | expect(deep.obj.x ||= 1).toBe(1);
: ^^^^
33 | expect(gets).toBe(1);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:32:8]
31 |
32 | expect(deep.obj.x ||= 1).toBe(1);
: ^^^^
33 | expect(gets).toBe(1);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:34:8]
33 | expect(gets).toBe(1);
34 | expect(deep.obj.x ||= 2).toBe(1);
: ^^^^
35 | expect(gets).toBe(2);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:34:8]
33 | expect(gets).toBe(1);
34 | expect(deep.obj.x ||= 2).toBe(1);
: ^^^^
35 | expect(gets).toBe(2);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:37:8]
36 |
37 | expect(deep.obj.x &&= 0).toBe(0);
: ^^^^
38 | expect(gets).toBe(3);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:37:8]
36 |
37 | expect(deep.obj.x &&= 0).toBe(0);
: ^^^^
38 | expect(gets).toBe(3);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:39:8]
38 | expect(gets).toBe(3);
39 | expect(deep.obj.x &&= 3).toBe(0);
: ^^^^
40 | expect(gets).toBe(4);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:39:8]
38 | expect(gets).toBe(3);
39 | expect(deep.obj.x &&= 3).toBe(0);
: ^^^^
40 | expect(gets).toBe(4);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:43:14]
42 | var key = 0;
43 | expect(obj[++key] ||= 1).toBe(1);
: ^^^
44 | expect(key).toBe(1);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:46:14]
45 | key = 0;
46 | expect(obj[++key] ||= 2).toBe(1);
: ^^^
47 | expect(key).toBe(1);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:50:14]
49 | key = 0;
50 | expect(obj[++key] &&= 0).toBe(0);
: ^^^
51 | expect(key).toBe(1);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:53:14]
52 | key = 0;
53 | expect(obj[++key] &&= 3).toBe(0);
: ^^^
54 | expect(key).toBe(1);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:57:8]
56 | key = 0;
57 | expect(deep.obj[++key] ||= 1).toBe(1);
: ^^^^
58 | expect(gets).toBe(5);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:57:19]
56 | key = 0;
57 | expect(deep.obj[++key] ||= 1).toBe(1);
: ^^^
58 | expect(gets).toBe(5);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:57:8]
56 | key = 0;
57 | expect(deep.obj[++key] ||= 1).toBe(1);
: ^^^^
58 | expect(gets).toBe(5);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:57:19]
56 | key = 0;
57 | expect(deep.obj[++key] ||= 1).toBe(1);
: ^^^
58 | expect(gets).toBe(5);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:61:8]
60 | key = 0;
61 | expect(deep.obj[++key] ||= 2).toBe(1);
: ^^^^
62 | expect(gets).toBe(6);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:61:19]
60 | key = 0;
61 | expect(deep.obj[++key] ||= 2).toBe(1);
: ^^^
62 | expect(gets).toBe(6);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:61:8]
60 | key = 0;
61 | expect(deep.obj[++key] ||= 2).toBe(1);
: ^^^^
62 | expect(gets).toBe(6);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:61:19]
60 | key = 0;
61 | expect(deep.obj[++key] ||= 2).toBe(1);
: ^^^
62 | expect(gets).toBe(6);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:66:8]
65 | key = 0;
66 | expect(deep.obj[++key] &&= 0).toBe(0);
: ^^^^
67 | expect(gets).toBe(7);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:66:19]
65 | key = 0;
66 | expect(deep.obj[++key] &&= 0).toBe(0);
: ^^^
67 | expect(gets).toBe(7);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:66:8]
65 | key = 0;
66 | expect(deep.obj[++key] &&= 0).toBe(0);
: ^^^^
67 | expect(gets).toBe(7);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:66:19]
65 | key = 0;
66 | expect(deep.obj[++key] &&= 0).toBe(0);
: ^^^
67 | expect(gets).toBe(7);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:70:8]
69 | key = 0;
70 | expect(deep.obj[++key] &&= 3).toBe(0);
: ^^^^
71 | expect(gets).toBe(8);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:70:19]
69 | key = 0;
70 | expect(deep.obj[++key] &&= 3).toBe(0);
: ^^^
71 | expect(gets).toBe(8);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:70:8]
69 | key = 0;
70 | expect(deep.obj[++key] &&= 3).toBe(0);
: ^^^^
71 | expect(gets).toBe(8);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/general-semantics/input.js:70:19]
69 | key = 0;
70 | expect(deep.obj[++key] &&= 3).toBe(0);
: ^^^
71 | expect(gets).toBe(8);
`----
# babel-plugin-transform-logical-assignment-operators (5/6)
* logical-assignment/null-coalescing/input.js
x Semantic Collector failed after transform
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js:28:8]
27 | obj.x = undefined;
28 | expect(deep.obj.x ??= 1).toBe(1);
: ^^^^
29 | expect(gets, 1);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js:28:8]
27 | obj.x = undefined;
28 | expect(deep.obj.x ??= 1).toBe(1);
: ^^^^
29 | expect(gets, 1);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js:30:8]
29 | expect(gets, 1);
30 | expect(deep.obj.x ??= 2).toBe(1);
: ^^^^
31 | expect(gets, 2);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js:30:8]
29 | expect(gets, 1);
30 | expect(deep.obj.x ??= 2).toBe(1);
: ^^^^
31 | expect(gets, 2);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js:35:14]
34 | obj.x = undefined;
35 | expect(obj[++key] ??= 1).toBe(1);
: ^^^
36 | expect(key, 1);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js:38:14]
37 | key = 0;
38 | expect(obj[++key] ??= 2).toBe(1);
: ^^^
39 | expect(key, 1);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js:43:8]
42 | key = 0;
43 | expect(deep.obj[++key] ??= 1).toBe(1);
: ^^^^
44 | expect(gets, 3);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js:43:19]
42 | key = 0;
43 | expect(deep.obj[++key] ??= 1).toBe(1);
: ^^^
44 | expect(gets, 3);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js:43:8]
42 | key = 0;
43 | expect(deep.obj[++key] ??= 1).toBe(1);
: ^^^^
44 | expect(gets, 3);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js:43:19]
42 | key = 0;
43 | expect(deep.obj[++key] ??= 1).toBe(1);
: ^^^
44 | expect(gets, 3);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js:47:8]
46 | key = 0;
47 | expect(deep.obj[++key] ??= 2).toBe(1);
: ^^^^
48 | expect(gets, 4);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js:47:19]
46 | key = 0;
47 | expect(deep.obj[++key] ??= 2).toBe(1);
: ^^^
48 | expect(gets, 4);
`----
x Missing ReferenceId: deep
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js:47:8]
46 | key = 0;
47 | expect(deep.obj[++key] ??= 2).toBe(1);
: ^^^^
48 | expect(gets, 4);
`----
x Missing ReferenceId: key
,-[tasks/coverage/babel/packages/babel-plugin-transform-logical-assignment-operators/test/fixtures/logical-assignment/null-coalescing/input.js:47:19]
46 | key = 0;
47 | expect(deep.obj[++key] ??= 2).toBe(1);
: ^^^
48 | expect(gets, 4);
`----