mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
feat(ecmascript): add ConstantEvaluation (#6549)
This commit is contained in:
parent
67ad08a056
commit
3556062213
11 changed files with 343 additions and 255 deletions
171
crates/oxc_ecmascript/src/constant_evaluation.rs
Normal file
171
crates/oxc_ecmascript/src/constant_evaluation.rs
Normal file
|
|
@ -0,0 +1,171 @@
|
||||||
|
use core::f64;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use num_traits::Zero;
|
||||||
|
#[allow(clippy::wildcard_imports)]
|
||||||
|
use oxc_ast::ast::*;
|
||||||
|
|
||||||
|
pub enum ConstantValue<'a> {
|
||||||
|
Number(f64),
|
||||||
|
String(Cow<'a, str>),
|
||||||
|
Identifier,
|
||||||
|
Undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
// impl<'a> ConstantValue<'a> {
|
||||||
|
// fn to_boolean(&self) -> Option<bool> {
|
||||||
|
// match self {
|
||||||
|
// Self::Number(n) => Some(!n.is_zero()),
|
||||||
|
// Self::String(s) => Some(!s.is_empty()),
|
||||||
|
// Self::Identifier => None,
|
||||||
|
// Self::Undefined => Some(false),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub trait ConstantEvaluation<'a> {
|
||||||
|
fn is_global_reference(&self, ident: &IdentifierReference<'a>) -> bool {
|
||||||
|
matches!(ident.name.as_str(), "undefined" | "NaN" | "Infinity")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_binding(&self, ident: &IdentifierReference<'a>) -> Option<ConstantValue> {
|
||||||
|
match ident.name.as_str() {
|
||||||
|
"undefined" if self.is_global_reference(ident) => Some(ConstantValue::Undefined),
|
||||||
|
"NaN" if self.is_global_reference(ident) => Some(ConstantValue::Number(f64::NAN)),
|
||||||
|
"Infinity" if self.is_global_reference(ident) => {
|
||||||
|
Some(ConstantValue::Number(f64::INFINITY))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_to_boolean(&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),
|
||||||
|
"Infinity" if self.is_global_reference(ident) => Some(true),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Expression::LogicalExpression(logical_expr) => {
|
||||||
|
match logical_expr.operator {
|
||||||
|
// true && true -> true
|
||||||
|
// 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);
|
||||||
|
match (left, right) {
|
||||||
|
(Some(true), Some(true)) => Some(true),
|
||||||
|
(Some(false), _) | (_, Some(false)) => Some(false),
|
||||||
|
(None, _) | (_, None) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// true || false -> true
|
||||||
|
// 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);
|
||||||
|
match (left, right) {
|
||||||
|
(Some(true), _) | (_, Some(true)) => Some(true),
|
||||||
|
(Some(false), Some(false)) => Some(false),
|
||||||
|
(None, _) | (_, None) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogicalOperator::Coalesce => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
Expression::UnaryExpression(unary_expr) => {
|
||||||
|
match unary_expr.operator {
|
||||||
|
UnaryOperator::Void => Some(false),
|
||||||
|
|
||||||
|
UnaryOperator::BitwiseNot
|
||||||
|
| UnaryOperator::UnaryPlus
|
||||||
|
| UnaryOperator::UnaryNegation => {
|
||||||
|
// `~0 -> true` `+1 -> true` `+0 -> false` `-0 -> false`
|
||||||
|
self.eval_to_number(expr).map(|value| !value.is_zero())
|
||||||
|
}
|
||||||
|
UnaryOperator::LogicalNot => {
|
||||||
|
// !true -> false
|
||||||
|
self.eval_to_boolean(&unary_expr.argument).map(|b| !b)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::AssignmentExpression(assign_expr) => {
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expr => {
|
||||||
|
use crate::ToBoolean;
|
||||||
|
expr.to_boolean()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_to_number(&self, expr: &Expression<'a>) -> Option<f64> {
|
||||||
|
match expr {
|
||||||
|
Expression::Identifier(ident) => match ident.name.as_str() {
|
||||||
|
"undefined" | "NaN" if self.is_global_reference(ident) => Some(f64::NAN),
|
||||||
|
"Infinity" if self.is_global_reference(ident) => Some(f64::INFINITY),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
|
||||||
|
UnaryOperator::UnaryPlus => self.eval_to_number(&unary_expr.argument),
|
||||||
|
UnaryOperator::UnaryNegation => {
|
||||||
|
self.eval_to_number(&unary_expr.argument).map(|v| -v)
|
||||||
|
}
|
||||||
|
// UnaryOperator::BitwiseNot => {
|
||||||
|
// unary_expr.argument.to_number().map(|value| {
|
||||||
|
// match value {
|
||||||
|
// NumberValue::Number(num) => NumberValue::Number(f64::from(
|
||||||
|
// !NumericLiteral::ecmascript_to_int32(num),
|
||||||
|
// )),
|
||||||
|
// // ~Infinity -> -1
|
||||||
|
// // ~-Infinity -> -1
|
||||||
|
// // ~NaN -> -1
|
||||||
|
// _ => NumberValue::Number(-1_f64),
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
UnaryOperator::LogicalNot => {
|
||||||
|
self.eval_to_boolean(expr).map(|b| if b { 1_f64 } else { 0_f64 })
|
||||||
|
}
|
||||||
|
UnaryOperator::Void => Some(f64::NAN),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
expr => {
|
||||||
|
use crate::ToNumber;
|
||||||
|
expr.to_number()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_expression(&self, expr: &Expression<'a>) -> Option<ConstantValue> {
|
||||||
|
match expr {
|
||||||
|
Expression::LogicalExpression(e) => self.eval_logical_expression(e),
|
||||||
|
Expression::Identifier(ident) => self.resolve_binding(ident),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_logical_expression(&self, expr: &LogicalExpression<'a>) -> Option<ConstantValue> {
|
||||||
|
match expr.operator {
|
||||||
|
LogicalOperator::And => {
|
||||||
|
if self.eval_to_boolean(&expr.left) == Some(true) {
|
||||||
|
self.eval_expression(&expr.right)
|
||||||
|
} else {
|
||||||
|
self.eval_expression(&expr.left)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -17,11 +17,22 @@ mod to_int_32;
|
||||||
mod to_number;
|
mod to_number;
|
||||||
mod to_string;
|
mod to_string;
|
||||||
|
|
||||||
|
// Constant Evaluation
|
||||||
|
mod constant_evaluation;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
bound_names::BoundNames, is_simple_parameter_list::IsSimpleParameterList,
|
bound_names::BoundNames,
|
||||||
private_bound_identifiers::PrivateBoundIdentifiers, prop_name::PropName,
|
constant_evaluation::{ConstantEvaluation, ConstantValue},
|
||||||
string_char_at::StringCharAt, string_index_of::StringIndexOf,
|
is_simple_parameter_list::IsSimpleParameterList,
|
||||||
string_last_index_of::StringLastIndexOf, string_to_big_int::StringToBigInt,
|
private_bound_identifiers::PrivateBoundIdentifiers,
|
||||||
to_big_int::ToBigInt, to_boolean::ToBoolean, to_int_32::ToInt32, to_number::ToNumber,
|
prop_name::PropName,
|
||||||
|
string_char_at::StringCharAt,
|
||||||
|
string_index_of::StringIndexOf,
|
||||||
|
string_last_index_of::StringLastIndexOf,
|
||||||
|
string_to_big_int::StringToBigInt,
|
||||||
|
to_big_int::ToBigInt,
|
||||||
|
to_boolean::ToBoolean,
|
||||||
|
to_int_32::ToInt32,
|
||||||
|
to_number::ToNumber,
|
||||||
to_string::ToJsString,
|
to_string::ToJsString,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,4 @@
|
||||||
use num_traits::Zero;
|
use oxc_ast::ast::Expression;
|
||||||
#[allow(clippy::wildcard_imports)]
|
|
||||||
use oxc_ast::ast::*;
|
|
||||||
|
|
||||||
use crate::ToNumber;
|
|
||||||
|
|
||||||
/// `ToBoolean`
|
/// `ToBoolean`
|
||||||
///
|
///
|
||||||
|
|
@ -13,7 +9,16 @@ pub trait ToBoolean<'a> {
|
||||||
|
|
||||||
impl<'a> ToBoolean<'a> for Expression<'a> {
|
impl<'a> ToBoolean<'a> for Expression<'a> {
|
||||||
fn to_boolean(&self) -> Option<bool> {
|
fn to_boolean(&self) -> Option<bool> {
|
||||||
|
// 1. If argument is a Boolean, return argument.
|
||||||
|
// 2. If argument is one of undefined, null, +0𝔽, -0𝔽, NaN, 0ℤ, or the empty String, return false.
|
||||||
|
// 3. NOTE: This step is replaced in section B.3.6.1.
|
||||||
|
// 4. Return true.
|
||||||
match self {
|
match self {
|
||||||
|
Expression::Identifier(ident) => match ident.name.as_str() {
|
||||||
|
"NaN" | "undefined" => Some(false),
|
||||||
|
"Infinity" => Some(true),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
Expression::RegExpLiteral(_)
|
Expression::RegExpLiteral(_)
|
||||||
| Expression::ArrayExpression(_)
|
| Expression::ArrayExpression(_)
|
||||||
| Expression::ArrowFunctionExpression(_)
|
| Expression::ArrowFunctionExpression(_)
|
||||||
|
|
@ -35,73 +40,6 @@ impl<'a> ToBoolean<'a> for Expression<'a> {
|
||||||
.and_then(|quasi| quasi.value.cooked.as_ref())
|
.and_then(|quasi| quasi.value.cooked.as_ref())
|
||||||
.map(|cooked| !cooked.is_empty())
|
.map(|cooked| !cooked.is_empty())
|
||||||
}
|
}
|
||||||
Expression::Identifier(ident) => match ident.name.as_str() {
|
|
||||||
"NaN" | "undefined" => Some(false),
|
|
||||||
"Infinity" => Some(true),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
Expression::AssignmentExpression(assign_expr) => {
|
|
||||||
match assign_expr.operator {
|
|
||||||
AssignmentOperator::LogicalAnd | AssignmentOperator::LogicalOr => None,
|
|
||||||
// For ASSIGN, the value is the value of the RHS.
|
|
||||||
_ => assign_expr.right.to_boolean(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Expression::LogicalExpression(logical_expr) => {
|
|
||||||
match logical_expr.operator {
|
|
||||||
// true && true -> true
|
|
||||||
// true && false -> false
|
|
||||||
// a && true -> None
|
|
||||||
LogicalOperator::And => {
|
|
||||||
let left = logical_expr.left.to_boolean();
|
|
||||||
let right = logical_expr.right.to_boolean();
|
|
||||||
match (left, right) {
|
|
||||||
(Some(true), Some(true)) => Some(true),
|
|
||||||
(Some(false), _) | (_, Some(false)) => Some(false),
|
|
||||||
(None, _) | (_, None) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// true || false -> true
|
|
||||||
// false || false -> false
|
|
||||||
// a || b -> None
|
|
||||||
LogicalOperator::Or => {
|
|
||||||
let left = logical_expr.left.to_boolean();
|
|
||||||
let right = logical_expr.right.to_boolean();
|
|
||||||
|
|
||||||
match (left, right) {
|
|
||||||
(Some(true), _) | (_, Some(true)) => Some(true),
|
|
||||||
(Some(false), Some(false)) => Some(false),
|
|
||||||
(None, _) | (_, None) => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
LogicalOperator::Coalesce => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Expression::SequenceExpression(sequence_expr) => {
|
|
||||||
// For sequence expression, the value is the value of the RHS.
|
|
||||||
sequence_expr.expressions.last().and_then(ToBoolean::to_boolean)
|
|
||||||
}
|
|
||||||
Expression::UnaryExpression(unary_expr) => {
|
|
||||||
if unary_expr.operator == UnaryOperator::Void {
|
|
||||||
Some(false)
|
|
||||||
} else if matches!(
|
|
||||||
unary_expr.operator,
|
|
||||||
UnaryOperator::BitwiseNot
|
|
||||||
| UnaryOperator::UnaryPlus
|
|
||||||
| UnaryOperator::UnaryNegation
|
|
||||||
) {
|
|
||||||
// ~0 -> true
|
|
||||||
// +1 -> true
|
|
||||||
// +0 -> false
|
|
||||||
// -0 -> false
|
|
||||||
self.to_number().map(|value| !value.is_zero())
|
|
||||||
} else if unary_expr.operator == UnaryOperator::LogicalNot {
|
|
||||||
// !true -> false
|
|
||||||
unary_expr.argument.to_boolean().map(|b| !b)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
#[allow(clippy::wildcard_imports)]
|
#[allow(clippy::wildcard_imports)]
|
||||||
use oxc_ast::ast::*;
|
use oxc_ast::ast::*;
|
||||||
use oxc_syntax::operator::UnaryOperator;
|
|
||||||
|
|
||||||
use crate::ToBoolean;
|
|
||||||
|
|
||||||
/// `ToNumber`
|
/// `ToNumber`
|
||||||
///
|
///
|
||||||
|
|
@ -15,28 +12,6 @@ impl<'a> ToNumber<'a> for Expression<'a> {
|
||||||
fn to_number(&self) -> Option<f64> {
|
fn to_number(&self) -> Option<f64> {
|
||||||
match self {
|
match self {
|
||||||
Expression::NumericLiteral(number_literal) => Some(number_literal.value),
|
Expression::NumericLiteral(number_literal) => Some(number_literal.value),
|
||||||
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
|
|
||||||
UnaryOperator::UnaryPlus => unary_expr.argument.to_number(),
|
|
||||||
UnaryOperator::UnaryNegation => unary_expr.argument.to_number().map(|v| -v),
|
|
||||||
// UnaryOperator::BitwiseNot => {
|
|
||||||
// unary_expr.argument.to_number().map(|value| {
|
|
||||||
// match value {
|
|
||||||
// NumberValue::Number(num) => NumberValue::Number(f64::from(
|
|
||||||
// !NumericLiteral::ecmascript_to_int32(num),
|
|
||||||
// )),
|
|
||||||
// // ~Infinity -> -1
|
|
||||||
// // ~-Infinity -> -1
|
|
||||||
// // ~NaN -> -1
|
|
||||||
// _ => NumberValue::Number(-1_f64),
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
UnaryOperator::LogicalNot => {
|
|
||||||
self.to_boolean().map(|tri| if tri { 1_f64 } else { 0_f64 })
|
|
||||||
}
|
|
||||||
UnaryOperator::Void => Some(f64::NAN),
|
|
||||||
_ => None,
|
|
||||||
},
|
|
||||||
Expression::BooleanLiteral(bool_literal) => {
|
Expression::BooleanLiteral(bool_literal) => {
|
||||||
if bool_literal.value {
|
if bool_literal.value {
|
||||||
Some(1.0)
|
Some(1.0)
|
||||||
|
|
@ -50,7 +25,7 @@ impl<'a> ToNumber<'a> for Expression<'a> {
|
||||||
"NaN" | "undefined" => Some(f64::NAN),
|
"NaN" | "undefined" => Some(f64::NAN),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
// TODO: will be implemented in next PR, just for test pass now.
|
// TODO: StringToNumber
|
||||||
Expression::StringLiteral(string_literal) => {
|
Expression::StringLiteral(string_literal) => {
|
||||||
string_literal.value.parse::<f64>().map_or(Some(f64::NAN), Some)
|
string_literal.value.parse::<f64>().map_or(Some(f64::NAN), Some)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,21 +19,8 @@ pub use remove_syntax::RemoveSyntax;
|
||||||
pub use statement_fusion::StatementFusion;
|
pub use statement_fusion::StatementFusion;
|
||||||
|
|
||||||
use oxc_ast::ast::Program;
|
use oxc_ast::ast::Program;
|
||||||
use oxc_semantic::{ScopeTree, SymbolTable};
|
|
||||||
use oxc_traverse::{Traverse, TraverseCtx};
|
use oxc_traverse::{Traverse, TraverseCtx};
|
||||||
|
|
||||||
use crate::node_util::NodeUtil;
|
|
||||||
|
|
||||||
impl<'a> NodeUtil<'a> for TraverseCtx<'a> {
|
|
||||||
fn symbols(&self) -> &SymbolTable {
|
|
||||||
self.scoping.symbols()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn scopes(&self) -> &ScopeTree {
|
|
||||||
self.scoping.scopes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait CompressorPass<'a>: Traverse<'a> {
|
pub trait CompressorPass<'a>: Traverse<'a> {
|
||||||
fn changed(&self) -> bool;
|
fn changed(&self) -> bool;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ use oxc_syntax::{
|
||||||
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
|
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
node_util::{is_exact_int64, IsLiteralValue, MayHaveSideEffects, NodeUtil},
|
node_util::{is_exact_int64, Ctx, IsLiteralValue, MayHaveSideEffects},
|
||||||
tri::Tri,
|
tri::Tri,
|
||||||
value_type::ValueType,
|
value_type::ValueType,
|
||||||
CompressorPass,
|
CompressorPass,
|
||||||
|
|
@ -43,6 +43,7 @@ impl<'a> CompressorPass<'a> for PeepholeFoldConstants {
|
||||||
|
|
||||||
impl<'a> Traverse<'a> for PeepholeFoldConstants {
|
impl<'a> Traverse<'a> for PeepholeFoldConstants {
|
||||||
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
|
let ctx = Ctx(ctx);
|
||||||
if let Some(folded_expr) = match expr {
|
if let Some(folded_expr) = match expr {
|
||||||
Expression::CallExpression(e) => {
|
Expression::CallExpression(e) => {
|
||||||
Self::try_fold_useless_object_dot_define_properties_call(e, ctx)
|
Self::try_fold_useless_object_dot_define_properties_call(e, ctx)
|
||||||
|
|
@ -66,21 +67,21 @@ impl<'a> Traverse<'a> for PeepholeFoldConstants {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PeepholeFoldConstants {
|
impl<'a, 'b> PeepholeFoldConstants {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { changed: false }
|
Self { changed: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_fold_useless_object_dot_define_properties_call(
|
fn try_fold_useless_object_dot_define_properties_call(
|
||||||
_call_expr: &mut CallExpression<'a>,
|
_call_expr: &mut CallExpression<'a>,
|
||||||
_ctx: &mut TraverseCtx<'a>,
|
_ctx: Ctx<'a, '_>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_fold_ctor_cal(
|
fn try_fold_ctor_cal(
|
||||||
_new_expr: &mut NewExpression<'a>,
|
_new_expr: &mut NewExpression<'a>,
|
||||||
_ctx: &mut TraverseCtx<'a>,
|
_ctx: Ctx<'a, '_>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +91,7 @@ impl<'a> PeepholeFoldConstants {
|
||||||
/// `typeof(6) --> "number"`
|
/// `typeof(6) --> "number"`
|
||||||
fn try_fold_type_of(
|
fn try_fold_type_of(
|
||||||
expr: &mut UnaryExpression<'a>,
|
expr: &mut UnaryExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, '_>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
if !expr.argument.is_literal_value(/* include_function */ true) {
|
if !expr.argument.is_literal_value(/* include_function */ true) {
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -115,21 +116,21 @@ impl<'a> PeepholeFoldConstants {
|
||||||
// fn try_fold_spread(
|
// fn try_fold_spread(
|
||||||
// &mut self,
|
// &mut self,
|
||||||
// _new_expr: &mut NewExpression<'a>,
|
// _new_expr: &mut NewExpression<'a>,
|
||||||
// _ctx: &mut TraverseCtx<'a>,
|
// _ctx: Ctx<'a,'b>,
|
||||||
// ) -> Option<Expression<'a>> {
|
// ) -> Option<Expression<'a>> {
|
||||||
// None
|
// None
|
||||||
// }
|
// }
|
||||||
|
|
||||||
fn try_flatten_array_expression(
|
fn try_flatten_array_expression(
|
||||||
_new_expr: &mut ArrayExpression<'a>,
|
_new_expr: &mut ArrayExpression<'a>,
|
||||||
_ctx: &mut TraverseCtx<'a>,
|
_ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_flatten_object_expression(
|
fn try_flatten_object_expression(
|
||||||
_new_expr: &mut ObjectExpression<'a>,
|
_new_expr: &mut ObjectExpression<'a>,
|
||||||
_ctx: &mut TraverseCtx<'a>,
|
_ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +138,7 @@ impl<'a> PeepholeFoldConstants {
|
||||||
fn try_fold_unary_expression(
|
fn try_fold_unary_expression(
|
||||||
&mut self,
|
&mut self,
|
||||||
expr: &mut UnaryExpression<'a>,
|
expr: &mut UnaryExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
fn is_valid(x: f64) -> bool {
|
fn is_valid(x: f64) -> bool {
|
||||||
x.is_finite() && x.fract() == 0.0
|
x.is_finite() && x.fract() == 0.0
|
||||||
|
|
@ -154,7 +155,6 @@ impl<'a> PeepholeFoldConstants {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ctx.get_boolean_value(&expr.argument)
|
ctx.get_boolean_value(&expr.argument)
|
||||||
.to_option()
|
|
||||||
.map(|b| ctx.ast.expression_boolean_literal(SPAN, !b))
|
.map(|b| ctx.ast.expression_boolean_literal(SPAN, !b))
|
||||||
}
|
}
|
||||||
// `-NaN` -> `NaN`
|
// `-NaN` -> `NaN`
|
||||||
|
|
@ -258,7 +258,7 @@ impl<'a> PeepholeFoldConstants {
|
||||||
fn try_reduce_void(
|
fn try_reduce_void(
|
||||||
&mut self,
|
&mut self,
|
||||||
expr: &mut UnaryExpression<'a>,
|
expr: &mut UnaryExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
if (!expr.argument.is_number() || !expr.argument.is_number_0())
|
if (!expr.argument.is_number() || !expr.argument.is_number_0())
|
||||||
&& !expr.may_have_side_effects()
|
&& !expr.may_have_side_effects()
|
||||||
|
|
@ -271,7 +271,7 @@ impl<'a> PeepholeFoldConstants {
|
||||||
|
|
||||||
fn try_fold_logical_expression(
|
fn try_fold_logical_expression(
|
||||||
logical_expr: &mut LogicalExpression<'a>,
|
logical_expr: &mut LogicalExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
match logical_expr.operator {
|
match logical_expr.operator {
|
||||||
LogicalOperator::And | LogicalOperator::Or => Self::try_fold_and_or(logical_expr, ctx),
|
LogicalOperator::And | LogicalOperator::Or => Self::try_fold_and_or(logical_expr, ctx),
|
||||||
|
|
@ -284,13 +284,13 @@ impl<'a> PeepholeFoldConstants {
|
||||||
/// port from [closure-compiler](https://github.com/google/closure-compiler/blob/09094b551915a6487a980a783831cba58b5739d1/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L587)
|
/// port from [closure-compiler](https://github.com/google/closure-compiler/blob/09094b551915a6487a980a783831cba58b5739d1/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L587)
|
||||||
pub fn try_fold_and_or(
|
pub fn try_fold_and_or(
|
||||||
logical_expr: &mut LogicalExpression<'a>,
|
logical_expr: &mut LogicalExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
let op = logical_expr.operator;
|
let op = logical_expr.operator;
|
||||||
debug_assert!(matches!(op, LogicalOperator::And | LogicalOperator::Or));
|
debug_assert!(matches!(op, LogicalOperator::And | LogicalOperator::Or));
|
||||||
|
|
||||||
let left = &logical_expr.left;
|
let left = &logical_expr.left;
|
||||||
let left_val = ctx.get_boolean_value(left).to_option();
|
let left_val = ctx.get_boolean_value(left);
|
||||||
|
|
||||||
if let Some(lval) = left_val {
|
if let Some(lval) = left_val {
|
||||||
// Bail `0 && (module.exports = {})` for `cjs-module-lexer`.
|
// Bail `0 && (module.exports = {})` for `cjs-module-lexer`.
|
||||||
|
|
@ -332,7 +332,7 @@ impl<'a> PeepholeFoldConstants {
|
||||||
return Some(sequence_expr);
|
return Some(sequence_expr);
|
||||||
} else if let Expression::LogicalExpression(left_child) = &mut logical_expr.left {
|
} else if let Expression::LogicalExpression(left_child) = &mut logical_expr.left {
|
||||||
if left_child.operator == logical_expr.operator {
|
if left_child.operator == logical_expr.operator {
|
||||||
let left_child_right_boolean = ctx.get_boolean_value(&left_child.right).to_option();
|
let left_child_right_boolean = ctx.get_boolean_value(&left_child.right);
|
||||||
let left_child_op = left_child.operator;
|
let left_child_op = left_child.operator;
|
||||||
if let Some(right_boolean) = left_child_right_boolean {
|
if let Some(right_boolean) = left_child_right_boolean {
|
||||||
if !left_child.right.may_have_side_effects() {
|
if !left_child.right.may_have_side_effects() {
|
||||||
|
|
@ -361,7 +361,7 @@ impl<'a> PeepholeFoldConstants {
|
||||||
/// Try to fold a nullish coalesce `foo ?? bar`.
|
/// Try to fold a nullish coalesce `foo ?? bar`.
|
||||||
pub fn try_fold_coalesce(
|
pub fn try_fold_coalesce(
|
||||||
logical_expr: &mut LogicalExpression<'a>,
|
logical_expr: &mut LogicalExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
debug_assert_eq!(logical_expr.operator, LogicalOperator::Coalesce);
|
debug_assert_eq!(logical_expr.operator, LogicalOperator::Coalesce);
|
||||||
let left = &logical_expr.left;
|
let left = &logical_expr.left;
|
||||||
|
|
@ -394,7 +394,7 @@ impl<'a> PeepholeFoldConstants {
|
||||||
|
|
||||||
fn try_fold_binary_expression(
|
fn try_fold_binary_expression(
|
||||||
e: &mut BinaryExpression<'a>,
|
e: &mut BinaryExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
// TODO: tryReduceOperandsForOp
|
// TODO: tryReduceOperandsForOp
|
||||||
match e.operator {
|
match e.operator {
|
||||||
|
|
@ -424,11 +424,11 @@ impl<'a> PeepholeFoldConstants {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_fold_addition<'b>(
|
fn try_fold_addition(
|
||||||
span: Span,
|
span: Span,
|
||||||
left: &'b Expression<'a>,
|
left: &'b Expression<'a>,
|
||||||
right: &'b Expression<'a>,
|
right: &'b Expression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
// skip any potentially dangerous compressions
|
// skip any potentially dangerous compressions
|
||||||
if left.may_have_side_effects() || right.may_have_side_effects() {
|
if left.may_have_side_effects() || right.may_have_side_effects() {
|
||||||
|
|
@ -469,7 +469,7 @@ impl<'a> PeepholeFoldConstants {
|
||||||
|
|
||||||
fn try_fold_arithmetic_op(
|
fn try_fold_arithmetic_op(
|
||||||
operation: &mut BinaryExpression<'a>,
|
operation: &mut BinaryExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
fn shorter_than_original(
|
fn shorter_than_original(
|
||||||
result: f64,
|
result: f64,
|
||||||
|
|
@ -544,7 +544,7 @@ impl<'a> PeepholeFoldConstants {
|
||||||
left: f64,
|
left: f64,
|
||||||
operator: BinaryOperator,
|
operator: BinaryOperator,
|
||||||
right: f64,
|
right: f64,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
if left.is_finite() && right.is_finite() || !operator.is_arithmetic() {
|
if left.is_finite() && right.is_finite() || !operator.is_arithmetic() {
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -585,21 +585,21 @@ impl<'a> PeepholeFoldConstants {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_fold_instanceof<'b>(
|
fn try_fold_instanceof(
|
||||||
_span: Span,
|
_span: Span,
|
||||||
_left: &'b Expression<'a>,
|
_left: &'b Expression<'a>,
|
||||||
_right: &'b Expression<'a>,
|
_right: &'b Expression<'a>,
|
||||||
_ctx: &mut TraverseCtx<'a>,
|
_ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_fold_comparison<'b>(
|
fn try_fold_comparison(
|
||||||
span: Span,
|
span: Span,
|
||||||
op: BinaryOperator,
|
op: BinaryOperator,
|
||||||
left: &'b Expression<'a>,
|
left: &'b Expression<'a>,
|
||||||
right: &'b Expression<'a>,
|
right: &'b Expression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
let value = match Self::evaluate_comparison(op, left, right, ctx) {
|
let value = match Self::evaluate_comparison(op, left, right, ctx) {
|
||||||
Tri::True => true,
|
Tri::True => true,
|
||||||
|
|
@ -609,11 +609,11 @@ impl<'a> PeepholeFoldConstants {
|
||||||
Some(ctx.ast.expression_boolean_literal(span, value))
|
Some(ctx.ast.expression_boolean_literal(span, value))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn evaluate_comparison<'b>(
|
fn evaluate_comparison(
|
||||||
op: BinaryOperator,
|
op: BinaryOperator,
|
||||||
left: &'b Expression<'a>,
|
left: &'b Expression<'a>,
|
||||||
right: &'b Expression<'a>,
|
right: &'b Expression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Tri {
|
) -> Tri {
|
||||||
if left.may_have_side_effects() || right.may_have_side_effects() {
|
if left.may_have_side_effects() || right.may_have_side_effects() {
|
||||||
return Tri::Unknown;
|
return Tri::Unknown;
|
||||||
|
|
@ -646,10 +646,10 @@ impl<'a> PeepholeFoldConstants {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://tc39.es/ecma262/#sec-abstract-equality-comparison>
|
/// <https://tc39.es/ecma262/#sec-abstract-equality-comparison>
|
||||||
fn try_abstract_equality_comparison<'b>(
|
fn try_abstract_equality_comparison(
|
||||||
left_expr: &'b Expression<'a>,
|
left_expr: &'b Expression<'a>,
|
||||||
right_expr: &'b Expression<'a>,
|
right_expr: &'b Expression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Tri {
|
) -> Tri {
|
||||||
let left = ValueType::from(left_expr);
|
let left = ValueType::from(left_expr);
|
||||||
let right = ValueType::from(right_expr);
|
let right = ValueType::from(right_expr);
|
||||||
|
|
@ -737,11 +737,11 @@ impl<'a> PeepholeFoldConstants {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://tc39.es/ecma262/#sec-abstract-relational-comparison>
|
/// <https://tc39.es/ecma262/#sec-abstract-relational-comparison>
|
||||||
fn try_abstract_relational_comparison<'b>(
|
fn try_abstract_relational_comparison(
|
||||||
left_expr: &'b Expression<'a>,
|
left_expr: &'b Expression<'a>,
|
||||||
right_expr: &'b Expression<'a>,
|
right_expr: &'b Expression<'a>,
|
||||||
will_negative: bool,
|
will_negative: bool,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Tri {
|
) -> Tri {
|
||||||
let left = ValueType::from(left_expr);
|
let left = ValueType::from(left_expr);
|
||||||
let right = ValueType::from(right_expr);
|
let right = ValueType::from(right_expr);
|
||||||
|
|
@ -848,10 +848,10 @@ impl<'a> PeepholeFoldConstants {
|
||||||
|
|
||||||
/// <https://tc39.es/ecma262/#sec-strict-equality-comparison>
|
/// <https://tc39.es/ecma262/#sec-strict-equality-comparison>
|
||||||
#[expect(clippy::float_cmp)]
|
#[expect(clippy::float_cmp)]
|
||||||
fn try_strict_equality_comparison<'b>(
|
fn try_strict_equality_comparison(
|
||||||
left_expr: &'b Expression<'a>,
|
left_expr: &'b Expression<'a>,
|
||||||
right_expr: &'b Expression<'a>,
|
right_expr: &'b Expression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Tri {
|
) -> Tri {
|
||||||
let left = ValueType::from(left_expr);
|
let left = ValueType::from(left_expr);
|
||||||
let right = ValueType::from(right_expr);
|
let right = ValueType::from(right_expr);
|
||||||
|
|
@ -922,12 +922,12 @@ impl<'a> PeepholeFoldConstants {
|
||||||
|
|
||||||
/// ported from [closure-compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L1114-L1162)
|
/// ported from [closure-compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/PeepholeFoldConstants.java#L1114-L1162)
|
||||||
#[allow(clippy::cast_possible_truncation)]
|
#[allow(clippy::cast_possible_truncation)]
|
||||||
fn try_fold_shift<'b>(
|
fn try_fold_shift(
|
||||||
span: Span,
|
span: Span,
|
||||||
op: BinaryOperator,
|
op: BinaryOperator,
|
||||||
left: &'b Expression<'a>,
|
left: &'b Expression<'a>,
|
||||||
right: &'b Expression<'a>,
|
right: &'b Expression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
let left_num = ctx.get_side_free_number_value(left);
|
let left_num = ctx.get_side_free_number_value(left);
|
||||||
let right_num = ctx.get_side_free_number_value(right);
|
let right_num = ctx.get_side_free_number_value(right);
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
use oxc_allocator::Vec;
|
use oxc_allocator::Vec;
|
||||||
use oxc_ast::{ast::*, Visit};
|
use oxc_ast::{ast::*, Visit};
|
||||||
|
use oxc_ecmascript::ConstantEvaluation;
|
||||||
use oxc_span::SPAN;
|
use oxc_span::SPAN;
|
||||||
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
|
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
|
||||||
|
|
||||||
use crate::node_util::IsLiteralValue;
|
use crate::node_util::{Ctx, IsLiteralValue};
|
||||||
use crate::{keep_var::KeepVar, node_util::NodeUtil, tri::Tri, CompressorPass};
|
use crate::{keep_var::KeepVar, CompressorPass};
|
||||||
|
|
||||||
/// Remove Dead Code from the AST.
|
/// Remove Dead Code from the AST.
|
||||||
///
|
///
|
||||||
|
|
@ -29,6 +30,7 @@ impl<'a> CompressorPass<'a> for PeepholeRemoveDeadCode {
|
||||||
|
|
||||||
impl<'a> Traverse<'a> for PeepholeRemoveDeadCode {
|
impl<'a> Traverse<'a> for PeepholeRemoveDeadCode {
|
||||||
fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
|
let ctx = Ctx(ctx);
|
||||||
if let Some(new_stmt) = match stmt {
|
if let Some(new_stmt) = match stmt {
|
||||||
Statement::IfStatement(if_stmt) => self.try_fold_if(if_stmt, ctx),
|
Statement::IfStatement(if_stmt) => self.try_fold_if(if_stmt, ctx),
|
||||||
Statement::ForStatement(for_stmt) => self.try_fold_for(for_stmt, ctx),
|
Statement::ForStatement(for_stmt) => self.try_fold_for(for_stmt, ctx),
|
||||||
|
|
@ -43,17 +45,18 @@ impl<'a> Traverse<'a> for PeepholeRemoveDeadCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
fn exit_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
self.compress_block(stmt, ctx);
|
self.compress_block(stmt, Ctx(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
fn exit_statements(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: &mut TraverseCtx<'a>) {
|
||||||
if stmts.iter().any(|stmt| matches!(stmt, Statement::EmptyStatement(_))) {
|
if stmts.iter().any(|stmt| matches!(stmt, Statement::EmptyStatement(_))) {
|
||||||
stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_)));
|
stmts.retain(|stmt| !matches!(stmt, Statement::EmptyStatement(_)));
|
||||||
}
|
}
|
||||||
self.dead_code_elimination(stmts, ctx);
|
self.dead_code_elimination(stmts, Ctx(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
|
let ctx = Ctx(ctx);
|
||||||
if let Some(folded_expr) = match expr {
|
if let Some(folded_expr) = match expr {
|
||||||
Expression::ConditionalExpression(e) => Self::try_fold_conditional_expression(e, ctx),
|
Expression::ConditionalExpression(e) => Self::try_fold_conditional_expression(e, ctx),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
@ -64,17 +67,13 @@ impl<'a> Traverse<'a> for PeepholeRemoveDeadCode {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PeepholeRemoveDeadCode {
|
impl<'a, 'b> PeepholeRemoveDeadCode {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self { changed: false }
|
Self { changed: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes dead code thats comes after `return` statements after inlining `if` statements
|
/// Removes dead code thats comes after `return` statements after inlining `if` statements
|
||||||
fn dead_code_elimination(
|
fn dead_code_elimination(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: Ctx<'a, 'b>) {
|
||||||
&mut self,
|
|
||||||
stmts: &mut Vec<'a, Statement<'a>>,
|
|
||||||
ctx: &mut TraverseCtx<'a>,
|
|
||||||
) {
|
|
||||||
// Remove code after `return` and `throw` statements
|
// Remove code after `return` and `throw` statements
|
||||||
let mut index = None;
|
let mut index = None;
|
||||||
'outer: for (i, stmt) in stmts.iter().enumerate() {
|
'outer: for (i, stmt) in stmts.iter().enumerate() {
|
||||||
|
|
@ -134,7 +133,7 @@ impl<'a> PeepholeRemoveDeadCode {
|
||||||
|
|
||||||
/// Remove block from single line blocks
|
/// Remove block from single line blocks
|
||||||
/// `{ block } -> block`
|
/// `{ block } -> block`
|
||||||
fn compress_block(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
fn compress_block(&mut self, stmt: &mut Statement<'a>, ctx: Ctx<'a, 'b>) {
|
||||||
if let Statement::BlockStatement(block) = stmt {
|
if let Statement::BlockStatement(block) = stmt {
|
||||||
// Avoid compressing `if (x) { var x = 1 }` to `if (x) var x = 1` due to different
|
// Avoid compressing `if (x) { var x = 1 }` to `if (x) var x = 1` due to different
|
||||||
// semantics according to AnnexB, which lead to different semantics.
|
// semantics according to AnnexB, which lead to different semantics.
|
||||||
|
|
@ -156,7 +155,7 @@ impl<'a> PeepholeRemoveDeadCode {
|
||||||
fn try_fold_if(
|
fn try_fold_if(
|
||||||
&mut self,
|
&mut self,
|
||||||
if_stmt: &mut IfStatement<'a>,
|
if_stmt: &mut IfStatement<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Statement<'a>> {
|
) -> Option<Statement<'a>> {
|
||||||
// Descend and remove `else` blocks first.
|
// Descend and remove `else` blocks first.
|
||||||
if let Some(Statement::IfStatement(alternate)) = &mut if_stmt.alternate {
|
if let Some(Statement::IfStatement(alternate)) = &mut if_stmt.alternate {
|
||||||
|
|
@ -171,11 +170,11 @@ impl<'a> PeepholeRemoveDeadCode {
|
||||||
}
|
}
|
||||||
|
|
||||||
match ctx.get_boolean_value(&if_stmt.test) {
|
match ctx.get_boolean_value(&if_stmt.test) {
|
||||||
Tri::True => {
|
Some(true) => {
|
||||||
// self.changed = true;
|
// self.changed = true;
|
||||||
Some(ctx.ast.move_statement(&mut if_stmt.consequent))
|
Some(ctx.ast.move_statement(&mut if_stmt.consequent))
|
||||||
}
|
}
|
||||||
Tri::False => {
|
Some(false) => {
|
||||||
Some(if let Some(alternate) = &mut if_stmt.alternate {
|
Some(if let Some(alternate) = &mut if_stmt.alternate {
|
||||||
ctx.ast.move_statement(alternate)
|
ctx.ast.move_statement(alternate)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -188,19 +187,18 @@ impl<'a> PeepholeRemoveDeadCode {
|
||||||
})
|
})
|
||||||
// self.changed = true;
|
// self.changed = true;
|
||||||
}
|
}
|
||||||
Tri::Unknown => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_fold_for(
|
fn try_fold_for(
|
||||||
&mut self,
|
&mut self,
|
||||||
for_stmt: &mut ForStatement<'a>,
|
for_stmt: &mut ForStatement<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Statement<'a>> {
|
) -> Option<Statement<'a>> {
|
||||||
let test_boolean =
|
let test_boolean = for_stmt.test.as_ref().and_then(|test| ctx.get_boolean_value(test));
|
||||||
for_stmt.test.as_ref().map_or(Tri::Unknown, |test| ctx.get_boolean_value(test));
|
|
||||||
match test_boolean {
|
match test_boolean {
|
||||||
Tri::False => {
|
Some(false) => {
|
||||||
// Remove the entire `for` statement.
|
// Remove the entire `for` statement.
|
||||||
// Check vars in statement
|
// Check vars in statement
|
||||||
let mut keep_var = KeepVar::new(ctx.ast);
|
let mut keep_var = KeepVar::new(ctx.ast);
|
||||||
|
|
@ -211,19 +209,19 @@ impl<'a> PeepholeRemoveDeadCode {
|
||||||
.unwrap_or_else(|| ctx.ast.statement_empty(SPAN)),
|
.unwrap_or_else(|| ctx.ast.statement_empty(SPAN)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Tri::True => {
|
Some(true) => {
|
||||||
// Remove the test expression.
|
// Remove the test expression.
|
||||||
for_stmt.test = None;
|
for_stmt.test = None;
|
||||||
self.changed = true;
|
self.changed = true;
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
Tri::Unknown => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_fold_expression_stmt(
|
fn try_fold_expression_stmt(
|
||||||
stmt: &mut ExpressionStatement<'a>,
|
stmt: &mut ExpressionStatement<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Statement<'a>> {
|
) -> Option<Statement<'a>> {
|
||||||
// We need to check if it is in arrow function with `expression: true`.
|
// We need to check if it is in arrow function with `expression: true`.
|
||||||
// This is the only scenario where we can't remove it even if `ExpressionStatement`.
|
// This is the only scenario where we can't remove it even if `ExpressionStatement`.
|
||||||
|
|
@ -245,10 +243,10 @@ impl<'a> PeepholeRemoveDeadCode {
|
||||||
/// Try folding conditional expression (?:) if the condition results of the condition is known.
|
/// Try folding conditional expression (?:) if the condition results of the condition is known.
|
||||||
fn try_fold_conditional_expression(
|
fn try_fold_conditional_expression(
|
||||||
expr: &mut ConditionalExpression<'a>,
|
expr: &mut ConditionalExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
match ctx.get_boolean_value(&expr.test) {
|
match ctx.eval_to_boolean(&expr.test) {
|
||||||
Tri::True => {
|
Some(true) => {
|
||||||
// Bail `let o = { f() { assert.ok(this !== o); } }; (true ? o.f : false)(); (true ? o.f : false)``;`
|
// Bail `let o = { f() { assert.ok(this !== o); } }; (true ? o.f : false)(); (true ? o.f : false)``;`
|
||||||
let parent = ctx.ancestry.parent();
|
let parent = ctx.ancestry.parent();
|
||||||
if parent.is_tagged_template_expression()
|
if parent.is_tagged_template_expression()
|
||||||
|
|
@ -258,8 +256,8 @@ impl<'a> PeepholeRemoveDeadCode {
|
||||||
}
|
}
|
||||||
Some(ctx.ast.move_expression(&mut expr.consequent))
|
Some(ctx.ast.move_expression(&mut expr.consequent))
|
||||||
}
|
}
|
||||||
Tri::False => Some(ctx.ast.move_expression(&mut expr.alternate)),
|
Some(false) => Some(ctx.ast.move_expression(&mut expr.alternate)),
|
||||||
Tri::Unknown => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use oxc_syntax::{
|
||||||
};
|
};
|
||||||
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
|
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
|
||||||
|
|
||||||
use crate::{node_util::NodeUtil, CompressOptions, CompressorPass};
|
use crate::{node_util::Ctx, CompressOptions, CompressorPass};
|
||||||
|
|
||||||
/// A peephole optimization that minimizes code by simplifying conditional
|
/// A peephole optimization that minimizes code by simplifying conditional
|
||||||
/// expressions, replacing IFs with HOOKs, replacing object constructors
|
/// expressions, replacing IFs with HOOKs, replacing object constructors
|
||||||
|
|
@ -48,7 +48,7 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) {
|
) {
|
||||||
for declarator in decl.declarations.iter_mut() {
|
for declarator in decl.declarations.iter_mut() {
|
||||||
self.compress_variable_declarator(declarator, ctx);
|
self.compress_variable_declarator(declarator, Ctx(ctx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,6 +76,7 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
fn enter_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
|
let ctx = Ctx(ctx);
|
||||||
if let Expression::AssignmentExpression(assignment_expr) = expr {
|
if let Expression::AssignmentExpression(assignment_expr) = expr {
|
||||||
if let Some(new_expr) = Self::try_compress_assignment_expression(assignment_expr, ctx) {
|
if let Some(new_expr) = Self::try_compress_assignment_expression(assignment_expr, ctx) {
|
||||||
*expr = new_expr;
|
*expr = new_expr;
|
||||||
|
|
@ -88,6 +89,7 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||||
|
let ctx = Ctx(ctx);
|
||||||
match expr {
|
match expr {
|
||||||
Expression::NewExpression(new_expr) => {
|
Expression::NewExpression(new_expr) => {
|
||||||
if let Some(new_expr) = Self::try_fold_new_expression(new_expr, ctx) {
|
if let Some(new_expr) = Self::try_fold_new_expression(new_expr, ctx) {
|
||||||
|
|
@ -128,11 +130,11 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
|
||||||
expr: &mut BinaryExpression<'a>,
|
expr: &mut BinaryExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: &mut TraverseCtx<'a>,
|
||||||
) {
|
) {
|
||||||
self.compress_typeof_undefined(expr, ctx);
|
self.compress_typeof_undefined(expr, Ctx(ctx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PeepholeSubstituteAlternateSyntax {
|
impl<'a, 'b> PeepholeSubstituteAlternateSyntax {
|
||||||
pub fn new(options: CompressOptions) -> Self {
|
pub fn new(options: CompressOptions) -> Self {
|
||||||
Self { options, in_define_export: false, changed: false }
|
Self { options, in_define_export: false, changed: false }
|
||||||
}
|
}
|
||||||
|
|
@ -140,7 +142,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
||||||
/* Utilities */
|
/* Utilities */
|
||||||
|
|
||||||
/// Transforms `undefined` => `void 0`
|
/// Transforms `undefined` => `void 0`
|
||||||
fn compress_undefined(expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
fn compress_undefined(expr: &mut Expression<'a>, ctx: Ctx<'a, 'b>) -> bool {
|
||||||
if ctx.is_expression_undefined(expr) {
|
if ctx.is_expression_undefined(expr) {
|
||||||
*expr = ctx.ast.void_0(expr.span());
|
*expr = ctx.ast.void_0(expr.span());
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -185,7 +187,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
||||||
/// Transforms boolean expression `true` => `!0` `false` => `!1`.
|
/// Transforms boolean expression `true` => `!0` `false` => `!1`.
|
||||||
/// Enabled by `compress.booleans`.
|
/// Enabled by `compress.booleans`.
|
||||||
/// Do not compress `true` in `Object.defineProperty(exports, 'Foo', {enumerable: true, ...})`.
|
/// Do not compress `true` in `Object.defineProperty(exports, 'Foo', {enumerable: true, ...})`.
|
||||||
fn compress_boolean(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) -> bool {
|
fn compress_boolean(&mut self, expr: &mut Expression<'a>, ctx: Ctx<'a, 'b>) -> bool {
|
||||||
let Expression::BooleanLiteral(lit) = expr else { return false };
|
let Expression::BooleanLiteral(lit) = expr else { return false };
|
||||||
if self.options.booleans && !self.in_define_export {
|
if self.options.booleans && !self.in_define_export {
|
||||||
let parent = ctx.ancestry.parent();
|
let parent = ctx.ancestry.parent();
|
||||||
|
|
@ -223,11 +225,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
||||||
|
|
||||||
/// Compress `typeof foo == "undefined"` into `typeof foo > "u"`
|
/// Compress `typeof foo == "undefined"` into `typeof foo > "u"`
|
||||||
/// Enabled by `compress.typeofs`
|
/// Enabled by `compress.typeofs`
|
||||||
fn compress_typeof_undefined(
|
fn compress_typeof_undefined(&self, expr: &mut BinaryExpression<'a>, ctx: Ctx<'a, 'b>) {
|
||||||
&self,
|
|
||||||
expr: &mut BinaryExpression<'a>,
|
|
||||||
ctx: &mut TraverseCtx<'a>,
|
|
||||||
) {
|
|
||||||
if !self.options.typeofs {
|
if !self.options.typeofs {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -298,7 +296,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
||||||
fn compress_variable_declarator(
|
fn compress_variable_declarator(
|
||||||
&mut self,
|
&mut self,
|
||||||
decl: &mut VariableDeclarator<'a>,
|
decl: &mut VariableDeclarator<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) {
|
) {
|
||||||
if decl.kind.is_const() {
|
if decl.kind.is_const() {
|
||||||
return;
|
return;
|
||||||
|
|
@ -311,7 +309,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
||||||
|
|
||||||
fn try_compress_assignment_expression(
|
fn try_compress_assignment_expression(
|
||||||
expr: &mut AssignmentExpression<'a>,
|
expr: &mut AssignmentExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
let target = expr.left.as_simple_assignment_target_mut()?;
|
let target = expr.left.as_simple_assignment_target_mut()?;
|
||||||
if matches!(expr.operator, AssignmentOperator::Subtraction) {
|
if matches!(expr.operator, AssignmentOperator::Subtraction) {
|
||||||
|
|
@ -354,7 +352,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
||||||
|
|
||||||
fn try_fold_new_expression(
|
fn try_fold_new_expression(
|
||||||
new_expr: &mut NewExpression<'a>,
|
new_expr: &mut NewExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
// `new Object` -> `{}`
|
// `new Object` -> `{}`
|
||||||
if new_expr.arguments.is_empty()
|
if new_expr.arguments.is_empty()
|
||||||
|
|
@ -413,7 +411,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
||||||
|
|
||||||
fn try_fold_literal_constructor_call_expression(
|
fn try_fold_literal_constructor_call_expression(
|
||||||
call_expr: &mut CallExpression<'a>,
|
call_expr: &mut CallExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
// `Object()` -> `{}`
|
// `Object()` -> `{}`
|
||||||
if call_expr.arguments.is_empty()
|
if call_expr.arguments.is_empty()
|
||||||
|
|
@ -467,7 +465,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
||||||
|
|
||||||
fn try_fold_simple_function_call(
|
fn try_fold_simple_function_call(
|
||||||
call_expr: &mut CallExpression<'a>,
|
call_expr: &mut CallExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Option<Expression<'a>> {
|
) -> Option<Expression<'a>> {
|
||||||
if call_expr.optional || call_expr.arguments.len() != 1 {
|
if call_expr.optional || call_expr.arguments.len() != 1 {
|
||||||
return None;
|
return None;
|
||||||
|
|
@ -521,7 +519,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
||||||
fn try_fold_chain_call_expression(
|
fn try_fold_chain_call_expression(
|
||||||
&mut self,
|
&mut self,
|
||||||
call_expr: &mut CallExpression<'a>,
|
call_expr: &mut CallExpression<'a>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) {
|
) {
|
||||||
// `window.Object?.()` -> `Object?.()`
|
// `window.Object?.()` -> `Object?.()`
|
||||||
if call_expr.arguments.is_empty() && Self::is_window_object(&call_expr.callee) {
|
if call_expr.arguments.is_empty() && Self::is_window_object(&call_expr.callee) {
|
||||||
|
|
@ -534,7 +532,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
||||||
/// returns an `Array()` constructor call with zero, one, or more arguments, copying from the input
|
/// returns an `Array()` constructor call with zero, one, or more arguments, copying from the input
|
||||||
fn array_constructor_call(
|
fn array_constructor_call(
|
||||||
arguments: Vec<'a, Argument<'a>>,
|
arguments: Vec<'a, Argument<'a>>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Expression<'a> {
|
) -> Expression<'a> {
|
||||||
let callee = ctx.ast.expression_identifier_reference(SPAN, "Array");
|
let callee = ctx.ast.expression_identifier_reference(SPAN, "Array");
|
||||||
ctx.ast.expression_call(SPAN, callee, NONE, arguments, false)
|
ctx.ast.expression_call(SPAN, callee, NONE, arguments, false)
|
||||||
|
|
@ -543,13 +541,13 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
||||||
/// returns an array literal `[]` of zero, one, or more elements, copying from the input
|
/// returns an array literal `[]` of zero, one, or more elements, copying from the input
|
||||||
fn array_literal(
|
fn array_literal(
|
||||||
elements: Vec<'a, ArrayExpressionElement<'a>>,
|
elements: Vec<'a, ArrayExpressionElement<'a>>,
|
||||||
ctx: &mut TraverseCtx<'a>,
|
ctx: Ctx<'a, 'b>,
|
||||||
) -> Expression<'a> {
|
) -> Expression<'a> {
|
||||||
ctx.ast.expression_array(SPAN, elements, None)
|
ctx.ast.expression_array(SPAN, elements, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns a new empty array literal expression: `[]`
|
/// returns a new empty array literal expression: `[]`
|
||||||
fn empty_array_literal(ctx: &mut TraverseCtx<'a>) -> Expression<'a> {
|
fn empty_array_literal(ctx: Ctx<'a, 'b>) -> Expression<'a> {
|
||||||
Self::array_literal(ctx.ast.vec(), ctx)
|
Self::array_literal(ctx.ast.vec(), ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,47 +3,61 @@ mod is_literal_value;
|
||||||
mod may_have_side_effects;
|
mod may_have_side_effects;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
use num_bigint::BigInt;
|
use num_bigint::BigInt;
|
||||||
use oxc_ast::ast::*;
|
use oxc_ast::ast::*;
|
||||||
use oxc_ecmascript::{StringToBigInt, ToBigInt, ToBoolean, ToJsString, ToNumber};
|
use oxc_ecmascript::ConstantEvaluation;
|
||||||
use oxc_semantic::{IsGlobalReference, ScopeTree, SymbolTable};
|
use oxc_ecmascript::{StringToBigInt, ToBigInt, ToJsString};
|
||||||
|
use oxc_semantic::{IsGlobalReference, SymbolTable};
|
||||||
|
use oxc_traverse::TraverseCtx;
|
||||||
|
|
||||||
pub use self::{is_literal_value::IsLiteralValue, may_have_side_effects::MayHaveSideEffects};
|
pub use self::{is_literal_value::IsLiteralValue, may_have_side_effects::MayHaveSideEffects};
|
||||||
|
|
||||||
use crate::tri::Tri;
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct Ctx<'a, 'b>(pub &'b TraverseCtx<'a>);
|
||||||
|
|
||||||
|
impl<'a, 'b> Deref for Ctx<'a, 'b> {
|
||||||
|
type Target = &'b TraverseCtx<'a>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> ConstantEvaluation<'a> for Ctx<'a, 'b> {
|
||||||
|
fn is_global_reference(&self, ident: &oxc_ast::ast::IdentifierReference<'a>) -> bool {
|
||||||
|
ident.is_global_reference(self.0.symbols())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_exact_int64(num: f64) -> bool {
|
pub fn is_exact_int64(num: f64) -> bool {
|
||||||
num.fract() == 0.0
|
num.fract() == 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait NodeUtil<'a> {
|
impl<'a, 'b> Ctx<'a, 'b> {
|
||||||
fn symbols(&self) -> &SymbolTable;
|
fn symbols(&self) -> &SymbolTable {
|
||||||
|
self.0.symbols()
|
||||||
#[allow(unused)]
|
|
||||||
fn scopes(&self) -> &ScopeTree;
|
|
||||||
|
|
||||||
fn is_expression_undefined(&self, expr: &Expression) -> bool {
|
|
||||||
match expr {
|
|
||||||
Expression::Identifier(ident) if self.is_identifier_undefined(ident) => true,
|
|
||||||
Expression::UnaryExpression(e) if e.operator.is_void() && e.argument.is_number() => {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_identifier_undefined(&self, ident: &IdentifierReference) -> bool {
|
/// Gets the boolean value of a node that represents an expression, or `None` if no
|
||||||
if ident.name == "undefined" && ident.is_global_reference(self.symbols()) {
|
/// such value can be determined by static analysis.
|
||||||
return true;
|
/// 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>
|
||||||
false
|
pub fn get_boolean_value(self, expr: &Expression<'a>) -> Option<bool> {
|
||||||
|
self.eval_to_boolean(expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the value of a node as a Number, or None if it cannot be converted.
|
||||||
|
/// This method does not consider whether `expr` may have side effects.
|
||||||
|
/// <https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/NodeUtil.java#L348>
|
||||||
|
pub fn get_number_value(self, expr: &Expression<'a>) -> Option<f64> {
|
||||||
|
self.eval_to_number(expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/AbstractPeepholeOptimization.java#L104-L114)
|
/// 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.
|
/// Returns the number value of the node if it has one and it cannot have side effects.
|
||||||
fn get_side_free_number_value(&self, expr: &Expression<'a>) -> Option<f64> {
|
pub fn get_side_free_number_value(self, expr: &Expression<'a>) -> Option<f64> {
|
||||||
let value = self.get_number_value(expr);
|
let value = self.eval_to_number(expr);
|
||||||
// Calculating the number value, if any, is likely to be faster than calculating side effects,
|
// 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
|
// 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
|
// also be side effects. e.g. `void doSomething()` has value NaN, regardless of the behavior
|
||||||
|
|
@ -55,8 +69,25 @@ pub trait NodeUtil<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_expression_undefined(self, expr: &Expression) -> bool {
|
||||||
|
match expr {
|
||||||
|
Expression::Identifier(ident) if self.is_identifier_undefined(ident) => true,
|
||||||
|
Expression::UnaryExpression(e) if e.operator.is_void() && e.argument.is_number() => {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_identifier_undefined(self, ident: &IdentifierReference) -> bool {
|
||||||
|
if ident.name == "undefined" && ident.is_global_reference(self.symbols()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/AbstractPeepholeOptimization.java#L121)
|
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/AbstractPeepholeOptimization.java#L121)
|
||||||
fn get_side_free_bigint_value(&self, expr: &Expression<'a>) -> Option<BigInt> {
|
pub fn get_side_free_bigint_value(self, expr: &Expression<'a>) -> Option<BigInt> {
|
||||||
let value = self.get_bigint_value(expr);
|
let value = self.get_bigint_value(expr);
|
||||||
// Calculating the bigint value, if any, is likely to be faster than calculating side effects,
|
// 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
|
// and there are only a very few cases where we can compute a bigint value, but there could
|
||||||
|
|
@ -73,7 +104,7 @@ pub trait NodeUtil<'a> {
|
||||||
/// Gets the value of a node as a String, or `None` if it cannot be converted.
|
/// 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
|
/// This method effectively emulates the `String()` JavaScript cast function when
|
||||||
/// possible and the node has no side effects. Otherwise, it returns `None`.
|
/// possible and the node has no side effects. Otherwise, it returns `None`.
|
||||||
fn get_side_free_string_value(&self, expr: &'a Expression) -> Option<Cow<'a, str>> {
|
pub fn get_side_free_string_value(self, expr: &'a Expression) -> Option<Cow<'a, str>> {
|
||||||
let value = self.get_string_value(expr);
|
let value = self.get_string_value(expr);
|
||||||
// Calculating the string value, if any, is likely to be faster than calculating side effects,
|
// 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
|
// and there are only a very few cases where we can compute a string value, but there could
|
||||||
|
|
@ -85,22 +116,8 @@ pub trait NodeUtil<'a> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/NodeUtil.java#L109)
|
#[expect(clippy::unused_self)]
|
||||||
// Gets the boolean value of a node that represents an expression, or `None` if no
|
pub fn get_bigint_value(self, expr: &Expression<'a>) -> Option<BigInt> {
|
||||||
// such value can be determined by static analysis.
|
|
||||||
// This method does not consider whether the node may have side-effects.
|
|
||||||
fn get_boolean_value(&self, expr: &Expression<'a>) -> Tri {
|
|
||||||
Tri::from(expr.to_boolean())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/NodeUtil.java#L348)
|
|
||||||
/// Gets the value of a node as a Number, or None if it cannot be converted.
|
|
||||||
/// This method does not consider whether `expr` may have side effects.
|
|
||||||
fn get_number_value(&self, expr: &Expression<'a>) -> Option<f64> {
|
|
||||||
expr.to_number()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_bigint_value(&self, expr: &Expression<'a>) -> Option<BigInt> {
|
|
||||||
expr.to_big_int()
|
expr.to_big_int()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,12 +125,14 @@ pub trait NodeUtil<'a> {
|
||||||
/// Gets the value of a node as a String, or `None` if it cannot be converted. When it returns a
|
/// 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.
|
/// String, this method effectively emulates the `String()` JavaScript cast function.
|
||||||
/// This method does not consider whether `expr` may have side effects.
|
/// This method does not consider whether `expr` may have side effects.
|
||||||
fn get_string_value(&self, expr: &Expression<'a>) -> Option<Cow<'a, str>> {
|
#[expect(clippy::unused_self)]
|
||||||
|
pub fn get_string_value(self, expr: &Expression<'a>) -> Option<Cow<'a, str>> {
|
||||||
expr.to_js_string()
|
expr.to_js_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/NodeUtil.java#L540)
|
/// port from [closure compiler](https://github.com/google/closure-compiler/blob/master/src/com/google/javascript/jscomp/NodeUtil.java#L540)
|
||||||
fn get_string_bigint_value(&self, raw_string: &str) -> Option<BigInt> {
|
#[expect(clippy::unused_self)]
|
||||||
|
pub fn get_string_bigint_value(self, raw_string: &str) -> Option<BigInt> {
|
||||||
raw_string.string_to_big_int()
|
raw_string.string_to_big_int()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,14 +45,6 @@ impl Tri {
|
||||||
Self::from(-self.value() * other.value())
|
Self::from(-self.value() * other.value())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_option(self) -> Option<bool> {
|
|
||||||
match self {
|
|
||||||
Self::True => Some(true),
|
|
||||||
Self::False => Some(false),
|
|
||||||
Self::Unknown => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn value(self) -> i8 {
|
pub fn value(self) -> i8 {
|
||||||
match self {
|
match self {
|
||||||
Self::True => 1,
|
Self::True => 1,
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,9 @@ fn test(source_text: &str, expected: &str) {
|
||||||
crate::test(&source_text, expected, options);
|
crate::test(&source_text, expected, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn test_same(source_text: &str) {
|
fn test_same(source_text: &str) {
|
||||||
// test(source_text, source_text);
|
test(source_text, source_text);
|
||||||
// }
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn dce_if_statement() {
|
fn dce_if_statement() {
|
||||||
|
|
@ -63,8 +63,7 @@ fn dce_if_statement() {
|
||||||
|
|
||||||
// Shadowed `undefined` as a variable should not be erased.
|
// Shadowed `undefined` as a variable should not be erased.
|
||||||
// This is a rollup test.
|
// This is a rollup test.
|
||||||
// FIXME:
|
test_same("function foo(undefined) { if (!undefined) { } }");
|
||||||
// test_same("function foo(undefined) { if (!undefined) { } }");
|
|
||||||
|
|
||||||
test("function foo() { if (undefined) { bar } }", "function foo() { }");
|
test("function foo() { if (undefined) { bar } }", "function foo() { }");
|
||||||
test("function foo() { { bar } }", "function foo() { bar }");
|
test("function foo() { { bar } }", "function foo() { bar }");
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue