Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions crates/oxc_ecmascript/src/constant_evaluation/equality_comparison.rs
Original file line number Diff line number Diff line change
@@ -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
}
32 changes: 30 additions & 2 deletions crates/oxc_ecmascript/src/constant_evaluation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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),
Expand Down Expand Up @@ -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,
}
}

Expand Down
8 changes: 6 additions & 2 deletions crates/oxc_minifier/src/ctx.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 {
Expand Down
Loading
Loading