mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
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:
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,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}"
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user