From cae222c20694012baac66f563a66e8dcf6ce48b2 Mon Sep 17 00:00:00 2001 From: sapphi-red <49056869+sapphi-red@users.noreply.github.com> Date: Sun, 24 Aug 2025 04:31:52 +0000 Subject: [PATCH] feat(minifier): compress `return void foo()` => `foo(); return` (#13271) Compress `return void foo()` => `foo(); return`. `return void 0` and `return undefined` was handled but this wasn't. --- crates/oxc_minifier/src/ctx.rs | 5 ++++ .../src/peephole/minimize_statements.rs | 29 ++++++++++++++++++- .../peephole/substitute_alternate_syntax.rs | 10 ++----- tasks/minsize/minsize.snap | 2 +- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/crates/oxc_minifier/src/ctx.rs b/crates/oxc_minifier/src/ctx.rs index 39f4ed64d6b6c..579fa4021566f 100644 --- a/crates/oxc_minifier/src/ctx.rs +++ b/crates/oxc_minifier/src/ctx.rs @@ -13,6 +13,7 @@ use oxc_syntax::{ identifier::{is_identifier_part, is_identifier_start}, reference::ReferenceId, }; +use oxc_traverse::Ancestor; use crate::{options::CompressOptions, state::MinifierState, symbol_value::SymbolValue}; @@ -253,4 +254,8 @@ impl<'a> Ctx<'a, '_> { chars.next().is_some_and(is_identifier_start) && chars.all(|c| is_identifier_part(c) && c != '・' && c != '・') } + + pub fn is_in_async_generator(&self) -> bool { + self.ancestors().any(|ancestor| matches!(ancestor, Ancestor::FunctionBody(body) if *body.r#async() && *body.generator())) + } } diff --git a/crates/oxc_minifier/src/peephole/minimize_statements.rs b/crates/oxc_minifier/src/peephole/minimize_statements.rs index 2d0243c3933b5..0b9606f011bd8 100644 --- a/crates/oxc_minifier/src/peephole/minimize_statements.rs +++ b/crates/oxc_minifier/src/peephole/minimize_statements.rs @@ -3,7 +3,10 @@ use std::{iter, ops::ControlFlow}; use oxc_allocator::{Box, TakeIn, Vec}; use oxc_ast::ast::*; use oxc_ast_visit::Visit; -use oxc_ecmascript::side_effects::MayHaveSideEffects; +use oxc_ecmascript::{ + constant_evaluation::{DetermineValueType, ValueType}, + side_effects::MayHaveSideEffects, +}; use oxc_semantic::ScopeId; use oxc_span::{ContentEq, GetSpan}; use oxc_traverse::Ancestor; @@ -610,6 +613,30 @@ impl<'a> PeepholeOptimizations { ctx: &mut Ctx<'a, '_>, ) { + if let Some(argument) = &mut ret_stmt.argument + && argument.value_type(ctx) == ValueType::Undefined + // `return undefined` has a different semantic in async generator function. + && !ctx.is_in_async_generator() + { + ctx.state.changed = true; + if argument.may_have_side_effects(ctx) { + if ctx.options().sequences + && let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut() + { + let a = &mut prev_expr_stmt.expression; + prev_expr_stmt.expression = Self::join_sequence(a, argument, ctx); + } else { + result.push( + ctx.ast.statement_expression(argument.span(), argument.take_in(ctx.ast)), + ); + } + } + ret_stmt.argument = None; + result.push(Statement::ReturnStatement(ret_stmt)); + *is_control_flow_dead = true; + return; + } + if ctx.options().sequences { if let Some(Statement::ExpressionStatement(prev_expr_stmt)) = result.last_mut() { if let Some(argument) = &mut ret_stmt.argument { diff --git a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs index 604fbb0e3b471..14d6047b89eef 100644 --- a/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs +++ b/crates/oxc_minifier/src/peephole/substitute_alternate_syntax.rs @@ -790,12 +790,8 @@ impl<'a> PeepholeOptimizations { return; } // `return undefined` has a different semantic in async generator function. - for ancestor in ctx.ancestors() { - if let Ancestor::FunctionBody(func) = ancestor { - if *func.r#async() && *func.generator() { - return; - } - } + if ctx.is_in_async_generator() { + return; } stmt.argument = None; ctx.state.changed = true; @@ -1482,7 +1478,7 @@ mod test { test("function f(){return !1;}", "function f(){return !1}"); test("function f(){return null;}", "function f(){return null}"); test("function f(){return void 0;}", "function f(){}"); - test("function f(){return void foo();}", "function f(){return void foo()}"); + test("function f(){return void foo();}", "function f(){foo()}"); test("function f(){return undefined;}", "function f(){}"); test("function f(){if(a()){return undefined;}}", "function f(){a()}"); test_same("function a(undefined) { return undefined; }"); diff --git a/tasks/minsize/minsize.snap b/tasks/minsize/minsize.snap index e7a5c8551d9c6..44622ed287e72 100644 --- a/tasks/minsize/minsize.snap +++ b/tasks/minsize/minsize.snap @@ -23,5 +23,5 @@ Original | minified | minified | gzip | gzip | Iterations | Fi 6.69 MB | 2.23 MB | 2.31 MB | 461.69 kB | 488.28 kB | 3 | antd.js -10.95 MB | 3.35 MB | 3.49 MB | 860.59 kB | 915.50 kB | 2 | typescript.js +10.95 MB | 3.35 MB | 3.49 MB | 860.58 kB | 915.50 kB | 2 | typescript.js