Skip to content

Commit 4436621

Browse files
authoredOct 14, 2024··
fix(es/minifier): Check type of assignment target before merging assignments (#9617)
**Description:** Collect types of vars, maybe other optimization could benefit from this: `merged_var_type: Option<Value<Type>>` When the variable is reassigned, the we merge the types with some simple rules: `None` + `None` = `None` `None` + `Some(ty)` = `Some(ty)` `Some(ty1)` + `Some(ty2)` if `ty1` == `ty2` = `Some(ty1)` Otherwise = Unknown **Related issue:** - Closes #8718
1 parent 30f9a70 commit 4436621

File tree

22 files changed

+141
-32
lines changed

22 files changed

+141
-32
lines changed
 

‎.changeset/honest-cooks-dream.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
swc_ecma_minifier: patch
3+
swc_ecma_utils: patch
4+
swc_ecma_usage_analyzer: patch
5+
---
6+
7+
fix(es/minifier): Check type of assignment target before merging assignments

‎crates/swc/tests/tsc-references/compoundAdditionAssignmentLHSCanBeAssigned.2.minified.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
//// [compoundAdditionAssignmentWithInvalidOperands.ts]
22
var E, a, x1, x2, x3, x4, x5, E1 = ((E = E1 || {})[E.a = 0] = "a", E[E.b = 1] = "b", E);
3-
x1 += a, x1 += !0, x1 += 0, x1 += {}, x1 += null, x1 += void 0, x2 += a, x2 += !0, x2 += 0, x2 += {}, x2 += null, x2 += void 0, x3 += a, x3 += !0, x3 += 0, x3 += {}, x3 += null, x3 += void 0, x4 += a, x4 += !0, x4 += {}, x5 += a, x5 += !0;
3+
x1 += a, x1 += !0, x1 += 0, x1 += 0, x1 += {}, x1 += null, x1 += void 0, x2 += a, x2 += !0, x2 += 0, x2 += 0, x2 += {}, x2 += null, x2 += void 0, x3 += a, x3 += !0, x3 += 0, x3 += 0, x3 += {}, x3 += null, x3 += void 0, x4 += a, x4 += !0, x4 += {}, x5 += a, x5 += !0;

‎crates/swc_ecma_minifier/src/compress/optimize/sequences.rs

+21-3
Original file line numberDiff line numberDiff line change
@@ -2479,13 +2479,18 @@ impl Optimizer<'_> {
24792479
_ => None,
24802480
};
24812481

2482+
let var_type = self
2483+
.data
2484+
.vars
2485+
.get(&left_id.to_id())
2486+
.and_then(|info| info.merged_var_type);
24822487
let Some(a_type) = a_type else {
24832488
return Ok(false);
24842489
};
24852490
let b_type = b.right.get_type();
24862491

24872492
if let Some(a_op) = a_op {
2488-
if can_drop_op_for(a_op, b.op, a_type, b_type) {
2493+
if can_drop_op_for(a_op, b.op, var_type, a_type, b_type) {
24892494
if b_left.to_id() == left_id.to_id() {
24902495
if let Some(bin_op) = b.op.to_update() {
24912496
report_change!(
@@ -2717,13 +2722,26 @@ pub(crate) fn is_trivial_lit(e: &Expr) -> bool {
27172722
}
27182723

27192724
/// This assumes `a.left.to_id() == b.left.to_id()`
2720-
fn can_drop_op_for(a: AssignOp, b: AssignOp, a_type: Value<Type>, b_type: Value<Type>) -> bool {
2725+
fn can_drop_op_for(
2726+
a: AssignOp,
2727+
b: AssignOp,
2728+
var_type: Option<Value<Type>>,
2729+
a_type: Value<Type>,
2730+
b_type: Value<Type>,
2731+
) -> bool {
27212732
if a == op!("=") {
27222733
return true;
27232734
}
27242735

27252736
if a == b {
2726-
if a == op!("+=") && a_type.is_known() && a_type == b_type {
2737+
if a == op!("+=")
2738+
&& a_type.is_known()
2739+
&& a_type == b_type
2740+
&& (match var_type {
2741+
Some(ty) => a_type == ty,
2742+
None => true,
2743+
})
2744+
{
27272745
return true;
27282746
}
27292747

‎crates/swc_ecma_minifier/src/program_data.rs

+13-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use swc_ecma_usage_analyzer::{
1818
marks::Marks,
1919
util::is_global_var_with_pure_property_access,
2020
};
21+
use swc_ecma_utils::{Merge, Type, Value};
2122
use swc_ecma_visit::VisitWith;
2223

2324
pub(crate) fn analyze<N>(n: &N, marks: Option<Marks>) -> ProgramData
@@ -98,6 +99,7 @@ pub(crate) struct VarUsageInfo {
9899

99100
pub(crate) var_kind: Option<VarDeclKind>,
100101
pub(crate) var_initialized: bool,
102+
pub(crate) merged_var_type: Option<Value<Type>>,
101103

102104
pub(crate) declared_as_catch_param: bool,
103105

@@ -149,6 +151,7 @@ impl Default for VarUsageInfo {
149151
used_in_cond: Default::default(),
150152
var_kind: Default::default(),
151153
var_initialized: Default::default(),
154+
merged_var_type: Default::default(),
152155
declared_as_catch_param: Default::default(),
153156
no_side_effect_for_member_access: Default::default(),
154157
callee_count: Default::default(),
@@ -248,6 +251,8 @@ impl Storage for ProgramData {
248251
}
249252
}
250253

254+
e.get_mut().merged_var_type.merge(var_info.merged_var_type);
255+
251256
e.get_mut().ref_count += var_info.ref_count;
252257

253258
e.get_mut().reassigned |= var_info.reassigned;
@@ -352,7 +357,7 @@ impl Storage for ProgramData {
352357
e.used_in_cond |= ctx.in_cond;
353358
}
354359

355-
fn report_assign(&mut self, ctx: Ctx, i: Id, is_op: bool) {
360+
fn report_assign(&mut self, ctx: Ctx, i: Id, is_op: bool, ty: Value<Type>) {
356361
let e = self.vars.entry(i.clone()).or_default();
357362

358363
let inited = self.initialized_vars.contains(&i);
@@ -361,14 +366,15 @@ impl Storage for ProgramData {
361366
e.reassigned = true
362367
}
363368

369+
e.merged_var_type.merge(Some(ty));
364370
e.assign_count += 1;
365371

366372
if !is_op {
367373
self.initialized_vars.insert(i.clone());
368374
if e.ref_count == 1 && e.var_kind != Some(VarDeclKind::Const) && !inited {
369375
e.var_initialized = true;
370376
} else {
371-
e.reassigned = true
377+
e.reassigned = true;
372378
}
373379

374380
if e.ref_count == 1 && e.used_above_decl {
@@ -406,7 +412,7 @@ impl Storage for ProgramData {
406412
&mut self,
407413
ctx: Ctx,
408414
i: &Ident,
409-
has_init: bool,
415+
init_type: Option<Value<Type>>,
410416
kind: Option<VarDeclKind>,
411417
) -> &mut VarUsageInfo {
412418
// if cfg!(feature = "debug") {
@@ -417,7 +423,7 @@ impl Storage for ProgramData {
417423
v.is_top_level |= ctx.is_top_level;
418424

419425
// assigned or declared before this declaration
420-
if has_init {
426+
if init_type.is_some() {
421427
if v.declared || v.var_initialized || v.assign_count > 0 {
422428
#[cfg(feature = "debug")]
423429
{
@@ -440,12 +446,13 @@ impl Storage for ProgramData {
440446
v.is_fn_local = false;
441447
}
442448

443-
v.var_initialized |= has_init;
449+
v.var_initialized |= init_type.is_some();
450+
v.merged_var_type.merge(init_type);
444451

445452
v.declared_count += 1;
446453
v.declared = true;
447454
// not a VarDecl, thus always inited
448-
if has_init || kind.is_none() {
455+
if init_type.is_some() || kind.is_none() {
449456
self.initialized_vars.insert(i.to_id());
450457
}
451458
v.declared_as_catch_param |= ctx.in_catch_param;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
let a = "";
2+
console.log(((a += 1), (a += 2)));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
let a = "";
2+
console.log((a += 1, a += 2));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
let a = 0;
2+
a = "";
3+
console.log(((a += 1), (a += 2)));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log("12");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
let a;
2+
function f() {
3+
a = "123";
4+
console.log(a);
5+
}
6+
7+
f();
8+
console.log(((a += 1), (a += 2)));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
let a;
2+
console.log(a = "123"), console.log((a += 1, a += 2));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
let a;
2+
function g() {
3+
a = "123";
4+
console.log(a);
5+
}
6+
function f() {
7+
// a = "123";
8+
console.log(a);
9+
}
10+
f(), g();
11+
console.log(((a += 1), (a += 2)));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
let a;
2+
// a = "123";
3+
console.log(a), console.log(a = "123"), console.log((a += 1, a += 2));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
let a = 0;
2+
function f() {
3+
a = "123";
4+
console.log(a);
5+
}
6+
7+
console.log(((a += 1), (a += 2)));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log(3);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
let a = 0;
2+
function g() {
3+
a = "123";
4+
console.log(a);
5+
}
6+
function f() {
7+
// a = "123";
8+
console.log(a);
9+
}
10+
console.log(((a += 1), (a += 2)));
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log(3);

‎crates/swc_ecma_usage_analyzer/src/analyzer/ctx.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
use std::ops::{Deref, DerefMut};
44

55
use swc_ecma_ast::VarDeclKind;
6+
use swc_ecma_utils::{Type, Value};
67

78
use super::{storage::Storage, UsageAnalyzer};
89

@@ -28,7 +29,7 @@ pub struct Ctx {
2829
pub in_decl_with_no_side_effect_for_member_access: bool,
2930

3031
pub in_pat_of_var_decl: bool,
31-
pub in_pat_of_var_decl_with_init: bool,
32+
pub in_pat_of_var_decl_with_init: Option<Value<Type>>,
3233
pub in_pat_of_param: bool,
3334
pub in_catch_param: bool,
3435

‎crates/swc_ecma_usage_analyzer/src/analyzer/mod.rs

+26-18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use swc_common::{collections::AHashMap, SyntaxContext};
22
use swc_ecma_ast::*;
3-
use swc_ecma_utils::{find_pat_ids, ExprCtx, ExprExt, IsEmpty, StmtExt};
3+
use swc_ecma_utils::{find_pat_ids, ExprCtx, ExprExt, IsEmpty, StmtExt, Type, Value};
44
use swc_ecma_visit::{noop_visit_type, Visit, VisitWith};
55
use swc_timer::timer;
66

@@ -155,33 +155,38 @@ where
155155

156156
fn report_assign_pat(&mut self, p: &Pat, is_read_modify: bool) {
157157
for id in find_pat_ids(p) {
158-
self.data.report_assign(self.ctx, id, is_read_modify)
158+
// It's hard to determined the type of pat assignment
159+
self.data
160+
.report_assign(self.ctx, id, is_read_modify, Value::Unknown)
159161
}
160162

161163
if let Pat::Expr(e) = p {
162164
match &**e {
163-
Expr::Ident(i) => self.data.report_assign(self.ctx, i.to_id(), is_read_modify),
165+
Expr::Ident(i) => {
166+
self.data
167+
.report_assign(self.ctx, i.to_id(), is_read_modify, Value::Unknown)
168+
}
164169
_ => self.mark_mutation_if_member(e.as_member()),
165170
}
166171
}
167172
}
168173

169-
fn report_assign_expr_if_ident(&mut self, e: Option<&Ident>, is_op: bool) {
174+
fn report_assign_expr_if_ident(&mut self, e: Option<&Ident>, is_op: bool, ty: Value<Type>) {
170175
if let Some(i) = e {
171-
self.data.report_assign(self.ctx, i.to_id(), is_op)
176+
self.data.report_assign(self.ctx, i.to_id(), is_op, ty)
172177
}
173178
}
174179

175180
fn declare_decl(
176181
&mut self,
177182
i: &Ident,
178-
has_init: bool,
183+
init_type: Option<Value<Type>>,
179184
kind: Option<VarDeclKind>,
180185
is_fn_decl: bool,
181186
) -> &mut S::VarData {
182187
self.scope.add_declared_symbol(i);
183188

184-
let v = self.data.declare_decl(self.ctx, i, has_init, kind);
189+
let v = self.data.declare_decl(self.ctx, i, init_type, kind);
185190

186191
if is_fn_decl {
187192
v.mark_declared_as_fn_decl();
@@ -267,13 +272,15 @@ where
267272
match &n.left {
268273
AssignTarget::Pat(p) => {
269274
for id in find_pat_ids(p) {
270-
self.data.report_assign(self.ctx, id, is_op_assign)
275+
self.data
276+
.report_assign(self.ctx, id, is_op_assign, n.right.get_type())
271277
}
272278
}
273279
AssignTarget::Simple(e) => {
274280
self.report_assign_expr_if_ident(
275281
e.as_ident().map(Ident::from).as_ref(),
276282
is_op_assign,
283+
n.right.get_type(),
277284
);
278285
self.mark_mutation_if_member(e.as_member())
279286
}
@@ -516,7 +523,7 @@ where
516523

517524
#[cfg_attr(feature = "tracing-spans", tracing::instrument(skip_all))]
518525
fn visit_class_decl(&mut self, n: &ClassDecl) {
519-
self.declare_decl(&n.ident, true, None, false);
526+
self.declare_decl(&n.ident, Some(Value::Unknown), None, false);
520527

521528
n.visit_children_with(self);
522529
}
@@ -526,7 +533,7 @@ where
526533
n.visit_children_with(self);
527534

528535
if let Some(id) = &n.ident {
529-
self.declare_decl(id, true, None, false);
536+
self.declare_decl(id, Some(Value::Unknown), None, false);
530537
}
531538
}
532539

@@ -681,7 +688,7 @@ where
681688
in_pat_of_param: false,
682689
in_catch_param: false,
683690
var_decl_kind_of_pat: None,
684-
in_pat_of_var_decl_with_init: false,
691+
in_pat_of_var_decl_with_init: None,
685692
..self.ctx
686693
};
687694

@@ -721,7 +728,8 @@ where
721728
in_decl_with_no_side_effect_for_member_access: true,
722729
..self.ctx
723730
};
724-
self.with_ctx(ctx).declare_decl(&n.ident, true, None, true);
731+
self.with_ctx(ctx)
732+
.declare_decl(&n.ident, Some(Value::Known(Type::Obj)), None, true);
725733

726734
if n.function.body.is_empty() {
727735
self.data.var_or_default(n.ident.to_id()).mark_as_pure_fn();
@@ -889,15 +897,15 @@ where
889897
}
890898

891899
fn visit_import_default_specifier(&mut self, n: &ImportDefaultSpecifier) {
892-
self.declare_decl(&n.local, true, None, false);
900+
self.declare_decl(&n.local, Some(Value::Unknown), None, false);
893901
}
894902

895903
fn visit_import_named_specifier(&mut self, n: &ImportNamedSpecifier) {
896-
self.declare_decl(&n.local, true, None, false);
904+
self.declare_decl(&n.local, Some(Value::Unknown), None, false);
897905
}
898906

899907
fn visit_import_star_as_specifier(&mut self, n: &ImportStarAsSpecifier) {
900-
self.declare_decl(&n.local, true, None, false);
908+
self.declare_decl(&n.local, Some(Value::Unknown), None, false);
901909
}
902910

903911
#[cfg_attr(feature = "tracing-spans", tracing::instrument(skip_all))]
@@ -907,7 +915,7 @@ where
907915
in_pat_of_param: false,
908916
in_catch_param: false,
909917
var_decl_kind_of_pat: None,
910-
in_pat_of_var_decl_with_init: false,
918+
in_pat_of_var_decl_with_init: None,
911919
..self.ctx
912920
};
913921

@@ -1238,7 +1246,7 @@ where
12381246
fn visit_update_expr(&mut self, n: &UpdateExpr) {
12391247
n.visit_children_with(self);
12401248

1241-
self.report_assign_expr_if_ident(n.arg.as_ident(), true);
1249+
self.report_assign_expr_if_ident(n.arg.as_ident(), true, Value::Known(Type::Num));
12421250
self.mark_mutation_if_member(n.arg.as_member());
12431251
}
12441252

@@ -1278,7 +1286,7 @@ where
12781286
let ctx = Ctx {
12791287
inline_prevented: self.ctx.inline_prevented || prevent_inline,
12801288
in_pat_of_var_decl: true,
1281-
in_pat_of_var_decl_with_init: e.init.is_some(),
1289+
in_pat_of_var_decl_with_init: e.init.as_ref().map(|init| init.get_type()),
12821290
in_decl_with_no_side_effect_for_member_access: e
12831291
.init
12841292
.as_deref()

‎crates/swc_ecma_usage_analyzer/src/analyzer/storage.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use swc_atoms::JsWord;
22
use swc_common::SyntaxContext;
33
use swc_ecma_ast::*;
4+
use swc_ecma_utils::{Type, Value};
45

56
use super::{ctx::Ctx, ScopeKind};
67
use crate::alias::Access;
@@ -19,13 +20,13 @@ pub trait Storage: Sized + Default {
1920

2021
fn report_usage(&mut self, ctx: Ctx, i: Id);
2122

22-
fn report_assign(&mut self, ctx: Ctx, i: Id, is_op: bool);
23+
fn report_assign(&mut self, ctx: Ctx, i: Id, is_op: bool, ty: Value<Type>);
2324

2425
fn declare_decl(
2526
&mut self,
2627
ctx: Ctx,
2728
i: &Ident,
28-
has_init: bool,
29+
init_type: Option<Value<Type>>,
2930
kind: Option<VarDeclKind>,
3031
) -> &mut Self::VarData;
3132

‎crates/swc_ecma_utils/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use tracing::trace;
3131
pub use self::{
3232
factory::{ExprFactory, FunctionFactory, IntoIndirectCall},
3333
value::{
34+
Merge,
3435
Type::{
3536
self, Bool as BoolType, Null as NullType, Num as NumberType, Obj as ObjectType,
3637
Str as StringType, Symbol as SymbolType, Undefined as UndefinedType,

‎crates/swc_ecma_utils/src/value.rs

+15
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,18 @@ impl Not for Value<bool> {
108108
}
109109
}
110110
}
111+
112+
pub trait Merge {
113+
fn merge(&mut self, rhs: Self);
114+
}
115+
116+
impl Merge for Option<Value<Type>> {
117+
fn merge(&mut self, rhs: Self) {
118+
*self = match (*self, rhs) {
119+
(None, None) => None,
120+
(None, Some(ty)) | (Some(ty), None) => Some(ty),
121+
(Some(ty1), Some(ty2)) if ty1 == ty2 => Some(ty1),
122+
_ => Some(Unknown),
123+
}
124+
}
125+
}

0 commit comments

Comments
 (0)
Please sign in to comment.