Skip to content

Commit 5e3a0b2

Browse files
committed
refactor(minifier): move equality comparison to ConstantEvaluation
1 parent 2d3dd93 commit 5e3a0b2

4 files changed

Lines changed: 170 additions & 188 deletions

File tree

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
use oxc_ast::ast::{Expression, NumberBase};
2+
3+
use super::{ConstantEvaluation, ValueType};
4+
5+
/// <https://tc39.es/ecma262/#sec-abstract-equality-comparison>
6+
pub(super) fn abstract_equality_comparison<'a>(
7+
c: &impl ConstantEvaluation<'a>,
8+
left_expr: &Expression<'a>,
9+
right_expr: &Expression<'a>,
10+
) -> Option<bool> {
11+
let left = ValueType::from(left_expr);
12+
let right = ValueType::from(right_expr);
13+
if left != ValueType::Undetermined && right != ValueType::Undetermined {
14+
if left == right {
15+
return strict_equality_comparison(c, left_expr, right_expr);
16+
}
17+
if matches!(
18+
(left, right),
19+
(ValueType::Null, ValueType::Undefined) | (ValueType::Undefined, ValueType::Null)
20+
) {
21+
return Some(true);
22+
}
23+
24+
if matches!((left, right), (ValueType::Number, ValueType::String))
25+
|| matches!(right, ValueType::Boolean)
26+
{
27+
if let Some(num) = c.get_side_free_number_value(right_expr) {
28+
let number_literal_expr = c.ast().expression_numeric_literal(
29+
oxc_span::SPAN,
30+
num,
31+
None,
32+
if num.fract() == 0.0 { NumberBase::Decimal } else { NumberBase::Float },
33+
);
34+
return abstract_equality_comparison(c, left_expr, &number_literal_expr);
35+
}
36+
return None;
37+
}
38+
39+
if matches!((left, right), (ValueType::String, ValueType::Number))
40+
|| matches!(left, ValueType::Boolean)
41+
{
42+
if let Some(num) = c.get_side_free_number_value(left_expr) {
43+
let number_literal_expr = c.ast().expression_numeric_literal(
44+
oxc_span::SPAN,
45+
num,
46+
None,
47+
if num.fract() == 0.0 { NumberBase::Decimal } else { NumberBase::Float },
48+
);
49+
return abstract_equality_comparison(c, &number_literal_expr, right_expr);
50+
}
51+
return None;
52+
}
53+
54+
if matches!(left, ValueType::BigInt) || matches!(right, ValueType::BigInt) {
55+
let left_bigint = c.get_side_free_bigint_value(left_expr);
56+
let right_bigint = c.get_side_free_bigint_value(right_expr);
57+
if let (Some(l_big), Some(r_big)) = (left_bigint, right_bigint) {
58+
return Some(l_big.eq(&r_big));
59+
}
60+
}
61+
62+
if matches!(left, ValueType::String | ValueType::Number | ValueType::BigInt)
63+
&& matches!(right, ValueType::Object)
64+
{
65+
return None;
66+
}
67+
68+
if matches!(left, ValueType::Object)
69+
&& matches!(right, ValueType::String | ValueType::Number | ValueType::BigInt)
70+
{
71+
return None;
72+
}
73+
74+
return Some(false);
75+
}
76+
None
77+
}
78+
79+
/// <https://tc39.es/ecma262/#sec-strict-equality-comparison>
80+
#[expect(clippy::float_cmp)]
81+
pub(super) fn strict_equality_comparison<'a>(
82+
c: &impl ConstantEvaluation<'a>,
83+
left_expr: &Expression<'a>,
84+
right_expr: &Expression<'a>,
85+
) -> Option<bool> {
86+
let left = ValueType::from(left_expr);
87+
let right = ValueType::from(right_expr);
88+
if !left.is_undetermined() && !right.is_undetermined() {
89+
// Strict equality can only be true for values of the same type.
90+
if left != right {
91+
return Some(false);
92+
}
93+
return match left {
94+
ValueType::Number => {
95+
let lnum = c.get_side_free_number_value(left_expr)?;
96+
let rnum = c.get_side_free_number_value(right_expr)?;
97+
if lnum.is_nan() || rnum.is_nan() {
98+
return Some(false);
99+
}
100+
Some(lnum == rnum)
101+
}
102+
ValueType::String => {
103+
let left = c.get_side_free_string_value(left_expr)?;
104+
let right = c.get_side_free_string_value(right_expr)?;
105+
Some(left == right)
106+
}
107+
ValueType::Undefined | ValueType::Null => Some(true),
108+
ValueType::Boolean => {
109+
let left = c.get_boolean_value(left_expr)?;
110+
let right = c.get_boolean_value(right_expr)?;
111+
Some(left == right)
112+
}
113+
ValueType::BigInt => {
114+
let left = c.get_side_free_bigint_value(left_expr)?;
115+
let right = c.get_side_free_bigint_value(right_expr)?;
116+
Some(left == right)
117+
}
118+
ValueType::Object => None,
119+
ValueType::Undetermined => unreachable!(),
120+
};
121+
}
122+
123+
// Then, try to evaluate based on the value of the expression.
124+
// There's only one special case:
125+
// Any strict equality comparison against NaN returns false.
126+
if left_expr.is_nan() || right_expr.is_nan() {
127+
return Some(false);
128+
}
129+
None
130+
}

crates/oxc_ecmascript/src/constant_evaluation/mod.rs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ use std::{borrow::Cow, cmp::Ordering};
33
use num_bigint::BigInt;
44
use num_traits::{ToPrimitive, Zero};
55

6-
use oxc_ast::ast::*;
6+
use equality_comparison::{abstract_equality_comparison, strict_equality_comparison};
7+
use oxc_ast::{ast::*, AstBuilder};
78

89
use crate::{side_effects::MayHaveSideEffects, ToBigInt, ToBoolean, ToInt32, ToJsString, ToNumber};
910

11+
mod equality_comparison;
1012
mod is_literal_value;
1113
mod value;
1214
mod value_type;
@@ -15,6 +17,8 @@ pub use value::ConstantValue;
1517
pub use value_type::ValueType;
1618

1719
pub trait ConstantEvaluation<'a>: MayHaveSideEffects {
20+
fn ast(&self) -> AstBuilder<'a>;
21+
1822
fn resolve_binding(&self, ident: &IdentifierReference<'a>) -> Option<ConstantValue<'a>> {
1923
match ident.name.as_str() {
2024
"undefined" if self.is_global_reference(ident) => Some(ConstantValue::Undefined),
@@ -374,7 +378,31 @@ pub trait ConstantEvaluation<'a>: MayHaveSideEffects {
374378
}
375379
None
376380
}
377-
_ => None,
381+
BinaryOperator::StrictEquality
382+
| BinaryOperator::StrictInequality
383+
| BinaryOperator::Equality
384+
| BinaryOperator::Inequality => {
385+
if self.expression_may_have_side_effects(left)
386+
|| self.expression_may_have_side_effects(right)
387+
{
388+
return None;
389+
}
390+
let value = match operator {
391+
BinaryOperator::StrictEquality | BinaryOperator::StrictInequality => {
392+
strict_equality_comparison(self, left, right)?
393+
}
394+
BinaryOperator::Equality | BinaryOperator::Inequality => {
395+
abstract_equality_comparison(self, left, right)?
396+
}
397+
_ => unreachable!(),
398+
};
399+
Some(ConstantValue::Boolean(match operator {
400+
BinaryOperator::StrictEquality | BinaryOperator::Equality => value,
401+
BinaryOperator::StrictInequality | BinaryOperator::Inequality => !value,
402+
_ => unreachable!(),
403+
}))
404+
}
405+
BinaryOperator::In => None,
378406
}
379407
}
380408

crates/oxc_minifier/src/ctx.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::ops::Deref;
22

3-
use oxc_ast::ast::*;
3+
use oxc_ast::{ast::*, AstBuilder};
44
use oxc_ecmascript::{
55
constant_evaluation::{ConstantEvaluation, ConstantValue},
66
side_effects::MayHaveSideEffects,
@@ -19,7 +19,11 @@ impl<'a, 'b> Deref for Ctx<'a, 'b> {
1919
}
2020
}
2121

22-
impl<'a> ConstantEvaluation<'a> for Ctx<'a, '_> {}
22+
impl<'a> ConstantEvaluation<'a> for Ctx<'a, '_> {
23+
fn ast(&self) -> AstBuilder<'a> {
24+
self.ast
25+
}
26+
}
2327

2428
impl MayHaveSideEffects for Ctx<'_, '_> {
2529
fn is_global_reference(&self, ident: &IdentifierReference<'_>) -> bool {

0 commit comments

Comments
 (0)