feat(ecmascript): add abstract_relational_comparison to dce (#6846)

I removed bigint comparisons because they were incorrect
This commit is contained in:
Boshen 2024-10-24 06:46:07 +00:00
parent 3b99fe6d59
commit fd57e00108
10 changed files with 178 additions and 304 deletions

1
Cargo.lock generated
View file

@ -1723,7 +1723,6 @@ version = "0.33.0"
dependencies = [
"cow-utils",
"insta",
"num-bigint",
"oxc_allocator",
"oxc_ast",
"oxc_codegen",

View file

@ -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));
}
}

View file

@ -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`

View file

@ -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 }

View file

@ -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");

View file

@ -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();

View file

@ -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;

View file

@ -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()
}
}

View file

@ -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,
}
}
}

View file

@ -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