-
-
Notifications
You must be signed in to change notification settings - Fork 892
Description
Summary
Currently, Scoping::no_side_effects only tracks symbols annotated with @__NO_SIDE_EFFECTS__ (the .pure flag).
oxc/crates/oxc_semantic/src/binder.rs
Lines 101 to 112 in e4aa5b5
| // Save `@__NO_SIDE_EFFECTS__` for function initializers. | |
| if let BindingPattern::BindingIdentifier(id) = &self.id | |
| && let Some(symbol_id) = id.symbol_id.get() | |
| && let Some(init) = &self.init | |
| && match init { | |
| Expression::FunctionExpression(func) => func.pure, | |
| Expression::ArrowFunctionExpression(func) => func.pure, | |
| _ => false, | |
| } | |
| { | |
| builder.scoping.no_side_effects.insert(symbol_id); | |
| } |
Propose also marking functions with empty bodies and simple parameters (no destructuring, no defaults).
This would allow Rolldown to use scoping.no_side_effects() as the single source of truth for "calling this function has no side effects", eliminating the need for a separate SymbolRefFlags::SideEffectsFreeFunction flag that currently duplicates this logic in the bundler.
Current Rolldown duplication
Rolldown maintains its own empty-function detection in rolldown_ecmascript_utils/src/extensions/ast_ext/function.rs and uses it alongside .pure in the scanner (ast_scanner/mod.rs#L738-L748):
let is_side_effect_free_function = decl
.init
.as_ref()
.map(|expr| match expr {
Expression::FunctionExpression(func) => func.is_side_effect_free() || func.pure,
Expression::ArrowFunctionExpression(func) => {
func.is_side_effect_free() || func.pure
}
_ => false,
})
.unwrap_or(false);This sets SymbolRefFlags::SideEffectsFreeFunction, which is then consumed during cross-module optimization (cross_module_optimization.rs#L62-L85) to identify pure function calls across module boundaries.
Proposed change
In crates/oxc_semantic/src/binder.rs:
Function::bind (line ~155):
// Current
if self.pure {
builder.scoping.no_side_effects.insert(symbol_id);
}
// Proposed
if self.pure || is_empty_and_simple_params(self) {
builder.scoping.no_side_effects.insert(symbol_id);
}VariableDeclarator::bind (line ~101): same extension for function expression / arrow function initializers.
Where "empty and simple params" means:
- Body has no statements (or
None) - All params are
BindingIdentifierwith no initializer
Industry precedent
| Tool | Removes calls to empty functions? | Mechanism |
|---|---|---|
| Rollup | Yes (tree-shaking) | Traces into function body via hasEffectsOnInteractionAtPath |
| esbuild | Yes (minify) | IsEmptyFunction symbol flag + removal at print time |
| Terser | Yes (with toplevel) |
Inlines empty function body, then drops |
| SWC | No | Conservative, requires explicit annotations |
3 out of 4 major tools treat empty function calls as side-effect-free. Verified with actual test runs against Rollup 4.59 and esbuild 0.27.