Skip to content

Commit d68f127

Browse files
committed
feat(minifier): remove pure function calls when the return value is not used
1 parent 57dafa2 commit d68f127

File tree

4 files changed

+43
-15
lines changed

4 files changed

+43
-15
lines changed

crates/oxc_minifier/src/peephole/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ impl<'a> Traverse<'a, MinifierState<'a>> for PeepholeOptimizations {
132132

133133
fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
134134
let ctx = &mut Ctx::new(ctx);
135-
Self::keep_track_of_empty_functions(stmt, ctx);
135+
Self::keep_track_of_pure_functions(stmt, ctx);
136136
}
137137

138138
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {

crates/oxc_minifier/src/peephole/remove_dead_code.rs

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
use oxc_allocator::{TakeIn, Vec};
22
use oxc_ast::ast::*;
33
use oxc_ast_visit::Visit;
4-
use oxc_ecmascript::{constant_evaluation::ConstantEvaluation, side_effects::MayHaveSideEffects};
4+
use oxc_ecmascript::{
5+
constant_evaluation::{ConstantEvaluation, ConstantValue},
6+
side_effects::MayHaveSideEffects,
7+
};
58
use oxc_span::GetSpan;
69
use oxc_traverse::Ancestor;
710

@@ -340,11 +343,11 @@ impl<'a> PeepholeOptimizations {
340343
}
341344
}
342345

343-
pub fn keep_track_of_empty_functions(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
346+
pub fn keep_track_of_pure_functions(stmt: &mut Statement<'a>, ctx: &mut Ctx<'a, '_>) {
344347
match stmt {
345348
Statement::FunctionDeclaration(f) => {
346349
if let Some(body) = &f.body {
347-
Self::try_save_empty_function(
350+
Self::try_save_pure_function(
348351
f.id.as_ref(),
349352
&f.params,
350353
body,
@@ -359,7 +362,7 @@ impl<'a> PeepholeOptimizations {
359362
if let BindingPatternKind::BindingIdentifier(id) = &d.id.kind {
360363
match &d.init {
361364
Some(Expression::ArrowFunctionExpression(a)) => {
362-
Self::try_save_empty_function(
365+
Self::try_save_pure_function(
363366
Some(id),
364367
&a.params,
365368
&a.body,
@@ -370,7 +373,7 @@ impl<'a> PeepholeOptimizations {
370373
}
371374
Some(Expression::FunctionExpression(f)) => {
372375
if let Some(body) = &f.body {
373-
Self::try_save_empty_function(
376+
Self::try_save_pure_function(
374377
Some(id),
375378
&f.params,
376379
body,
@@ -389,24 +392,30 @@ impl<'a> PeepholeOptimizations {
389392
}
390393
}
391394

392-
fn try_save_empty_function(
395+
fn try_save_pure_function(
393396
id: Option<&BindingIdentifier<'a>>,
394397
params: &FormalParameters<'a>,
395398
body: &FunctionBody<'a>,
396399
r#async: bool,
397400
generator: bool,
398401
ctx: &mut Ctx<'a, '_>,
399402
) {
400-
if !body.is_empty() || r#async || generator {
403+
if r#async || generator {
401404
return;
402405
}
403406
// `function foo({}) {} foo(null)` is runtime type error.
404407
if !params.items.iter().all(|pat| pat.pattern.kind.is_binding_identifier()) {
405408
return;
406409
}
410+
if body.statements.iter().any(|stmt| stmt.may_have_side_effects(ctx)) {
411+
return;
412+
}
407413
let Some(symbol_id) = id.and_then(|id| id.symbol_id.get()) else { return };
408414
if ctx.scoping().get_resolved_references(symbol_id).all(|r| r.flags().is_read_only()) {
409-
ctx.state.empty_functions.insert(symbol_id);
415+
ctx.state.pure_functions.insert(
416+
symbol_id,
417+
if body.is_empty() { Some(ConstantValue::Undefined) } else { None },
418+
);
410419
}
411420
}
412421

@@ -415,7 +424,10 @@ impl<'a> PeepholeOptimizations {
415424
if let Expression::Identifier(ident) = &e.callee {
416425
if let Some(reference_id) = ident.reference_id.get() {
417426
if let Some(symbol_id) = ctx.scoping().get_reference(reference_id).symbol_id() {
418-
if ctx.state.empty_functions.contains(&symbol_id) {
427+
if matches!(
428+
ctx.state.pure_functions.get(&symbol_id),
429+
Some(Some(ConstantValue::Undefined))
430+
) {
419431
let mut exprs =
420432
Self::fold_arguments_into_needed_expressions(&mut e.arguments, ctx);
421433
if exprs.is_empty() {

crates/oxc_minifier/src/peephole/remove_unused_expression.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -521,7 +521,19 @@ impl<'a> PeepholeOptimizations {
521521
fn remove_unused_call_expr(e: &mut Expression<'a>, ctx: &mut Ctx<'a, '_>) -> bool {
522522
let Expression::CallExpression(call_expr) = e else { return false };
523523

524-
if call_expr.pure && ctx.annotations() {
524+
let is_pure = {
525+
(call_expr.pure && ctx.annotations())
526+
|| (if let Expression::Identifier(id) = &call_expr.callee
527+
&& let Some(symbol_id) =
528+
ctx.scoping().get_reference(id.reference_id()).symbol_id()
529+
{
530+
ctx.state.pure_functions.contains_key(&symbol_id)
531+
} else {
532+
false
533+
})
534+
};
535+
536+
if is_pure {
525537
let mut exprs =
526538
Self::fold_arguments_into_needed_expressions(&mut call_expr.arguments, ctx);
527539
if exprs.is_empty() {
@@ -954,6 +966,9 @@ mod test {
954966
test("/* @__PURE__ */ new Foo(a)", "a");
955967
test("true && /* @__PURE__ */ noEffect()", "");
956968
test("false || /* @__PURE__ */ noEffect()", "");
969+
970+
test("var foo = () => 1; foo(), foo()", "var foo = () => 1");
971+
test_same("var foo = () => { bar() }; foo(), foo()");
957972
}
958973

959974
#[test]

crates/oxc_minifier/src/state.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use rustc_hash::FxHashSet;
1+
use oxc_ecmascript::constant_evaluation::ConstantValue;
2+
use rustc_hash::FxHashMap;
23

34
use oxc_span::SourceType;
45
use oxc_syntax::symbol::SymbolId;
@@ -10,8 +11,8 @@ pub struct MinifierState<'a> {
1011

1112
pub options: CompressOptions,
1213

13-
/// Function declarations that are empty
14-
pub empty_functions: FxHashSet<SymbolId>,
14+
/// The return value of function declarations that are pure
15+
pub pure_functions: FxHashMap<SymbolId, Option<ConstantValue<'a>>>,
1516

1617
pub symbol_values: SymbolValues<'a>,
1718

@@ -23,7 +24,7 @@ impl MinifierState<'_> {
2324
Self {
2425
source_type,
2526
options,
26-
empty_functions: FxHashSet::default(),
27+
pure_functions: FxHashMap::default(),
2728
symbol_values: SymbolValues::default(),
2829
changed: false,
2930
}

0 commit comments

Comments
 (0)