refactor(ecmascript): pass IsGlobalReference to MayHaveSideEffects instead of extending it (#9101)

so that it can be used without creating a struct that implements MayHaveSideEffects.
This commit is contained in:
sapphi-red committed 2025-02-14 07:35:30 +00:00
1 parent f4e2d4ed72
commit d670ec7c99
12 files changed
+140 -134

No files matched your search

@@ -16,7 +16,7 @@ pub use is_literal_value::IsLiteralValue;
pub use value::ConstantValue;
pub use value_type::{DetermineValueType, ValueType};
pub trait ConstantEvaluation<'a>: MayHaveSideEffects + DetermineValueType {
pub trait ConstantEvaluation<'a>: DetermineValueType {
fn ast(&self) -> AstBuilder<'a>;
fn resolve_binding(&self, ident: &IdentifierReference<'a>) -> Option<ConstantValue<'a>> {
@@ -36,7 +36,7 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects + DetermineValueType {
// and there are only a very few cases where we can compute a number value, but there could
// also be side effects. e.g. `void doSomething()` has value NaN, regardless of the behavior
// of `doSomething()`
if value.is_some() && self.expression_may_have_side_effects(expr) {
if value.is_some() && expr.may_have_side_effects(self) {
None
} else {
value
@@ -45,7 +45,7 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects + DetermineValueType {
fn get_side_free_string_value(&self, expr: &Expression<'a>) -> Option<Cow<'a, str>> {
let value = expr.to_js_string();
if value.is_some() && !self.expression_may_have_side_effects(expr) {
if value.is_some() && !expr.may_have_side_effects(self) {
return value;
}
None
@@ -53,7 +53,7 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects + DetermineValueType {
fn get_side_free_boolean_value(&self, expr: &Expression<'a>) -> Option<bool> {
let value = self.get_boolean_value(expr);
if value.is_some() && !self.expression_may_have_side_effects(expr) {
if value.is_some() && !expr.may_have_side_effects(self) {
return value;
}
None
@@ -61,7 +61,7 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects + DetermineValueType {
fn get_side_free_bigint_value(&self, expr: &Expression<'a>) -> Option<BigInt> {
let value = expr.to_big_int();
if value.is_some() && self.expression_may_have_side_effects(expr) {
if value.is_some() && expr.may_have_side_effects(self) {
None
} else {
value
@@ -241,9 +241,7 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects + DetermineValueType {
) -> Option<ConstantValue<'a>> {
match operator {
BinaryOperator::Addition => {
if self.expression_may_have_side_effects(left)
|| self.expression_may_have_side_effects(right)
{
if left.may_have_side_effects(self) || right.may_have_side_effects(self) {
return None;
}
let left_type = self.expression_value_type(left);
@@ -361,7 +359,7 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects + DetermineValueType {
None
}
BinaryOperator::Instanceof => {
if self.expression_may_have_side_effects(left) {
if left.may_have_side_effects(self) {
return None;
}
if let Expression::Identifier(right_ident) = right {
@@ -384,9 +382,7 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects + DetermineValueType {
| BinaryOperator::StrictInequality
| BinaryOperator::Equality
| BinaryOperator::Inequality => {
if self.expression_may_have_side_effects(left)
|| self.expression_may_have_side_effects(right)
{
if left.may_have_side_effects(self) || right.may_have_side_effects(self) {
return None;
}
let value = match operator {
@@ -424,7 +420,7 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects + DetermineValueType {
fn eval_unary_expression(&self, expr: &UnaryExpression<'a>) -> Option<ConstantValue<'a>> {
match expr.operator {
UnaryOperator::Typeof => {
if self.expression_may_have_side_effects(&expr.argument) {
if expr.argument.may_have_side_effects(self) {
return None;
}
let arg_ty = self.expression_value_type(&expr.argument);
@@ -447,8 +443,9 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects + DetermineValueType {
};
Some(ConstantValue::String(Cow::Borrowed(s)))
}
UnaryOperator::Void => (!self.expression_may_have_side_effects(&expr.argument))
.then_some(ConstantValue::Undefined),
UnaryOperator::Void => {
(!expr.argument.may_have_side_effects(self)).then_some(ConstantValue::Undefined)
}
UnaryOperator::LogicalNot => self
.get_side_free_boolean_value(&expr.argument)
.map(|b| !b)
@@ -493,7 +490,7 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects + DetermineValueType {
if let Some(ConstantValue::String(s)) = self.eval_expression(&expr.object) {
Some(ConstantValue::Number(s.encode_utf16().count().to_f64().unwrap()))
} else {
if self.expression_may_have_side_effects(&expr.object) {
if expr.object.may_have_side_effects(self) {
return None;
}
if let Expression::ArrayExpression(arr) = &expr.object {
@@ -516,7 +513,7 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects + DetermineValueType {
if let Some(ConstantValue::String(s)) = self.eval_expression(&expr.object) {
Some(ConstantValue::Number(s.encode_utf16().count().to_f64().unwrap()))
} else {
if self.expression_may_have_side_effects(&expr.object) {
if expr.object.may_have_side_effects(self) {
return None;
}
if let Expression::ArrayExpression(arr) = &expr.object {
@@ -532,7 +529,7 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects + DetermineValueType {
/// <https://tc39.es/ecma262/multipage/abstract-operations.html#sec-islessthan>
fn is_less_than(&self, x: &Expression<'a>, y: &Expression<'a>) -> Option<ConstantValue<'a>> {
if self.expression_may_have_side_effects(x) || self.expression_may_have_side_effects(y) {
if x.may_have_side_effects(self) || y.may_have_side_effects(self) {
return None;
}
@@ -60,7 +60,7 @@ impl ValueType {
///
/// Evaluate the expression and attempt to determine which ValueType it could resolve to.
/// This function ignores the cases that throws an error, e.g. `foo * 0` can throw an error when `foo` is a bigint.
/// To detect those cases, use [`crate::side_effects::MayHaveSideEffects::expression_may_have_side_effects`].
/// To detect those cases, use [`crate::side_effects::MayHaveSideEffects`].
pub trait DetermineValueType: IsGlobalReference {
fn expression_value_type(&self, expr: &Expression<'_>) -> ValueType {
match expr {
@@ -1,6 +1,6 @@
use oxc_ast::ast::IdentifierReference;
pub trait IsGlobalReference {
pub trait IsGlobalReference: Sized {
/// Whether the reference is a global reference.
///
/// - None means it is unknown.
@@ -13,15 +13,19 @@ use crate::is_global_reference::IsGlobalReference;
/// - TDZ errors does not happen.
///
/// Ported from [closure-compiler](https://github.com/google/closure-compiler/blob/f3ce5ed8b630428e311fe9aa2e20d36560d975e2/src/com/google/javascript/jscomp/AstAnalyzer.java#L94)
pub trait MayHaveSideEffects: Sized + IsGlobalReference {
fn expression_may_have_side_effects(&self, e: &Expression<'_>) -> bool {
match e {
pub trait MayHaveSideEffects {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool;
}
impl MayHaveSideEffects for Expression<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
match self {
Expression::Identifier(ident) => match ident.name.as_str() {
"NaN" | "Infinity" | "undefined" => false,
// Reading global variables may have a side effect.
// NOTE: It should also return true when the reference might refer to a reference value created by a with statement
// NOTE: we ignore TDZ errors
_ => self.is_global_reference(ident) != Some(false),
_ => is_global_reference.is_global_reference(ident) != Some(false),
},
Expression::NumericLiteral(_)
| Expression::BooleanLiteral(_)
@@ -35,65 +39,69 @@ pub trait MayHaveSideEffects: Sized + IsGlobalReference {
| Expression::FunctionExpression(_)
| Expression::Super(_) => false,
Expression::TemplateLiteral(template) => {
template.expressions.iter().any(|e| self.expression_may_have_side_effects(e))
template.expressions.iter().any(|e| e.may_have_side_effects(is_global_reference))
}
Expression::UnaryExpression(e) => self.unary_expression_may_have_side_effects(e),
Expression::LogicalExpression(e) => self.logical_expression_may_have_side_effects(e),
Expression::UnaryExpression(e) => e.may_have_side_effects(is_global_reference),
Expression::LogicalExpression(e) => e.may_have_side_effects(is_global_reference),
Expression::ParenthesizedExpression(e) => {
self.expression_may_have_side_effects(&e.expression)
e.expression.may_have_side_effects(is_global_reference)
}
Expression::ConditionalExpression(e) => {
self.expression_may_have_side_effects(&e.test)
|| self.expression_may_have_side_effects(&e.consequent)
|| self.expression_may_have_side_effects(&e.alternate)
e.test.may_have_side_effects(is_global_reference)
|| e.consequent.may_have_side_effects(is_global_reference)
|| e.alternate.may_have_side_effects(is_global_reference)
}
Expression::SequenceExpression(e) => {
e.expressions.iter().any(|e| self.expression_may_have_side_effects(e))
e.expressions.iter().any(|e| e.may_have_side_effects(is_global_reference))
}
Expression::BinaryExpression(e) => self.binary_expression_may_have_side_effects(e),
Expression::BinaryExpression(e) => e.may_have_side_effects(is_global_reference),
Expression::ObjectExpression(object_expr) => object_expr
.properties
.iter()
.any(|property| self.object_property_kind_may_have_side_effects(property)),
Expression::ArrayExpression(e) => self.array_expression_may_have_side_effects(e),
Expression::ClassExpression(e) => self.class_may_have_side_effects(e),
.any(|property| property.may_have_side_effects(is_global_reference)),
Expression::ArrayExpression(e) => e.may_have_side_effects(is_global_reference),
Expression::ClassExpression(e) => e.may_have_side_effects(is_global_reference),
// NOTE: private in can throw `TypeError`
_ => true,
}
}
}
fn unary_expression_may_have_side_effects(&self, e: &UnaryExpression<'_>) -> bool {
match e.operator {
impl MayHaveSideEffects for UnaryExpression<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
match self.operator {
UnaryOperator::Delete => true,
UnaryOperator::Void | UnaryOperator::LogicalNot => {
self.expression_may_have_side_effects(&e.argument)
self.argument.may_have_side_effects(is_global_reference)
}
UnaryOperator::Typeof => {
if matches!(&e.argument, Expression::Identifier(_)) {
if matches!(&self.argument, Expression::Identifier(_)) {
false
} else {
self.expression_may_have_side_effects(&e.argument)
self.argument.may_have_side_effects(is_global_reference)
}
}
UnaryOperator::UnaryPlus => {
// ToNumber throws an error when the argument is Symbol / BigInt / an object that
// returns Symbol or BigInt from ToPrimitive
maybe_symbol_or_bigint_or_to_primitive_may_return_symbol_or_bigint(
self,
&e.argument,
) || self.expression_may_have_side_effects(&e.argument)
is_global_reference,
&self.argument,
) || self.argument.may_have_side_effects(is_global_reference)
}
UnaryOperator::UnaryNegation | UnaryOperator::BitwiseNot => {
// ToNumeric throws an error when the argument is Symbol / an object that
// returns Symbol from ToPrimitive
maybe_symbol_or_to_primitive_may_return_symbol(self, &e.argument)
|| self.expression_may_have_side_effects(&e.argument)
maybe_symbol_or_to_primitive_may_return_symbol(is_global_reference, &self.argument)
|| self.argument.may_have_side_effects(is_global_reference)
}
}
}
}
fn binary_expression_may_have_side_effects(&self, e: &BinaryExpression<'_>) -> bool {
match e.operator {
impl MayHaveSideEffects for BinaryExpression<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
match self.operator {
BinaryOperator::Equality
| BinaryOperator::Inequality
| BinaryOperator::StrictEquality
@@ -102,27 +110,27 @@ pub trait MayHaveSideEffects: Sized + IsGlobalReference {
| BinaryOperator::LessEqualThan
| BinaryOperator::GreaterThan
| BinaryOperator::GreaterEqualThan => {
self.expression_may_have_side_effects(&e.left)
|| self.expression_may_have_side_effects(&e.right)
self.left.may_have_side_effects(is_global_reference)
|| self.right.may_have_side_effects(is_global_reference)
}
BinaryOperator::In | BinaryOperator::Instanceof => {
// instanceof and in can throw `TypeError`
true
}
BinaryOperator::Addition => {
if is_string_or_to_primitive_returns_string(&e.left)
|| is_string_or_to_primitive_returns_string(&e.right)
if is_string_or_to_primitive_returns_string(&self.left)
|| is_string_or_to_primitive_returns_string(&self.right)
{
let other_side = if is_string_or_to_primitive_returns_string(&e.left) {
&e.right
let other_side = if is_string_or_to_primitive_returns_string(&self.left) {
&self.right
} else {
&e.left
&self.left
};
maybe_symbol_or_to_primitive_may_return_symbol(self, other_side)
|| self.expression_may_have_side_effects(&e.left)
|| self.expression_may_have_side_effects(&e.right)
} else if e.left.is_number() || e.right.is_number() {
let other_side = if e.left.is_number() { &e.right } else { &e.left };
maybe_symbol_or_to_primitive_may_return_symbol(is_global_reference, other_side)
|| self.left.may_have_side_effects(is_global_reference)
|| self.right.may_have_side_effects(is_global_reference)
} else if self.left.is_number() || self.right.is_number() {
let other_side = if self.left.is_number() { &self.right } else { &self.left };
!matches!(
other_side,
Expression::NullLiteral(_)
@@ -130,7 +138,7 @@ pub trait MayHaveSideEffects: Sized + IsGlobalReference {
| Expression::BooleanLiteral(_)
)
} else {
!(e.left.is_big_int_literal() && e.right.is_big_int_literal())
!(self.left.is_big_int_literal() && self.right.is_big_int_literal())
}
}
BinaryOperator::Subtraction
@@ -144,11 +152,11 @@ pub trait MayHaveSideEffects: Sized + IsGlobalReference {
| BinaryOperator::BitwiseAnd
| BinaryOperator::Exponential
| BinaryOperator::ShiftRightZeroFill => {
if e.left.is_big_int_literal() || e.right.is_big_int_literal() {
if self.left.is_big_int_literal() || self.right.is_big_int_literal() {
if let (Expression::BigIntLiteral(_), Expression::BigIntLiteral(right)) =
(&e.left, &e.right)
(&self.left, &self.right)
{
match e.operator {
match self.operator {
BinaryOperator::Exponential => right.is_negative(),
BinaryOperator::Division | BinaryOperator::Remainder => right.is_zero(),
BinaryOperator::ShiftRightZeroFill => true,
@@ -158,102 +166,111 @@ pub trait MayHaveSideEffects: Sized + IsGlobalReference {
true
}
} else if !(maybe_symbol_or_bigint_or_to_primitive_may_return_symbol_or_bigint(
self, &e.left,
is_global_reference,
&self.left,
) || maybe_symbol_or_bigint_or_to_primitive_may_return_symbol_or_bigint(
self, &e.right,
is_global_reference,
&self.right,
)) {
self.expression_may_have_side_effects(&e.left)
|| self.expression_may_have_side_effects(&e.right)
self.left.may_have_side_effects(is_global_reference)
|| self.right.may_have_side_effects(is_global_reference)
} else {
true
}
}
}
}
}
fn logical_expression_may_have_side_effects(&self, e: &LogicalExpression<'_>) -> bool {
self.expression_may_have_side_effects(&e.left)
|| self.expression_may_have_side_effects(&e.right)
impl MayHaveSideEffects for LogicalExpression<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
self.left.may_have_side_effects(is_global_reference)
|| self.right.may_have_side_effects(is_global_reference)
}
}
fn array_expression_may_have_side_effects(&self, e: &ArrayExpression<'_>) -> bool {
e.elements
.iter()
.any(|element| self.array_expression_element_may_have_side_effects(element))
impl MayHaveSideEffects for ArrayExpression<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
self.elements.iter().any(|element| element.may_have_side_effects(is_global_reference))
}
}
fn array_expression_element_may_have_side_effects(
&self,
e: &ArrayExpressionElement<'_>,
) -> bool {
match e {
impl MayHaveSideEffects for ArrayExpressionElement<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
match self {
ArrayExpressionElement::SpreadElement(e) => match &e.argument {
Expression::ArrayExpression(arr) => {
self.array_expression_may_have_side_effects(arr)
}
Expression::ArrayExpression(arr) => arr.may_have_side_effects(is_global_reference),
Expression::StringLiteral(_) => false,
Expression::TemplateLiteral(t) => {
t.expressions.iter().any(|e| self.expression_may_have_side_effects(e))
t.expressions.iter().any(|e| e.may_have_side_effects(is_global_reference))
}
_ => true,
},
match_expression!(ArrayExpressionElement) => {
self.expression_may_have_side_effects(e.to_expression())
self.to_expression().may_have_side_effects(is_global_reference)
}
ArrayExpressionElement::Elision(_) => false,
}
}
}
fn object_property_kind_may_have_side_effects(&self, e: &ObjectPropertyKind<'_>) -> bool {
match e {
ObjectPropertyKind::ObjectProperty(o) => self.object_property_may_have_side_effects(o),
impl MayHaveSideEffects for ObjectPropertyKind<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
match self {
ObjectPropertyKind::ObjectProperty(o) => o.may_have_side_effects(is_global_reference),
ObjectPropertyKind::SpreadProperty(e) => match &e.argument {
Expression::ArrayExpression(arr) => {
self.array_expression_may_have_side_effects(arr)
}
Expression::ArrayExpression(arr) => arr.may_have_side_effects(is_global_reference),
Expression::StringLiteral(_) => false,
Expression::TemplateLiteral(t) => {
t.expressions.iter().any(|e| self.expression_may_have_side_effects(e))
t.expressions.iter().any(|e| e.may_have_side_effects(is_global_reference))
}
_ => true,
},
}
}
}
fn object_property_may_have_side_effects(&self, e: &ObjectProperty<'_>) -> bool {
self.property_key_may_have_side_effects(&e.key)
|| self.expression_may_have_side_effects(&e.value)
impl MayHaveSideEffects for ObjectProperty<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
self.key.may_have_side_effects(is_global_reference)
|| self.value.may_have_side_effects(is_global_reference)
}
}
fn property_key_may_have_side_effects(&self, key: &PropertyKey<'_>) -> bool {
match key {
impl MayHaveSideEffects for PropertyKey<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
match self {
PropertyKey::StaticIdentifier(_) | PropertyKey::PrivateIdentifier(_) => false,
match_expression!(PropertyKey) => {
self.expression_may_have_side_effects(key.to_expression())
self.to_expression().may_have_side_effects(is_global_reference)
}
}
}
}
fn class_may_have_side_effects(&self, class: &Class<'_>) -> bool {
class.body.body.iter().any(|element| self.class_element_may_have_side_effects(element))
impl MayHaveSideEffects for Class<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
self.body.body.iter().any(|element| element.may_have_side_effects(is_global_reference))
}
}
fn class_element_may_have_side_effects(&self, e: &ClassElement<'_>) -> bool {
match e {
impl MayHaveSideEffects for ClassElement<'_> {
fn may_have_side_effects(&self, is_global_reference: &impl IsGlobalReference) -> bool {
match self {
// TODO: check side effects inside the block
ClassElement::StaticBlock(block) => !block.body.is_empty(),
ClassElement::MethodDefinition(e) => {
e.r#static && self.property_key_may_have_side_effects(&e.key)
e.r#static && e.key.may_have_side_effects(is_global_reference)
}
ClassElement::PropertyDefinition(e) => {
e.r#static
&& (self.property_key_may_have_side_effects(&e.key)
&& (e.key.may_have_side_effects(is_global_reference)
|| e.value
.as_ref()
.is_some_and(|v| self.expression_may_have_side_effects(v)))
.is_some_and(|v| v.may_have_side_effects(is_global_reference)))
}
ClassElement::AccessorProperty(e) => {
e.r#static && self.property_key_may_have_side_effects(&e.key)
e.r#static && e.key.may_have_side_effects(is_global_reference)
}
ClassElement::TSIndexSignature(_) => false,
}
@@ -277,13 +294,13 @@ fn is_string_or_to_primitive_returns_string(expr: &Expression<'_>) -> bool {
/// Whether the given expression may be a `Symbol` or converted to a `Symbol` when passed to `toPrimitive`.
fn maybe_symbol_or_to_primitive_may_return_symbol(
m: &impl MayHaveSideEffects,
is_global_reference: &impl IsGlobalReference,
expr: &Expression<'_>,
) -> bool {
match expr {
Expression::Identifier(ident) => {
!(matches!(ident.name.as_str(), "Infinity" | "NaN" | "undefined")
&& m.is_global_reference(ident) == Some(true))
&& is_global_reference.is_global_reference(ident) == Some(true))
}
Expression::StringLiteral(_)
| Expression::TemplateLiteral(_)
@@ -304,13 +321,13 @@ fn maybe_symbol_or_to_primitive_may_return_symbol(
/// Whether the given expression may be a `Symbol`/`BigInt` or converted to a `Symbol`/`BigInt` when passed to `toPrimitive`.
fn maybe_symbol_or_bigint_or_to_primitive_may_return_symbol_or_bigint(
m: &impl MayHaveSideEffects,
is_global_reference: &impl IsGlobalReference,
expr: &Expression<'_>,
) -> bool {
match expr {
Expression::Identifier(ident) => {
!(matches!(ident.name.as_str(), "Infinity" | "NaN" | "undefined")
&& m.is_global_reference(ident) == Some(true))
&& is_global_reference.is_global_reference(ident) == Some(true))
}
Expression::StringLiteral(_)
| Expression::TemplateLiteral(_)
+1 -6
View File
@@ -1,10 +1,7 @@
use std::ops::Deref;
use oxc_ast::{ast::*, AstBuilder};
use oxc_ecmascript::{
constant_evaluation::{ConstantEvaluation, ConstantValue, DetermineValueType},
side_effects::MayHaveSideEffects,
};
use oxc_ecmascript::constant_evaluation::{ConstantEvaluation, ConstantValue, DetermineValueType};
use oxc_semantic::{IsGlobalReference, SymbolTable};
use oxc_traverse::TraverseCtx;
@@ -25,8 +22,6 @@ impl oxc_ecmascript::is_global_reference::IsGlobalReference for Ctx<'_, '_> {
}
}
impl MayHaveSideEffects for Ctx<'_, '_> {}
impl DetermineValueType for Ctx<'_, '_> {}
impl<'a> ConstantEvaluation<'a> for Ctx<'a, '_> {
@@ -106,7 +106,7 @@ impl<'a> PeepholeOptimizations {
// (FALSE && x) => FALSE
if if lval { op.is_or() } else { op.is_and() } {
return Some(ctx.ast.move_expression(&mut logical_expr.left));
} else if !ctx.expression_may_have_side_effects(left) {
} else if !left.may_have_side_effects(&ctx) {
let parent = ctx.ancestry.parent();
// Bail `let o = { f() { assert.ok(this !== o); } }; (true && o.f)(); (true && o.f)``;`
if parent.is_tagged_template_expression()
@@ -131,7 +131,7 @@ impl<'a> PeepholeOptimizations {
let left_child_right_boolean = ctx.get_boolean_value(&left_child.right);
let left_child_op = left_child.operator;
if let Some(right_boolean) = left_child_right_boolean {
if !ctx.expression_may_have_side_effects(&left_child.right) {
if !left_child.right.may_have_side_effects(&ctx) {
// a || false || b => a || b
// a && true && b => a && b
if !right_boolean && left_child_op.is_or()
@@ -164,7 +164,7 @@ impl<'a> PeepholeOptimizations {
let left_val = ctx.expression_value_type(left);
match left_val {
ValueType::Null | ValueType::Undefined => {
Some(if ctx.expression_may_have_side_effects(left) {
Some(if left.may_have_side_effects(&ctx) {
// e.g. `(a(), null) ?? 1` => `(a(), null, 1)`
let expressions = ctx.ast.vec_from_array([
ctx.ast.move_expression(&mut logical_expr.left),
@@ -247,8 +247,8 @@ impl<'a> PeepholeOptimizations {
// we can improve compression by allowing side effects on one side if the other side is
// an identifier that is not modified after it is declared.
// but for now, we only perform compression if neither side has side effects.
&& !(ctx.expression_may_have_side_effects(&expr.test)
|| ctx.expression_may_have_side_effects(&consequent.callee))
&& !(expr.test.may_have_side_effects(&ctx)
|| consequent.callee.may_have_side_effects(&ctx))
&& ctx.expr_eq(&consequent.callee, &alternate.callee)
&& consequent
.arguments
@@ -574,7 +574,7 @@ impl<'a> PeepholeOptimizations {
&& var_decl.declarations[0]
.init
.as_ref()
.is_some_and(|init| ctx.expression_may_have_side_effects(init))
.is_some_and(|init| init.may_have_side_effects(&ctx))
} else {
// the spec does not allow multiple declarations though
true
@@ -340,7 +340,7 @@ impl<'a, 'b> PeepholeOptimizations {
}
ctx.get_boolean_value(&expr.test).map(|v| {
if ctx.expression_may_have_side_effects(&expr.test) {
if expr.test.may_have_side_effects(&ctx) {
// "(a, true) ? b : c" => "a, b"
let exprs = ctx.ast.vec_from_iter([
{
@@ -377,9 +377,7 @@ impl<'a, 'b> PeepholeOptimizations {
let (should_fold, new_len) = sequence_expr.expressions.iter().enumerate().fold(
(false, 0),
|(mut should_fold, mut new_len), (i, expr)| {
if i == sequence_expr.expressions.len() - 1
|| ctx.expression_may_have_side_effects(expr)
{
if i == sequence_expr.expressions.len() - 1 || expr.may_have_side_effects(&ctx) {
new_len += 1;
} else {
should_fold = true;
@@ -396,7 +394,7 @@ impl<'a, 'b> PeepholeOptimizations {
let mut new_exprs = ctx.ast.vec_with_capacity(new_len);
let len = sequence_expr.expressions.len();
for (i, expr) in sequence_expr.expressions.iter_mut().enumerate() {
if i == len - 1 || ctx.expression_may_have_side_effects(expr) {
if i == len - 1 || expr.may_have_side_effects(&ctx) {
new_exprs.push(ctx.ast.move_expression(expr));
}
}
@@ -170,7 +170,7 @@ impl<'a> PeepholeOptimizations {
| ValueType::Boolean
| ValueType::Number
| ValueType::String
) && !ctx.expression_may_have_side_effects(arg)
) && !arg.may_have_side_effects(&ctx)
}
_ => false,
},
@@ -536,7 +536,7 @@ impl<'a> PeepholeOptimizations {
if !match argument {
Expression::Identifier(ident) => ctx.is_identifier_undefined(ident),
Expression::UnaryExpression(e) => {
e.operator.is_void() && !ctx.expression_may_have_side_effects(argument)
e.operator.is_void() && !argument.may_have_side_effects(&ctx)
}
_ => false,
} {
@@ -4,15 +4,14 @@ use oxc_ecmascript::{is_global_reference::IsGlobalReference, side_effects::MayHa
use oxc_parser::Parser;
use oxc_span::SourceType;
struct SideEffectChecker {
struct GlobalReferenceChecker {
global_variable_names: Vec<String>,
}
impl IsGlobalReference for SideEffectChecker {
impl IsGlobalReference for GlobalReferenceChecker {
fn is_global_reference(&self, ident: &IdentifierReference<'_>) -> Option<bool> {
Some(self.global_variable_names.iter().any(|name| name == ident.name.as_str()))
}
}
impl MayHaveSideEffects for SideEffectChecker {}
fn test(source_text: &str, expected: bool) {
test_with_global_variables(source_text, vec![], expected);
@@ -28,13 +27,13 @@ fn test_with_global_variables(
assert!(!ret.panicked, "{source_text}");
assert!(ret.errors.is_empty(), "{source_text}");
let side_effect_checker = SideEffectChecker { global_variable_names };
let global_reference_checker = GlobalReferenceChecker { global_variable_names };
let Some(Statement::ExpressionStatement(stmt)) = &ret.program.body.first() else {
panic!("should have a expression statement body: {source_text}");
};
assert_eq!(
side_effect_checker.expression_may_have_side_effects(&stmt.expression),
stmt.expression.may_have_side_effects(&global_reference_checker),
expected,
"{source_text}"
);