mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +00:00
feat(ecmascript): add abstract_relational_comparison to dce (#6846)
I removed bigint comparisons because they were incorrect
This commit is contained in:
parent
3b99fe6d59
commit
fd57e00108
10 changed files with 178 additions and 304 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1723,7 +1723,6 @@ version = "0.33.0"
|
|||
dependencies = [
|
||||
"cow-utils",
|
||||
"insta",
|
||||
"num-bigint",
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
"oxc_codegen",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
mod is_litral_value;
|
||||
mod r#type;
|
||||
mod value;
|
||||
mod value_type;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::{borrow::Cow, cmp::Ordering};
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use num_traits::{One, Zero};
|
||||
|
|
@ -10,9 +10,9 @@ use num_traits::{One, Zero};
|
|||
#[allow(clippy::wildcard_imports)]
|
||||
use oxc_ast::ast::*;
|
||||
|
||||
use crate::{side_effects::MayHaveSideEffects, ToInt32, ToJsString};
|
||||
use crate::{side_effects::MayHaveSideEffects, ToBigInt, ToInt32, ToJsString};
|
||||
|
||||
pub use self::{is_litral_value::IsLiteralValue, r#type::ValueType, value::ConstantValue};
|
||||
pub use self::{is_litral_value::IsLiteralValue, value::ConstantValue, value_type::ValueType};
|
||||
|
||||
pub trait ConstantEvaluation<'a> {
|
||||
fn is_global_reference(&self, ident: &IdentifierReference<'a>) -> bool {
|
||||
|
|
@ -43,7 +43,24 @@ pub trait ConstantEvaluation<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn eval_to_boolean(&self, expr: &Expression<'a>) -> Option<bool> {
|
||||
fn get_side_free_string_value(&self, expr: &Expression<'a>) -> Option<Cow<'a, str>> {
|
||||
let value = expr.to_js_string();
|
||||
if value.is_some() && !expr.may_have_side_effects() {
|
||||
return value;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn get_side_free_bigint_value(&self, expr: &Expression<'a>) -> Option<BigInt> {
|
||||
let value = expr.to_big_int();
|
||||
if value.is_some() && expr.may_have_side_effects() {
|
||||
None
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fn get_boolean_value(&self, expr: &Expression<'a>) -> Option<bool> {
|
||||
match expr {
|
||||
Expression::Identifier(ident) => match ident.name.as_str() {
|
||||
"undefined" | "NaN" if self.is_global_reference(ident) => Some(false),
|
||||
|
|
@ -56,8 +73,8 @@ pub trait ConstantEvaluation<'a> {
|
|||
// true && false -> false
|
||||
// a && true -> None
|
||||
LogicalOperator::And => {
|
||||
let left = self.eval_to_boolean(&logical_expr.left);
|
||||
let right = self.eval_to_boolean(&logical_expr.right);
|
||||
let left = self.get_boolean_value(&logical_expr.left);
|
||||
let right = self.get_boolean_value(&logical_expr.right);
|
||||
match (left, right) {
|
||||
(Some(true), Some(true)) => Some(true),
|
||||
(Some(false), _) | (_, Some(false)) => Some(false),
|
||||
|
|
@ -68,8 +85,8 @@ pub trait ConstantEvaluation<'a> {
|
|||
// false || false -> false
|
||||
// a || b -> None
|
||||
LogicalOperator::Or => {
|
||||
let left = self.eval_to_boolean(&logical_expr.left);
|
||||
let right = self.eval_to_boolean(&logical_expr.right);
|
||||
let left = self.get_boolean_value(&logical_expr.left);
|
||||
let right = self.get_boolean_value(&logical_expr.right);
|
||||
match (left, right) {
|
||||
(Some(true), _) | (_, Some(true)) => Some(true),
|
||||
(Some(false), Some(false)) => Some(false),
|
||||
|
|
@ -81,7 +98,7 @@ pub trait ConstantEvaluation<'a> {
|
|||
}
|
||||
Expression::SequenceExpression(sequence_expr) => {
|
||||
// For sequence expression, the value is the value of the RHS.
|
||||
sequence_expr.expressions.last().and_then(|e| self.eval_to_boolean(e))
|
||||
sequence_expr.expressions.last().and_then(|e| self.get_boolean_value(e))
|
||||
}
|
||||
Expression::UnaryExpression(unary_expr) => {
|
||||
match unary_expr.operator {
|
||||
|
|
@ -95,7 +112,7 @@ pub trait ConstantEvaluation<'a> {
|
|||
}
|
||||
UnaryOperator::LogicalNot => {
|
||||
// !true -> false
|
||||
self.eval_to_boolean(&unary_expr.argument).map(|b| !b)
|
||||
self.get_boolean_value(&unary_expr.argument).map(|b| !b)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
|
|
@ -104,7 +121,7 @@ pub trait ConstantEvaluation<'a> {
|
|||
match assign_expr.operator {
|
||||
AssignmentOperator::LogicalAnd | AssignmentOperator::LogicalOr => None,
|
||||
// For ASSIGN, the value is the value of the RHS.
|
||||
_ => self.eval_to_boolean(&assign_expr.right),
|
||||
_ => self.get_boolean_value(&assign_expr.right),
|
||||
}
|
||||
}
|
||||
expr => {
|
||||
|
|
@ -127,7 +144,7 @@ pub trait ConstantEvaluation<'a> {
|
|||
self.eval_to_number(&unary_expr.argument).map(|v| -v)
|
||||
}
|
||||
UnaryOperator::LogicalNot => {
|
||||
self.eval_to_boolean(expr).map(|b| if b { 1_f64 } else { 0_f64 })
|
||||
self.get_boolean_value(expr).map(|b| if b { 1_f64 } else { 0_f64 })
|
||||
}
|
||||
UnaryOperator::Void => Some(f64::NAN),
|
||||
_ => None,
|
||||
|
|
@ -170,26 +187,26 @@ pub trait ConstantEvaluation<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
fn eval_binary_expression(&self, expr: &BinaryExpression<'a>) -> Option<ConstantValue<'a>> {
|
||||
match expr.operator {
|
||||
fn eval_binary_expression(&self, e: &BinaryExpression<'a>) -> Option<ConstantValue<'a>> {
|
||||
let left = &e.left;
|
||||
let right = &e.right;
|
||||
match e.operator {
|
||||
BinaryOperator::Addition => {
|
||||
let left = &expr.left;
|
||||
let right = &expr.right;
|
||||
if left.may_have_side_effects() || right.may_have_side_effects() {
|
||||
return None;
|
||||
}
|
||||
let left_type = ValueType::from(left);
|
||||
let right_type = ValueType::from(right);
|
||||
if left_type.is_string() || right_type.is_string() {
|
||||
let lval = self.eval_expression(&expr.left)?;
|
||||
let rval = self.eval_expression(&expr.right)?;
|
||||
let lval = self.eval_expression(left)?;
|
||||
let rval = self.eval_expression(right)?;
|
||||
let lstr = lval.to_js_string()?;
|
||||
let rstr = rval.to_js_string()?;
|
||||
return Some(ConstantValue::String(lstr + rstr));
|
||||
}
|
||||
if left_type.is_number() || right_type.is_number() {
|
||||
let lval = self.eval_expression(&expr.left)?;
|
||||
let rval = self.eval_expression(&expr.right)?;
|
||||
let lval = self.eval_expression(left)?;
|
||||
let rval = self.eval_expression(right)?;
|
||||
let lnum = lval.into_number()?;
|
||||
let rnum = rval.into_number()?;
|
||||
return Some(ConstantValue::Number(lnum + rnum));
|
||||
|
|
@ -201,9 +218,9 @@ pub trait ConstantEvaluation<'a> {
|
|||
| BinaryOperator::Remainder
|
||||
| BinaryOperator::Multiplication
|
||||
| BinaryOperator::Exponential => {
|
||||
let lval = self.eval_to_number(&expr.left)?;
|
||||
let rval = self.eval_to_number(&expr.right)?;
|
||||
let val = match expr.operator {
|
||||
let lval = self.eval_to_number(left)?;
|
||||
let rval = self.eval_to_number(right)?;
|
||||
let val = match e.operator {
|
||||
BinaryOperator::Subtraction => lval - rval,
|
||||
BinaryOperator::Division => {
|
||||
if rval.is_zero() {
|
||||
|
|
@ -235,8 +252,8 @@ pub trait ConstantEvaluation<'a> {
|
|||
BinaryOperator::ShiftLeft
|
||||
| BinaryOperator::ShiftRight
|
||||
| BinaryOperator::ShiftRightZeroFill => {
|
||||
let left_num = self.get_side_free_number_value(&expr.left);
|
||||
let right_num = self.get_side_free_number_value(&expr.right);
|
||||
let left_num = self.get_side_free_number_value(left);
|
||||
let right_num = self.get_side_free_number_value(right);
|
||||
if let (Some(left_val), Some(right_val)) = (left_num, right_num) {
|
||||
if left_val.fract() != 0.0 || right_val.fract() != 0.0 {
|
||||
return None;
|
||||
|
|
@ -249,7 +266,7 @@ pub trait ConstantEvaluation<'a> {
|
|||
let right_val_int = right_val as u32;
|
||||
let bits = left_val.to_int_32();
|
||||
|
||||
let result_val: f64 = match expr.operator {
|
||||
let result_val: f64 = match e.operator {
|
||||
BinaryOperator::ShiftLeft => f64::from(bits.wrapping_shl(right_val_int)),
|
||||
BinaryOperator::ShiftRight => f64::from(bits.wrapping_shr(right_val_int)),
|
||||
BinaryOperator::ShiftRightZeroFill => {
|
||||
|
|
@ -265,6 +282,36 @@ pub trait ConstantEvaluation<'a> {
|
|||
}
|
||||
None
|
||||
}
|
||||
BinaryOperator::LessThan => {
|
||||
return self.is_less_than(left, right, true).map(|value| match value {
|
||||
ConstantValue::Undefined => ConstantValue::Boolean(false),
|
||||
_ => value,
|
||||
})
|
||||
}
|
||||
BinaryOperator::GreaterThan => {
|
||||
return self.is_less_than(right, left, false).map(|value| match value {
|
||||
ConstantValue::Undefined => ConstantValue::Boolean(false),
|
||||
_ => value,
|
||||
})
|
||||
}
|
||||
BinaryOperator::LessEqualThan => {
|
||||
return self.is_less_than(right, left, false).map(|value| match value {
|
||||
ConstantValue::Boolean(true) | ConstantValue::Undefined => {
|
||||
ConstantValue::Boolean(false)
|
||||
}
|
||||
ConstantValue::Boolean(false) => ConstantValue::Boolean(true),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
BinaryOperator::GreaterEqualThan => {
|
||||
return self.is_less_than(left, right, true).map(|value| match value {
|
||||
ConstantValue::Boolean(true) | ConstantValue::Undefined => {
|
||||
ConstantValue::Boolean(false)
|
||||
}
|
||||
ConstantValue::Boolean(false) => ConstantValue::Boolean(true),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
@ -272,7 +319,7 @@ pub trait ConstantEvaluation<'a> {
|
|||
fn eval_logical_expression(&self, expr: &LogicalExpression<'a>) -> Option<ConstantValue<'a>> {
|
||||
match expr.operator {
|
||||
LogicalOperator::And => {
|
||||
if self.eval_to_boolean(&expr.left) == Some(true) {
|
||||
if self.get_boolean_value(&expr.left) == Some(true) {
|
||||
self.eval_expression(&expr.right)
|
||||
} else {
|
||||
self.eval_expression(&expr.left)
|
||||
|
|
@ -324,7 +371,7 @@ pub trait ConstantEvaluation<'a> {
|
|||
return None;
|
||||
}
|
||||
}
|
||||
self.eval_to_boolean(&expr.argument).map(|b| !b).map(ConstantValue::Boolean)
|
||||
self.get_boolean_value(&expr.argument).map(|b| !b).map(ConstantValue::Boolean)
|
||||
}
|
||||
UnaryOperator::UnaryPlus => {
|
||||
self.eval_to_number(&expr.argument).map(ConstantValue::Number)
|
||||
|
|
@ -360,4 +407,60 @@ pub trait ConstantEvaluation<'a> {
|
|||
UnaryOperator::Delete => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://tc39.es/ecma262/#sec-abstract-relational-comparison>
|
||||
fn is_less_than(
|
||||
&self,
|
||||
left_expr: &Expression<'a>,
|
||||
right_expr: &Expression<'a>,
|
||||
_left_first: bool,
|
||||
) -> Option<ConstantValue<'a>> {
|
||||
let left = ValueType::from(left_expr);
|
||||
let right = ValueType::from(right_expr);
|
||||
|
||||
if left.is_string() && right.is_string() {
|
||||
let left_string = self.get_side_free_string_value(left_expr);
|
||||
let right_string = self.get_side_free_string_value(right_expr);
|
||||
if let (Some(left_string), Some(right_string)) = (left_string, right_string) {
|
||||
// In JS, browsers parse \v differently. So do not compare strings if one contains \v.
|
||||
if left_string.contains('\u{000B}') || right_string.contains('\u{000B}') {
|
||||
return None;
|
||||
}
|
||||
return Some(ConstantValue::Boolean(
|
||||
left_string.cmp(&right_string) == Ordering::Less,
|
||||
));
|
||||
}
|
||||
|
||||
// Special case: `typeof a < typeof a` is always false.
|
||||
if let (Expression::UnaryExpression(left), Expression::UnaryExpression(right)) =
|
||||
(left_expr, right_expr)
|
||||
{
|
||||
if (left.operator, right.operator) == (UnaryOperator::Typeof, UnaryOperator::Typeof)
|
||||
{
|
||||
if let (Expression::Identifier(left), Expression::Identifier(right)) =
|
||||
(&left.argument, &right.argument)
|
||||
{
|
||||
if left.name == right.name {
|
||||
return Some(ConstantValue::Boolean(false));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: bigint is handled very differently in the spec
|
||||
// See <https://tc39.es/ecma262/#sec-islessthan>
|
||||
if left.is_bigint() || right.is_bigint() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let left_num = self.get_side_free_number_value(left_expr)?;
|
||||
let right_num = self.get_side_free_number_value(right_expr)?;
|
||||
|
||||
if left_num.is_nan() || right_num.is_nan() {
|
||||
return Some(ConstantValue::Undefined);
|
||||
}
|
||||
|
||||
return Some(ConstantValue::Boolean(left_num < right_num));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,10 @@ impl ValueType {
|
|||
pub fn is_number(self) -> bool {
|
||||
matches!(self, Self::Number)
|
||||
}
|
||||
|
||||
pub fn is_bigint(self) -> bool {
|
||||
matches!(self, Self::BigInt)
|
||||
}
|
||||
}
|
||||
|
||||
/// `get_known_value_type`
|
||||
|
|
@ -33,7 +33,6 @@ oxc_syntax = { workspace = true }
|
|||
oxc_traverse = { workspace = true }
|
||||
|
||||
cow-utils = { workspace = true }
|
||||
num-bigint = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
oxc_parser = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,24 +1,16 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::{
|
||||
constant_evaluation::{ConstantEvaluation, ValueType},
|
||||
side_effects::MayHaveSideEffects,
|
||||
};
|
||||
use oxc_span::{GetSpan, Span, SPAN};
|
||||
use oxc_span::{GetSpan, SPAN};
|
||||
use oxc_syntax::{
|
||||
number::{NumberBase, ToJsString},
|
||||
operator::{BinaryOperator, LogicalOperator, UnaryOperator},
|
||||
};
|
||||
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
|
||||
|
||||
use crate::{
|
||||
node_util::{is_exact_int64, Ctx},
|
||||
tri::Tri,
|
||||
CompressorPass,
|
||||
};
|
||||
use crate::{node_util::Ctx, tri::Tri, CompressorPass};
|
||||
|
||||
/// Constant Folding
|
||||
///
|
||||
|
|
@ -248,38 +240,25 @@ impl<'a, 'b> PeepholeFoldConstants {
|
|||
// return tryFoldLeftChildOp(subtree, left, right);
|
||||
None
|
||||
}
|
||||
op if op.is_equality() || op.is_compare() => {
|
||||
Self::try_fold_comparison(e.span, e.operator, &e.left, &e.right, ctx)
|
||||
}
|
||||
op if op.is_equality() || op.is_compare() => Self::try_fold_comparison(e, ctx),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_fold_comparison(
|
||||
span: Span,
|
||||
op: BinaryOperator,
|
||||
left: &'b Expression<'a>,
|
||||
right: &'b Expression<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let value = match Self::evaluate_comparison(op, left, right, ctx) {
|
||||
Tri::True => true,
|
||||
Tri::False => false,
|
||||
Tri::Unknown => return None,
|
||||
};
|
||||
Some(ctx.ast.expression_boolean_literal(span, value))
|
||||
}
|
||||
|
||||
fn evaluate_comparison(
|
||||
op: BinaryOperator,
|
||||
left: &'b Expression<'a>,
|
||||
right: &'b Expression<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Tri {
|
||||
fn try_fold_comparison(e: &BinaryExpression<'a>, ctx: Ctx<'a, 'b>) -> Option<Expression<'a>> {
|
||||
let left = &e.left;
|
||||
let right = &e.right;
|
||||
let op = e.operator;
|
||||
if left.may_have_side_effects() || right.may_have_side_effects() {
|
||||
return Tri::Unknown;
|
||||
return None;
|
||||
}
|
||||
match op {
|
||||
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).not()
|
||||
|
|
@ -290,20 +269,14 @@ impl<'a, 'b> PeepholeFoldConstants {
|
|||
BinaryOperator::StrictInequality => {
|
||||
Self::try_strict_equality_comparison(left, right, ctx).not()
|
||||
}
|
||||
BinaryOperator::LessThan => {
|
||||
Self::try_abstract_relational_comparison(left, right, false, ctx)
|
||||
}
|
||||
BinaryOperator::GreaterThan => {
|
||||
Self::try_abstract_relational_comparison(right, left, false, ctx)
|
||||
}
|
||||
BinaryOperator::LessEqualThan => {
|
||||
Self::try_abstract_relational_comparison(right, left, true, ctx).not()
|
||||
}
|
||||
BinaryOperator::GreaterEqualThan => {
|
||||
Self::try_abstract_relational_comparison(left, right, true, ctx).not()
|
||||
}
|
||||
_ => Tri::Unknown,
|
||||
}
|
||||
};
|
||||
let value = match value {
|
||||
Tri::True => true,
|
||||
Tri::False => false,
|
||||
Tri::Unknown => return None,
|
||||
};
|
||||
Some(ctx.ast.expression_boolean_literal(e.span, value))
|
||||
}
|
||||
|
||||
/// <https://tc39.es/ecma262/#sec-abstract-equality-comparison>
|
||||
|
|
@ -397,116 +370,6 @@ impl<'a, 'b> PeepholeFoldConstants {
|
|||
Tri::Unknown
|
||||
}
|
||||
|
||||
/// <https://tc39.es/ecma262/#sec-abstract-relational-comparison>
|
||||
fn try_abstract_relational_comparison(
|
||||
left_expr: &'b Expression<'a>,
|
||||
right_expr: &'b Expression<'a>,
|
||||
will_negative: bool,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Tri {
|
||||
let left = ValueType::from(left_expr);
|
||||
let right = ValueType::from(right_expr);
|
||||
|
||||
// First, check for a string comparison.
|
||||
if left == ValueType::String && right == ValueType::String {
|
||||
let left_string = ctx.get_side_free_string_value(left_expr);
|
||||
let right_string = ctx.get_side_free_string_value(right_expr);
|
||||
if let (Some(left_string), Some(right_string)) = (left_string, right_string) {
|
||||
// In JS, browsers parse \v differently. So do not compare strings if one contains \v.
|
||||
if left_string.contains('\u{000B}') || right_string.contains('\u{000B}') {
|
||||
return Tri::Unknown;
|
||||
}
|
||||
|
||||
return Tri::from(left_string.cmp(&right_string) == Ordering::Less);
|
||||
}
|
||||
|
||||
if let (Expression::UnaryExpression(left), Expression::UnaryExpression(right)) =
|
||||
(left_expr, right_expr)
|
||||
{
|
||||
if (left.operator, right.operator) == (UnaryOperator::Typeof, UnaryOperator::Typeof)
|
||||
{
|
||||
if let (Expression::Identifier(left), Expression::Identifier(right)) =
|
||||
(&left.argument, &right.argument)
|
||||
{
|
||||
if left.name == right.name {
|
||||
// Special case: `typeof a < typeof a` is always false.
|
||||
return Tri::False;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let left_bigint = ctx.get_side_free_bigint_value(left_expr);
|
||||
let right_bigint = ctx.get_side_free_bigint_value(right_expr);
|
||||
|
||||
let left_num = ctx.get_side_free_number_value(left_expr);
|
||||
let right_num = ctx.get_side_free_number_value(right_expr);
|
||||
|
||||
match (left_bigint, right_bigint, left_num, right_num) {
|
||||
// Next, try to evaluate based on the value of the node. Try comparing as BigInts first.
|
||||
(Some(l_big), Some(r_big), _, _) => {
|
||||
return Tri::from(l_big < r_big);
|
||||
}
|
||||
// try comparing as Numbers.
|
||||
(_, _, Some(l_num), Some(r_num)) => {
|
||||
return if l_num.is_nan() || r_num.is_nan() {
|
||||
Tri::from(will_negative)
|
||||
} else {
|
||||
Tri::from(l_num < r_num)
|
||||
}
|
||||
}
|
||||
// Finally, try comparisons between BigInt and Number.
|
||||
(Some(l_big), _, _, Some(r_num)) => {
|
||||
return Self::bigint_less_than_number(&l_big, r_num, Tri::False, will_negative);
|
||||
}
|
||||
(_, Some(r_big), Some(l_num), _) => {
|
||||
return Self::bigint_less_than_number(&r_big, l_num, Tri::True, will_negative);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
Tri::Unknown
|
||||
}
|
||||
|
||||
/// ported from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L1250)
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
pub fn bigint_less_than_number(
|
||||
bigint_value: &BigInt,
|
||||
number_value: f64,
|
||||
invert: Tri,
|
||||
will_negative: bool,
|
||||
) -> Tri {
|
||||
// if invert is false, then the number is on the right in tryAbstractRelationalComparison
|
||||
// if it's true, then the number is on the left
|
||||
match number_value {
|
||||
v if v.is_nan() => Tri::from(will_negative),
|
||||
v if v.is_infinite() && v.is_sign_positive() => Tri::True.xor(invert),
|
||||
v if v.is_infinite() && v.is_sign_negative() => Tri::False.xor(invert),
|
||||
num => {
|
||||
if let Some(Ordering::Equal | Ordering::Greater) =
|
||||
num.abs().partial_cmp(&2_f64.powi(53))
|
||||
{
|
||||
Tri::Unknown
|
||||
} else {
|
||||
let number_as_bigint = BigInt::from(num as i64);
|
||||
|
||||
match bigint_value.cmp(&number_as_bigint) {
|
||||
Ordering::Less => Tri::True.xor(invert),
|
||||
Ordering::Greater => Tri::False.xor(invert),
|
||||
Ordering::Equal => {
|
||||
if is_exact_int64(num) {
|
||||
Tri::False
|
||||
} else {
|
||||
Tri::from(num.is_sign_positive()).xor(invert)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://tc39.es/ecma262/#sec-strict-equality-comparison>
|
||||
#[expect(clippy::float_cmp)]
|
||||
fn try_strict_equality_comparison(
|
||||
|
|
@ -731,8 +594,8 @@ mod test {
|
|||
|
||||
test("null == 0", "false");
|
||||
test("null == 1", "false");
|
||||
test("null == 0n", "false");
|
||||
test("null == 1n", "false");
|
||||
// test("null == 0n", "false");
|
||||
// test("null == 1n", "false");
|
||||
test("null == 'hi'", "false");
|
||||
test("null == true", "false");
|
||||
test("null == false", "false");
|
||||
|
|
@ -772,16 +635,16 @@ mod test {
|
|||
test("0 < null", "false");
|
||||
test("0 > null", "false");
|
||||
test("0 >= null", "true");
|
||||
test("0n < null", "false");
|
||||
test("0n > null", "false");
|
||||
test("0n >= null", "true");
|
||||
// test("0n < null", "false");
|
||||
// test("0n > null", "false");
|
||||
// test("0n >= null", "true");
|
||||
test("true > null", "true");
|
||||
test("'hi' < null", "false");
|
||||
test("'hi' >= null", "false");
|
||||
test("null <= null", "true");
|
||||
|
||||
test("null < 0", "false");
|
||||
test("null < 0n", "false");
|
||||
// test("null < 0n", "false");
|
||||
test("null > true", "false");
|
||||
test("null < 'hi'", "false");
|
||||
test("null >= 'hi'", "false");
|
||||
|
|
@ -940,6 +803,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_bigint_number_comparison() {
|
||||
test("1n < 2", "true");
|
||||
test("1n > 2", "false");
|
||||
|
|
@ -975,11 +839,12 @@ mod test {
|
|||
test("1n > -Infinity", "true");
|
||||
|
||||
// null is interpreted as 0 when comparing with bigint
|
||||
test("1n < null", "false");
|
||||
test("1n > null", "true");
|
||||
// test("1n < null", "false");
|
||||
// test("1n > null", "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_bigint_string_comparison() {
|
||||
test("1n < '2'", "true");
|
||||
test("2n > '1'", "true");
|
||||
|
|
@ -992,6 +857,7 @@ mod test {
|
|||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_string_bigint_comparison() {
|
||||
test("'1' < 2n", "true");
|
||||
test("'2' > 1n", "true");
|
||||
|
|
@ -1009,10 +875,10 @@ mod test {
|
|||
test("NaN <= 1", "false");
|
||||
test("NaN > 1", "false");
|
||||
test("NaN >= 1", "false");
|
||||
test("NaN < 1n", "false");
|
||||
test("NaN <= 1n", "false");
|
||||
test("NaN > 1n", "false");
|
||||
test("NaN >= 1n", "false");
|
||||
// test("NaN < 1n", "false");
|
||||
// test("NaN <= 1n", "false");
|
||||
// test("NaN > 1n", "false");
|
||||
// test("NaN >= 1n", "false");
|
||||
|
||||
test("NaN < NaN", "false");
|
||||
test("NaN >= NaN", "false");
|
||||
|
|
|
|||
|
|
@ -400,7 +400,7 @@ impl<'a, 'b> PeepholeRemoveDeadCode {
|
|||
expr: &mut ConditionalExpression<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
match ctx.eval_to_boolean(&expr.test) {
|
||||
match ctx.get_boolean_value(&expr.test) {
|
||||
Some(true) => {
|
||||
// Bail `let o = { f() { assert.ok(this !== o); } }; (true ? o.f : false)(); (true ? o.f : false)``;`
|
||||
let parent = ctx.ancestry.parent();
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use oxc_allocator::Vec;
|
||||
use oxc_ast::{ast::*, NONE};
|
||||
use oxc_ecmascript::ToInt32;
|
||||
use oxc_ecmascript::{ToInt32, ToJsString};
|
||||
use oxc_semantic::IsGlobalReference;
|
||||
use oxc_span::{GetSpan, SPAN};
|
||||
use oxc_syntax::{
|
||||
|
|
@ -115,7 +115,7 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
|
|||
}
|
||||
}
|
||||
Expression::TemplateLiteral(_) => {
|
||||
if let Some(val) = ctx.get_string_value(expr) {
|
||||
if let Some(val) = expr.to_js_string() {
|
||||
let new_expr = ctx.ast.string_literal(expr.span(), val);
|
||||
*expr = ctx.ast.expression_from_string_literal(new_expr);
|
||||
self.changed = true;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
use std::borrow::Cow;
|
||||
use std::ops::Deref;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::{
|
||||
constant_evaluation::{ConstantEvaluation, ConstantValue},
|
||||
side_effects::MayHaveSideEffects,
|
||||
};
|
||||
use oxc_ecmascript::{ToBigInt, ToJsString};
|
||||
use oxc_ecmascript::constant_evaluation::{ConstantEvaluation, ConstantValue};
|
||||
use oxc_semantic::{IsGlobalReference, SymbolTable};
|
||||
use oxc_traverse::TraverseCtx;
|
||||
|
||||
|
|
@ -52,29 +46,6 @@ impl<'a, 'b> Ctx<'a, 'b> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Gets the boolean value of a node that represents an expression, or `None` if no
|
||||
/// such value can be determined by static analysis.
|
||||
/// This method does not consider whether the node may have side-effects.
|
||||
/// <https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/NodeUtil.java#L109>
|
||||
pub fn get_boolean_value(self, expr: &Expression<'a>) -> Option<bool> {
|
||||
self.eval_to_boolean(expr)
|
||||
}
|
||||
|
||||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/AbstractPeepholeOptimization.java#L104-L114)
|
||||
/// Returns the number value of the node if it has one and it cannot have side effects.
|
||||
pub fn get_side_free_number_value(self, expr: &Expression<'a>) -> Option<f64> {
|
||||
let value = self.eval_to_number(expr);
|
||||
// Calculating the number value, if any, is likely to be faster than calculating side effects,
|
||||
// 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() && expr.may_have_side_effects() {
|
||||
None
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_expression_undefined(self, expr: &Expression) -> bool {
|
||||
match expr {
|
||||
Expression::Identifier(ident) if self.is_identifier_undefined(ident) => true,
|
||||
|
|
@ -91,48 +62,4 @@ impl<'a, 'b> Ctx<'a, 'b> {
|
|||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/AbstractPeepholeOptimization.java#L121)
|
||||
pub fn get_side_free_bigint_value(self, expr: &Expression<'a>) -> Option<BigInt> {
|
||||
let value = self.get_bigint_value(expr);
|
||||
// Calculating the bigint value, if any, is likely to be faster than calculating side effects,
|
||||
// and there are only a very few cases where we can compute a bigint 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() && expr.may_have_side_effects() {
|
||||
None
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
/// Port from [closure-compiler](https://github.com/google/closure-compiler/blob/e13f5cd0a5d3d35f2db1e6c03fdf67ef02946009/src/com/google/javascript/jscomp/AbstractPeepholeOptimization.java#L139-L149)
|
||||
/// Gets the value of a node as a String, or `None` if it cannot be converted.
|
||||
/// This method effectively emulates the `String()` JavaScript cast function when
|
||||
/// possible and the node has no side effects. Otherwise, it returns `None`.
|
||||
pub fn get_side_free_string_value(self, expr: &'a Expression) -> Option<Cow<'a, str>> {
|
||||
let value = self.get_string_value(expr);
|
||||
// Calculating the string value, if any, is likely to be faster than calculating side effects,
|
||||
// and there are only a very few cases where we can compute a string value, but there could
|
||||
// also be side effects. e.g. `void doSomething()` has value 'undefined', regardless of the
|
||||
// behavior of `doSomething()`
|
||||
if value.is_some() && !expr.may_have_side_effects() {
|
||||
return value;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[expect(clippy::unused_self)]
|
||||
pub fn get_bigint_value(self, expr: &Expression<'a>) -> Option<BigInt> {
|
||||
expr.to_big_int()
|
||||
}
|
||||
|
||||
/// Port from [closure-compiler](https://github.com/google/closure-compiler/blob/e13f5cd0a5d3d35f2db1e6c03fdf67ef02946009/src/com/google/javascript/jscomp/NodeUtil.java#L234)
|
||||
/// Gets the value of a node as a String, or `None` if it cannot be converted. When it returns a
|
||||
/// String, this method effectively emulates the `String()` JavaScript cast function.
|
||||
/// This method does not consider whether `expr` may have side effects.
|
||||
#[expect(clippy::unused_self)]
|
||||
pub fn get_string_value(self, expr: &Expression<'a>) -> Option<Cow<'a, str>> {
|
||||
expr.to_js_string()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,16 +40,4 @@ impl Tri {
|
|||
Self::Unknown => Self::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn xor(self, other: Self) -> Self {
|
||||
Self::from(-self.value() * other.value())
|
||||
}
|
||||
|
||||
pub fn value(self) -> i8 {
|
||||
match self {
|
||||
Self::True => 1,
|
||||
Self::False => -1,
|
||||
Self::Unknown => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ commit: 06454619
|
|||
|
||||
runtime Summary:
|
||||
AST Parsed : 18444/18444 (100.00%)
|
||||
Positive Passed: 18264/18444 (99.02%)
|
||||
Positive Passed: 18268/18444 (99.05%)
|
||||
tasks/coverage/test262/test/annexB/language/function-code/if-decl-else-decl-a-func-existing-block-fn-no-init.js
|
||||
minify error: Test262Error: Expected SameValue(«function f(){}», «undefined») to be true
|
||||
|
||||
|
|
@ -381,18 +381,6 @@ minify error: Test262Error: The result of (0n == {valueOf: function() {return 0n
|
|||
tasks/coverage/test262/test/language/expressions/exponentiation/bigint-negative-exponent-throws.js
|
||||
transform error: Test262Error: (-1n) ** -1n throws RangeError Expected a RangeError but got a TypeError
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/greater-than/bigint-and-incomparable-string.js
|
||||
minify error: Test262Error: The result of (1n > "0.") is false Expected SameValue(«true», «false») to be true
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/greater-than-or-equal/bigint-and-incomparable-string.js
|
||||
minify error: Test262Error: The result of (1n >= "0.") is false Expected SameValue(«true», «false») to be true
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/less-than/bigint-and-incomparable-string.js
|
||||
minify error: Test262Error: The result of ("0." < 1n) is false Expected SameValue(«true», «false») to be true
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/less-than-or-equal/bigint-and-incomparable-string.js
|
||||
minify error: Test262Error: The result of ("0." <= 1n) is false Expected SameValue(«true», «false») to be true
|
||||
|
||||
tasks/coverage/test262/test/language/expressions/logical-and/S11.11.1_A3_T2.js
|
||||
minify error: Test262Error: #1.2: (-0 && -1) === -0
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue