mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
refactor(minifier): move equality comparison to ConstantEvaluation (#9009)
This commit is contained in:
4 files changed
+170
-188
No files matched your search
@@ -0,0 +1,130 @@
|
||||
use oxc_ast::ast::{Expression, NumberBase};
|
||||
|
||||
use super::{ConstantEvaluation, ValueType};
|
||||
|
||||
/// <https://tc39.es/ecma262/#sec-abstract-equality-comparison>
|
||||
pub(super) fn abstract_equality_comparison<'a>(
|
||||
c: &impl ConstantEvaluation<'a>,
|
||||
left_expr: &Expression<'a>,
|
||||
right_expr: &Expression<'a>,
|
||||
) -> Option<bool> {
|
||||
let left = ValueType::from(left_expr);
|
||||
let right = ValueType::from(right_expr);
|
||||
if left != ValueType::Undetermined && right != ValueType::Undetermined {
|
||||
if left == right {
|
||||
return strict_equality_comparison(c, left_expr, right_expr);
|
||||
}
|
||||
if matches!(
|
||||
(left, right),
|
||||
(ValueType::Null, ValueType::Undefined) | (ValueType::Undefined, ValueType::Null)
|
||||
) {
|
||||
return Some(true);
|
||||
}
|
||||
|
||||
if matches!((left, right), (ValueType::Number, ValueType::String))
|
||||
|| matches!(right, ValueType::Boolean)
|
||||
{
|
||||
if let Some(num) = c.get_side_free_number_value(right_expr) {
|
||||
let number_literal_expr = c.ast().expression_numeric_literal(
|
||||
oxc_span::SPAN,
|
||||
num,
|
||||
None,
|
||||
if num.fract() == 0.0 { NumberBase::Decimal } else { NumberBase::Float },
|
||||
);
|
||||
return abstract_equality_comparison(c, left_expr, &number_literal_expr);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
if matches!((left, right), (ValueType::String, ValueType::Number))
|
||||
|| matches!(left, ValueType::Boolean)
|
||||
{
|
||||
if let Some(num) = c.get_side_free_number_value(left_expr) {
|
||||
let number_literal_expr = c.ast().expression_numeric_literal(
|
||||
oxc_span::SPAN,
|
||||
num,
|
||||
None,
|
||||
if num.fract() == 0.0 { NumberBase::Decimal } else { NumberBase::Float },
|
||||
);
|
||||
return abstract_equality_comparison(c, &number_literal_expr, right_expr);
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
if matches!(left, ValueType::BigInt) || matches!(right, ValueType::BigInt) {
|
||||
let left_bigint = c.get_side_free_bigint_value(left_expr);
|
||||
let right_bigint = c.get_side_free_bigint_value(right_expr);
|
||||
if let (Some(l_big), Some(r_big)) = (left_bigint, right_bigint) {
|
||||
return Some(l_big.eq(&r_big));
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(left, ValueType::String | ValueType::Number | ValueType::BigInt)
|
||||
&& matches!(right, ValueType::Object)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if matches!(left, ValueType::Object)
|
||||
&& matches!(right, ValueType::String | ValueType::Number | ValueType::BigInt)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(false);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// <https://tc39.es/ecma262/#sec-strict-equality-comparison>
|
||||
#[expect(clippy::float_cmp)]
|
||||
pub(super) fn strict_equality_comparison<'a>(
|
||||
c: &impl ConstantEvaluation<'a>,
|
||||
left_expr: &Expression<'a>,
|
||||
right_expr: &Expression<'a>,
|
||||
) -> Option<bool> {
|
||||
let left = ValueType::from(left_expr);
|
||||
let right = ValueType::from(right_expr);
|
||||
if !left.is_undetermined() && !right.is_undetermined() {
|
||||
// Strict equality can only be true for values of the same type.
|
||||
if left != right {
|
||||
return Some(false);
|
||||
}
|
||||
return match left {
|
||||
ValueType::Number => {
|
||||
let lnum = c.get_side_free_number_value(left_expr)?;
|
||||
let rnum = c.get_side_free_number_value(right_expr)?;
|
||||
if lnum.is_nan() || rnum.is_nan() {
|
||||
return Some(false);
|
||||
}
|
||||
Some(lnum == rnum)
|
||||
}
|
||||
ValueType::String => {
|
||||
let left = c.get_side_free_string_value(left_expr)?;
|
||||
let right = c.get_side_free_string_value(right_expr)?;
|
||||
Some(left == right)
|
||||
}
|
||||
ValueType::Undefined | ValueType::Null => Some(true),
|
||||
ValueType::Boolean => {
|
||||
let left = c.get_boolean_value(left_expr)?;
|
||||
let right = c.get_boolean_value(right_expr)?;
|
||||
Some(left == right)
|
||||
}
|
||||
ValueType::BigInt => {
|
||||
let left = c.get_side_free_bigint_value(left_expr)?;
|
||||
let right = c.get_side_free_bigint_value(right_expr)?;
|
||||
Some(left == right)
|
||||
}
|
||||
ValueType::Object => None,
|
||||
ValueType::Undetermined => unreachable!(),
|
||||
};
|
||||
}
|
||||
|
||||
// Then, try to evaluate based on the value of the expression.
|
||||
// There's only one special case:
|
||||
// Any strict equality comparison against NaN returns false.
|
||||
if left_expr.is_nan() || right_expr.is_nan() {
|
||||
return Some(false);
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -3,10 +3,12 @@ use std::{borrow::Cow, cmp::Ordering};
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::{ToPrimitive, Zero};
|
||||
|
||||
use oxc_ast::ast::*;
|
||||
use equality_comparison::{abstract_equality_comparison, strict_equality_comparison};
|
||||
use oxc_ast::{ast::*, AstBuilder};
|
||||
|
||||
use crate::{side_effects::MayHaveSideEffects, ToBigInt, ToBoolean, ToInt32, ToJsString, ToNumber};
|
||||
|
||||
mod equality_comparison;
|
||||
mod is_literal_value;
|
||||
mod value;
|
||||
mod value_type;
|
||||
@@ -15,6 +17,8 @@ pub use value::ConstantValue;
|
||||
pub use value_type::ValueType;
|
||||
|
||||
pub trait ConstantEvaluation<'a>: MayHaveSideEffects {
|
||||
fn ast(&self) -> AstBuilder<'a>;
|
||||
|
||||
fn resolve_binding(&self, ident: &IdentifierReference<'a>) -> Option<ConstantValue<'a>> {
|
||||
match ident.name.as_str() {
|
||||
"undefined" if self.is_global_reference(ident) => Some(ConstantValue::Undefined),
|
||||
@@ -374,7 +378,31 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects {
|
||||
}
|
||||
None
|
||||
}
|
||||
_ => None,
|
||||
BinaryOperator::StrictEquality
|
||||
| BinaryOperator::StrictInequality
|
||||
| BinaryOperator::Equality
|
||||
| BinaryOperator::Inequality => {
|
||||
if self.expression_may_have_side_effects(left)
|
||||
|| self.expression_may_have_side_effects(right)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let value = match operator {
|
||||
BinaryOperator::StrictEquality | BinaryOperator::StrictInequality => {
|
||||
strict_equality_comparison(self, left, right)?
|
||||
}
|
||||
BinaryOperator::Equality | BinaryOperator::Inequality => {
|
||||
abstract_equality_comparison(self, left, right)?
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Some(ConstantValue::Boolean(match operator {
|
||||
BinaryOperator::StrictEquality | BinaryOperator::Equality => value,
|
||||
BinaryOperator::StrictInequality | BinaryOperator::Inequality => !value,
|
||||
_ => unreachable!(),
|
||||
}))
|
||||
}
|
||||
BinaryOperator::In => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::ops::Deref;
|
||||
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ast::{ast::*, AstBuilder};
|
||||
use oxc_ecmascript::{
|
||||
constant_evaluation::{ConstantEvaluation, ConstantValue},
|
||||
side_effects::MayHaveSideEffects,
|
||||
@@ -19,7 +19,11 @@ impl<'a, 'b> Deref for Ctx<'a, 'b> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ConstantEvaluation<'a> for Ctx<'a, '_> {}
|
||||
impl<'a> ConstantEvaluation<'a> for Ctx<'a, '_> {
|
||||
fn ast(&self) -> AstBuilder<'a> {
|
||||
self.ast
|
||||
}
|
||||
}
|
||||
|
||||
impl MayHaveSideEffects for Ctx<'_, '_> {
|
||||
fn is_global_reference(&self, ident: &IdentifierReference<'_>) -> bool {
|
||||
|
||||
@@ -4,10 +4,7 @@ use oxc_ecmascript::{
|
||||
side_effects::MayHaveSideEffects,
|
||||
};
|
||||
use oxc_span::GetSpan;
|
||||
use oxc_syntax::{
|
||||
number::NumberBase,
|
||||
operator::{BinaryOperator, LogicalOperator},
|
||||
};
|
||||
use oxc_syntax::operator::{BinaryOperator, LogicalOperator};
|
||||
use oxc_traverse::Ancestor;
|
||||
|
||||
use crate::ctx::Ctx;
|
||||
@@ -229,7 +226,9 @@ impl<'a> PeepholeOptimizations {
|
||||
| BinaryOperator::LessThan
|
||||
| BinaryOperator::GreaterThan
|
||||
| BinaryOperator::LessEqualThan
|
||||
| BinaryOperator::GreaterEqualThan => Self::try_fold_comparison(e, ctx),
|
||||
| BinaryOperator::GreaterEqualThan
|
||||
| BinaryOperator::ShiftRight
|
||||
| BinaryOperator::Instanceof => ctx.eval_binary(e),
|
||||
BinaryOperator::BitwiseAnd | BinaryOperator::BitwiseOR | BinaryOperator::BitwiseXOR => {
|
||||
ctx.eval_binary(e).or_else(|| Self::try_fold_left_child_op(e, ctx))
|
||||
}
|
||||
@@ -288,7 +287,6 @@ impl<'a> PeepholeOptimizations {
|
||||
}
|
||||
None
|
||||
}
|
||||
BinaryOperator::ShiftRight | BinaryOperator::Instanceof => ctx.eval_binary(e),
|
||||
BinaryOperator::In => None,
|
||||
}
|
||||
}
|
||||
@@ -373,184 +371,6 @@ impl<'a> PeepholeOptimizations {
|
||||
))
|
||||
}
|
||||
|
||||
fn try_fold_comparison(e: &BinaryExpression<'a>, ctx: Ctx<'a, '_>) -> Option<Expression<'a>> {
|
||||
let left = &e.left;
|
||||
let right = &e.right;
|
||||
let op = e.operator;
|
||||
if ctx.expression_may_have_side_effects(left) || ctx.expression_may_have_side_effects(right)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let value = match op {
|
||||
BinaryOperator::LessThan
|
||||
| BinaryOperator::GreaterThan
|
||||
| BinaryOperator::LessEqualThan
|
||||
| BinaryOperator::GreaterEqualThan => {
|
||||
return ctx.eval_binary_expression(e).map(|v| ctx.value_to_expr(e.span, v));
|
||||
}
|
||||
BinaryOperator::Equality => Self::try_abstract_equality_comparison(left, right, ctx),
|
||||
BinaryOperator::Inequality => {
|
||||
Self::try_abstract_equality_comparison(left, right, ctx).map(|b| !b)
|
||||
}
|
||||
BinaryOperator::StrictEquality => {
|
||||
Self::try_strict_equality_comparison(left, right, ctx)
|
||||
}
|
||||
BinaryOperator::StrictInequality => {
|
||||
Self::try_strict_equality_comparison(left, right, ctx).map(|b| !b)
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let value = match value {
|
||||
Some(true) => true,
|
||||
Some(false) => false,
|
||||
None => return None,
|
||||
};
|
||||
Some(ctx.ast.expression_boolean_literal(e.span, value))
|
||||
}
|
||||
|
||||
/// <https://tc39.es/ecma262/#sec-abstract-equality-comparison>
|
||||
fn try_abstract_equality_comparison(
|
||||
left_expr: &Expression<'a>,
|
||||
right_expr: &Expression<'a>,
|
||||
ctx: Ctx<'a, '_>,
|
||||
) -> Option<bool> {
|
||||
let left = ValueType::from(left_expr);
|
||||
let right = ValueType::from(right_expr);
|
||||
if left != ValueType::Undetermined && right != ValueType::Undetermined {
|
||||
if left == right {
|
||||
return Self::try_strict_equality_comparison(left_expr, right_expr, ctx);
|
||||
}
|
||||
if matches!(
|
||||
(left, right),
|
||||
(ValueType::Null, ValueType::Undefined) | (ValueType::Undefined, ValueType::Null)
|
||||
) {
|
||||
return Some(true);
|
||||
}
|
||||
|
||||
if matches!((left, right), (ValueType::Number, ValueType::String))
|
||||
|| matches!(right, ValueType::Boolean)
|
||||
{
|
||||
let right_number = ctx.get_side_free_number_value(right_expr);
|
||||
|
||||
if let Some(num) = right_number {
|
||||
let number_literal_expr = ctx.ast.expression_numeric_literal(
|
||||
right_expr.span(),
|
||||
num,
|
||||
None,
|
||||
if num.fract() == 0.0 { NumberBase::Decimal } else { NumberBase::Float },
|
||||
);
|
||||
|
||||
return Self::try_abstract_equality_comparison(
|
||||
left_expr,
|
||||
&number_literal_expr,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
if matches!((left, right), (ValueType::String, ValueType::Number))
|
||||
|| matches!(left, ValueType::Boolean)
|
||||
{
|
||||
let left_number = ctx.get_side_free_number_value(left_expr);
|
||||
|
||||
if let Some(num) = left_number {
|
||||
let number_literal_expr = ctx.ast.expression_numeric_literal(
|
||||
left_expr.span(),
|
||||
num,
|
||||
None,
|
||||
if num.fract() == 0.0 { NumberBase::Decimal } else { NumberBase::Float },
|
||||
);
|
||||
|
||||
return Self::try_abstract_equality_comparison(
|
||||
&number_literal_expr,
|
||||
right_expr,
|
||||
ctx,
|
||||
);
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
if matches!(left, ValueType::BigInt) || matches!(right, ValueType::BigInt) {
|
||||
let left_bigint = ctx.get_side_free_bigint_value(left_expr);
|
||||
let right_bigint = ctx.get_side_free_bigint_value(right_expr);
|
||||
|
||||
if let (Some(l_big), Some(r_big)) = (left_bigint, right_bigint) {
|
||||
return Some(l_big.eq(&r_big));
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(left, ValueType::String | ValueType::Number | ValueType::BigInt)
|
||||
&& matches!(right, ValueType::Object)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
if matches!(left, ValueType::Object)
|
||||
&& matches!(right, ValueType::String | ValueType::Number | ValueType::BigInt)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
return Some(false);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// <https://tc39.es/ecma262/#sec-strict-equality-comparison>
|
||||
#[expect(clippy::float_cmp)]
|
||||
fn try_strict_equality_comparison(
|
||||
left_expr: &Expression<'a>,
|
||||
right_expr: &Expression<'a>,
|
||||
ctx: Ctx<'a, '_>,
|
||||
) -> Option<bool> {
|
||||
let left = ValueType::from(left_expr);
|
||||
let right = ValueType::from(right_expr);
|
||||
if !left.is_undetermined() && !right.is_undetermined() {
|
||||
// Strict equality can only be true for values of the same type.
|
||||
if left != right {
|
||||
return Some(false);
|
||||
}
|
||||
return match left {
|
||||
ValueType::Number => {
|
||||
let lnum = ctx.get_side_free_number_value(left_expr)?;
|
||||
let rnum = ctx.get_side_free_number_value(right_expr)?;
|
||||
if lnum.is_nan() || rnum.is_nan() {
|
||||
return Some(false);
|
||||
}
|
||||
Some(lnum == rnum)
|
||||
}
|
||||
ValueType::String => {
|
||||
let left = ctx.get_side_free_string_value(left_expr)?;
|
||||
let right = ctx.get_side_free_string_value(right_expr)?;
|
||||
Some(left == right)
|
||||
}
|
||||
ValueType::Undefined | ValueType::Null => Some(true),
|
||||
ValueType::Boolean if right.is_boolean() => {
|
||||
let left = ctx.get_boolean_value(left_expr)?;
|
||||
let right = ctx.get_boolean_value(right_expr)?;
|
||||
Some(left == right)
|
||||
}
|
||||
ValueType::BigInt => {
|
||||
let left = ctx.get_side_free_bigint_value(left_expr)?;
|
||||
let right = ctx.get_side_free_bigint_value(right_expr)?;
|
||||
Some(left == right)
|
||||
}
|
||||
ValueType::Object | ValueType::Boolean | ValueType::Undetermined => None,
|
||||
};
|
||||
}
|
||||
|
||||
// Then, try to evaluate based on the value of the expression.
|
||||
// There's only one special case:
|
||||
// Any strict equality comparison against NaN returns false.
|
||||
if left_expr.is_nan() || right_expr.is_nan() {
|
||||
return Some(false);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn try_fold_number_constructor(
|
||||
e: &CallExpression<'a>,
|
||||
ctx: Ctx<'a, '_>,
|
||||
|
||||
Reference in new issue
Block a user