mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 12:19:15 +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_string;
|
||||
|
||||
// Constant Evaluation
|
||||
mod constant_evaluation;
|
||||
|
||||
pub use self::{
|
||||
bound_names::BoundNames, is_simple_parameter_list::IsSimpleParameterList,
|
||||
private_bound_identifiers::PrivateBoundIdentifiers, 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,
|
||||
bound_names::BoundNames,
|
||||
constant_evaluation::{ConstantEvaluation, ConstantValue},
|
||||
is_simple_parameter_list::IsSimpleParameterList,
|
||||
private_bound_identifiers::PrivateBoundIdentifiers,
|
||||
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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,4 @@
|
|||
use num_traits::Zero;
|
||||
#[allow(clippy::wildcard_imports)]
|
||||
use oxc_ast::ast::*;
|
||||
|
||||
use crate::ToNumber;
|
||||
use oxc_ast::ast::Expression;
|
||||
|
||||
/// `ToBoolean`
|
||||
///
|
||||
|
|
@ -13,7 +9,16 @@ pub trait ToBoolean<'a> {
|
|||
|
||||
impl<'a> ToBoolean<'a> for Expression<'a> {
|
||||
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 {
|
||||
Expression::Identifier(ident) => match ident.name.as_str() {
|
||||
"NaN" | "undefined" => Some(false),
|
||||
"Infinity" => Some(true),
|
||||
_ => None,
|
||||
},
|
||||
Expression::RegExpLiteral(_)
|
||||
| Expression::ArrayExpression(_)
|
||||
| Expression::ArrowFunctionExpression(_)
|
||||
|
|
@ -35,73 +40,6 @@ impl<'a> ToBoolean<'a> for Expression<'a> {
|
|||
.and_then(|quasi| quasi.value.cooked.as_ref())
|
||||
.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,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
#[allow(clippy::wildcard_imports)]
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_syntax::operator::UnaryOperator;
|
||||
|
||||
use crate::ToBoolean;
|
||||
|
||||
/// `ToNumber`
|
||||
///
|
||||
|
|
@ -15,28 +12,6 @@ impl<'a> ToNumber<'a> for Expression<'a> {
|
|||
fn to_number(&self) -> Option<f64> {
|
||||
match self {
|
||||
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) => {
|
||||
if bool_literal.value {
|
||||
Some(1.0)
|
||||
|
|
@ -50,7 +25,7 @@ impl<'a> ToNumber<'a> for Expression<'a> {
|
|||
"NaN" | "undefined" => Some(f64::NAN),
|
||||
_ => None,
|
||||
},
|
||||
// TODO: will be implemented in next PR, just for test pass now.
|
||||
// TODO: StringToNumber
|
||||
Expression::StringLiteral(string_literal) => {
|
||||
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;
|
||||
|
||||
use oxc_ast::ast::Program;
|
||||
use oxc_semantic::{ScopeTree, SymbolTable};
|
||||
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> {
|
||||
fn changed(&self) -> bool;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use oxc_syntax::{
|
|||
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
|
||||
|
||||
use crate::{
|
||||
node_util::{is_exact_int64, IsLiteralValue, MayHaveSideEffects, NodeUtil},
|
||||
node_util::{is_exact_int64, Ctx, IsLiteralValue, MayHaveSideEffects},
|
||||
tri::Tri,
|
||||
value_type::ValueType,
|
||||
CompressorPass,
|
||||
|
|
@ -43,6 +43,7 @@ impl<'a> CompressorPass<'a> for PeepholeFoldConstants {
|
|||
|
||||
impl<'a> Traverse<'a> for PeepholeFoldConstants {
|
||||
fn exit_expression(&mut self, expr: &mut Expression<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let ctx = Ctx(ctx);
|
||||
if let Some(folded_expr) = match expr {
|
||||
Expression::CallExpression(e) => {
|
||||
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 {
|
||||
Self { changed: false }
|
||||
}
|
||||
|
||||
fn try_fold_useless_object_dot_define_properties_call(
|
||||
_call_expr: &mut CallExpression<'a>,
|
||||
_ctx: &mut TraverseCtx<'a>,
|
||||
_ctx: Ctx<'a, '_>,
|
||||
) -> Option<Expression<'a>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_fold_ctor_cal(
|
||||
_new_expr: &mut NewExpression<'a>,
|
||||
_ctx: &mut TraverseCtx<'a>,
|
||||
_ctx: Ctx<'a, '_>,
|
||||
) -> Option<Expression<'a>> {
|
||||
None
|
||||
}
|
||||
|
|
@ -90,7 +91,7 @@ impl<'a> PeepholeFoldConstants {
|
|||
/// `typeof(6) --> "number"`
|
||||
fn try_fold_type_of(
|
||||
expr: &mut UnaryExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, '_>,
|
||||
) -> Option<Expression<'a>> {
|
||||
if !expr.argument.is_literal_value(/* include_function */ true) {
|
||||
return None;
|
||||
|
|
@ -115,21 +116,21 @@ impl<'a> PeepholeFoldConstants {
|
|||
// fn try_fold_spread(
|
||||
// &mut self,
|
||||
// _new_expr: &mut NewExpression<'a>,
|
||||
// _ctx: &mut TraverseCtx<'a>,
|
||||
// _ctx: Ctx<'a,'b>,
|
||||
// ) -> Option<Expression<'a>> {
|
||||
// None
|
||||
// }
|
||||
|
||||
fn try_flatten_array_expression(
|
||||
_new_expr: &mut ArrayExpression<'a>,
|
||||
_ctx: &mut TraverseCtx<'a>,
|
||||
_ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_flatten_object_expression(
|
||||
_new_expr: &mut ObjectExpression<'a>,
|
||||
_ctx: &mut TraverseCtx<'a>,
|
||||
_ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
None
|
||||
}
|
||||
|
|
@ -137,7 +138,7 @@ impl<'a> PeepholeFoldConstants {
|
|||
fn try_fold_unary_expression(
|
||||
&mut self,
|
||||
expr: &mut UnaryExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
fn is_valid(x: f64) -> bool {
|
||||
x.is_finite() && x.fract() == 0.0
|
||||
|
|
@ -154,7 +155,6 @@ impl<'a> PeepholeFoldConstants {
|
|||
}
|
||||
}
|
||||
ctx.get_boolean_value(&expr.argument)
|
||||
.to_option()
|
||||
.map(|b| ctx.ast.expression_boolean_literal(SPAN, !b))
|
||||
}
|
||||
// `-NaN` -> `NaN`
|
||||
|
|
@ -258,7 +258,7 @@ impl<'a> PeepholeFoldConstants {
|
|||
fn try_reduce_void(
|
||||
&mut self,
|
||||
expr: &mut UnaryExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
if (!expr.argument.is_number() || !expr.argument.is_number_0())
|
||||
&& !expr.may_have_side_effects()
|
||||
|
|
@ -271,7 +271,7 @@ impl<'a> PeepholeFoldConstants {
|
|||
|
||||
fn try_fold_logical_expression(
|
||||
logical_expr: &mut LogicalExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
match logical_expr.operator {
|
||||
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)
|
||||
pub fn try_fold_and_or(
|
||||
logical_expr: &mut LogicalExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let op = logical_expr.operator;
|
||||
debug_assert!(matches!(op, LogicalOperator::And | LogicalOperator::Or));
|
||||
|
||||
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 {
|
||||
// Bail `0 && (module.exports = {})` for `cjs-module-lexer`.
|
||||
|
|
@ -332,7 +332,7 @@ impl<'a> PeepholeFoldConstants {
|
|||
return Some(sequence_expr);
|
||||
} else if let Expression::LogicalExpression(left_child) = &mut logical_expr.left {
|
||||
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;
|
||||
if let Some(right_boolean) = left_child_right_boolean {
|
||||
if !left_child.right.may_have_side_effects() {
|
||||
|
|
@ -361,7 +361,7 @@ impl<'a> PeepholeFoldConstants {
|
|||
/// Try to fold a nullish coalesce `foo ?? bar`.
|
||||
pub fn try_fold_coalesce(
|
||||
logical_expr: &mut LogicalExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
debug_assert_eq!(logical_expr.operator, LogicalOperator::Coalesce);
|
||||
let left = &logical_expr.left;
|
||||
|
|
@ -394,7 +394,7 @@ impl<'a> PeepholeFoldConstants {
|
|||
|
||||
fn try_fold_binary_expression(
|
||||
e: &mut BinaryExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
// TODO: tryReduceOperandsForOp
|
||||
match e.operator {
|
||||
|
|
@ -424,11 +424,11 @@ impl<'a> PeepholeFoldConstants {
|
|||
}
|
||||
}
|
||||
|
||||
fn try_fold_addition<'b>(
|
||||
fn try_fold_addition(
|
||||
span: Span,
|
||||
left: &'b Expression<'a>,
|
||||
right: &'b Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
// skip any potentially dangerous compressions
|
||||
if left.may_have_side_effects() || right.may_have_side_effects() {
|
||||
|
|
@ -469,7 +469,7 @@ impl<'a> PeepholeFoldConstants {
|
|||
|
||||
fn try_fold_arithmetic_op(
|
||||
operation: &mut BinaryExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
fn shorter_than_original(
|
||||
result: f64,
|
||||
|
|
@ -544,7 +544,7 @@ impl<'a> PeepholeFoldConstants {
|
|||
left: f64,
|
||||
operator: BinaryOperator,
|
||||
right: f64,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
if left.is_finite() && right.is_finite() || !operator.is_arithmetic() {
|
||||
return None;
|
||||
|
|
@ -585,21 +585,21 @@ impl<'a> PeepholeFoldConstants {
|
|||
})
|
||||
}
|
||||
|
||||
fn try_fold_instanceof<'b>(
|
||||
fn try_fold_instanceof(
|
||||
_span: Span,
|
||||
_left: &'b Expression<'a>,
|
||||
_right: &'b Expression<'a>,
|
||||
_ctx: &mut TraverseCtx<'a>,
|
||||
_ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn try_fold_comparison<'b>(
|
||||
fn try_fold_comparison(
|
||||
span: Span,
|
||||
op: BinaryOperator,
|
||||
left: &'b Expression<'a>,
|
||||
right: &'b Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let value = match Self::evaluate_comparison(op, left, right, ctx) {
|
||||
Tri::True => true,
|
||||
|
|
@ -609,11 +609,11 @@ impl<'a> PeepholeFoldConstants {
|
|||
Some(ctx.ast.expression_boolean_literal(span, value))
|
||||
}
|
||||
|
||||
fn evaluate_comparison<'b>(
|
||||
fn evaluate_comparison(
|
||||
op: BinaryOperator,
|
||||
left: &'b Expression<'a>,
|
||||
right: &'b Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Tri {
|
||||
if left.may_have_side_effects() || right.may_have_side_effects() {
|
||||
return Tri::Unknown;
|
||||
|
|
@ -646,10 +646,10 @@ impl<'a> PeepholeFoldConstants {
|
|||
}
|
||||
|
||||
/// <https://tc39.es/ecma262/#sec-abstract-equality-comparison>
|
||||
fn try_abstract_equality_comparison<'b>(
|
||||
fn try_abstract_equality_comparison(
|
||||
left_expr: &'b Expression<'a>,
|
||||
right_expr: &'b Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Tri {
|
||||
let left = ValueType::from(left_expr);
|
||||
let right = ValueType::from(right_expr);
|
||||
|
|
@ -737,11 +737,11 @@ impl<'a> PeepholeFoldConstants {
|
|||
}
|
||||
|
||||
/// <https://tc39.es/ecma262/#sec-abstract-relational-comparison>
|
||||
fn try_abstract_relational_comparison<'b>(
|
||||
fn try_abstract_relational_comparison(
|
||||
left_expr: &'b Expression<'a>,
|
||||
right_expr: &'b Expression<'a>,
|
||||
will_negative: bool,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Tri {
|
||||
let left = ValueType::from(left_expr);
|
||||
let right = ValueType::from(right_expr);
|
||||
|
|
@ -848,10 +848,10 @@ impl<'a> PeepholeFoldConstants {
|
|||
|
||||
/// <https://tc39.es/ecma262/#sec-strict-equality-comparison>
|
||||
#[expect(clippy::float_cmp)]
|
||||
fn try_strict_equality_comparison<'b>(
|
||||
fn try_strict_equality_comparison(
|
||||
left_expr: &'b Expression<'a>,
|
||||
right_expr: &'b Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Tri {
|
||||
let left = ValueType::from(left_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)
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
fn try_fold_shift<'b>(
|
||||
fn try_fold_shift(
|
||||
span: Span,
|
||||
op: BinaryOperator,
|
||||
left: &'b Expression<'a>,
|
||||
right: &'b Expression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let left_num = ctx.get_side_free_number_value(left);
|
||||
let right_num = ctx.get_side_free_number_value(right);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use oxc_allocator::Vec;
|
||||
use oxc_ast::{ast::*, Visit};
|
||||
use oxc_ecmascript::ConstantEvaluation;
|
||||
use oxc_span::SPAN;
|
||||
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
|
||||
|
||||
use crate::node_util::IsLiteralValue;
|
||||
use crate::{keep_var::KeepVar, node_util::NodeUtil, tri::Tri, CompressorPass};
|
||||
use crate::node_util::{Ctx, IsLiteralValue};
|
||||
use crate::{keep_var::KeepVar, CompressorPass};
|
||||
|
||||
/// Remove Dead Code from the AST.
|
||||
///
|
||||
|
|
@ -29,6 +30,7 @@ impl<'a> CompressorPass<'a> for PeepholeRemoveDeadCode {
|
|||
|
||||
impl<'a> Traverse<'a> for PeepholeRemoveDeadCode {
|
||||
fn enter_statement(&mut self, stmt: &mut Statement<'a>, ctx: &mut TraverseCtx<'a>) {
|
||||
let ctx = Ctx(ctx);
|
||||
if let Some(new_stmt) = match stmt {
|
||||
Statement::IfStatement(if_stmt) => self.try_fold_if(if_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>) {
|
||||
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>) {
|
||||
if stmts.iter().any(|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>) {
|
||||
let ctx = Ctx(ctx);
|
||||
if let Some(folded_expr) = match expr {
|
||||
Expression::ConditionalExpression(e) => Self::try_fold_conditional_expression(e, ctx),
|
||||
_ => None,
|
||||
|
|
@ -64,17 +67,13 @@ impl<'a> Traverse<'a> for PeepholeRemoveDeadCode {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> PeepholeRemoveDeadCode {
|
||||
impl<'a, 'b> PeepholeRemoveDeadCode {
|
||||
pub fn new() -> Self {
|
||||
Self { changed: false }
|
||||
}
|
||||
|
||||
/// Removes dead code thats comes after `return` statements after inlining `if` statements
|
||||
fn dead_code_elimination(
|
||||
&mut self,
|
||||
stmts: &mut Vec<'a, Statement<'a>>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
fn dead_code_elimination(&mut self, stmts: &mut Vec<'a, Statement<'a>>, ctx: Ctx<'a, 'b>) {
|
||||
// Remove code after `return` and `throw` statements
|
||||
let mut index = None;
|
||||
'outer: for (i, stmt) in stmts.iter().enumerate() {
|
||||
|
|
@ -134,7 +133,7 @@ impl<'a> PeepholeRemoveDeadCode {
|
|||
|
||||
/// Remove block from single line blocks
|
||||
/// `{ 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 {
|
||||
// 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.
|
||||
|
|
@ -156,7 +155,7 @@ impl<'a> PeepholeRemoveDeadCode {
|
|||
fn try_fold_if(
|
||||
&mut self,
|
||||
if_stmt: &mut IfStatement<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Statement<'a>> {
|
||||
// Descend and remove `else` blocks first.
|
||||
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) {
|
||||
Tri::True => {
|
||||
Some(true) => {
|
||||
// self.changed = true;
|
||||
Some(ctx.ast.move_statement(&mut if_stmt.consequent))
|
||||
}
|
||||
Tri::False => {
|
||||
Some(false) => {
|
||||
Some(if let Some(alternate) = &mut if_stmt.alternate {
|
||||
ctx.ast.move_statement(alternate)
|
||||
} else {
|
||||
|
|
@ -188,19 +187,18 @@ impl<'a> PeepholeRemoveDeadCode {
|
|||
})
|
||||
// self.changed = true;
|
||||
}
|
||||
Tri::Unknown => None,
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_fold_for(
|
||||
&mut self,
|
||||
for_stmt: &mut ForStatement<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Statement<'a>> {
|
||||
let test_boolean =
|
||||
for_stmt.test.as_ref().map_or(Tri::Unknown, |test| ctx.get_boolean_value(test));
|
||||
let test_boolean = for_stmt.test.as_ref().and_then(|test| ctx.get_boolean_value(test));
|
||||
match test_boolean {
|
||||
Tri::False => {
|
||||
Some(false) => {
|
||||
// Remove the entire `for` statement.
|
||||
// Check vars in statement
|
||||
let mut keep_var = KeepVar::new(ctx.ast);
|
||||
|
|
@ -211,19 +209,19 @@ impl<'a> PeepholeRemoveDeadCode {
|
|||
.unwrap_or_else(|| ctx.ast.statement_empty(SPAN)),
|
||||
)
|
||||
}
|
||||
Tri::True => {
|
||||
Some(true) => {
|
||||
// Remove the test expression.
|
||||
for_stmt.test = None;
|
||||
self.changed = true;
|
||||
None
|
||||
}
|
||||
Tri::Unknown => None,
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn try_fold_expression_stmt(
|
||||
stmt: &mut ExpressionStatement<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Statement<'a>> {
|
||||
// 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`.
|
||||
|
|
@ -245,10 +243,10 @@ impl<'a> PeepholeRemoveDeadCode {
|
|||
/// Try folding conditional expression (?:) if the condition results of the condition is known.
|
||||
fn try_fold_conditional_expression(
|
||||
expr: &mut ConditionalExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
match ctx.get_boolean_value(&expr.test) {
|
||||
Tri::True => {
|
||||
match ctx.eval_to_boolean(&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();
|
||||
if parent.is_tagged_template_expression()
|
||||
|
|
@ -258,8 +256,8 @@ impl<'a> PeepholeRemoveDeadCode {
|
|||
}
|
||||
Some(ctx.ast.move_expression(&mut expr.consequent))
|
||||
}
|
||||
Tri::False => Some(ctx.ast.move_expression(&mut expr.alternate)),
|
||||
Tri::Unknown => None,
|
||||
Some(false) => Some(ctx.ast.move_expression(&mut expr.alternate)),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use oxc_syntax::{
|
|||
};
|
||||
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
|
||||
/// expressions, replacing IFs with HOOKs, replacing object constructors
|
||||
|
|
@ -48,7 +48,7 @@ impl<'a> Traverse<'a> for PeepholeSubstituteAlternateSyntax {
|
|||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
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>) {
|
||||
let ctx = Ctx(ctx);
|
||||
if let Expression::AssignmentExpression(assignment_expr) = expr {
|
||||
if let Some(new_expr) = Self::try_compress_assignment_expression(assignment_expr, ctx) {
|
||||
*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>) {
|
||||
let ctx = Ctx(ctx);
|
||||
match expr {
|
||||
Expression::NewExpression(new_expr) => {
|
||||
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>,
|
||||
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 {
|
||||
Self { options, in_define_export: false, changed: false }
|
||||
}
|
||||
|
|
@ -140,7 +142,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
|||
/* Utilities */
|
||||
|
||||
/// 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) {
|
||||
*expr = ctx.ast.void_0(expr.span());
|
||||
return true;
|
||||
|
|
@ -185,7 +187,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
|||
/// Transforms boolean expression `true` => `!0` `false` => `!1`.
|
||||
/// Enabled by `compress.booleans`.
|
||||
/// 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 };
|
||||
if self.options.booleans && !self.in_define_export {
|
||||
let parent = ctx.ancestry.parent();
|
||||
|
|
@ -223,11 +225,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
|||
|
||||
/// Compress `typeof foo == "undefined"` into `typeof foo > "u"`
|
||||
/// Enabled by `compress.typeofs`
|
||||
fn compress_typeof_undefined(
|
||||
&self,
|
||||
expr: &mut BinaryExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
) {
|
||||
fn compress_typeof_undefined(&self, expr: &mut BinaryExpression<'a>, ctx: Ctx<'a, 'b>) {
|
||||
if !self.options.typeofs {
|
||||
return;
|
||||
}
|
||||
|
|
@ -298,7 +296,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
|||
fn compress_variable_declarator(
|
||||
&mut self,
|
||||
decl: &mut VariableDeclarator<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) {
|
||||
if decl.kind.is_const() {
|
||||
return;
|
||||
|
|
@ -311,7 +309,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
|||
|
||||
fn try_compress_assignment_expression(
|
||||
expr: &mut AssignmentExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
let target = expr.left.as_simple_assignment_target_mut()?;
|
||||
if matches!(expr.operator, AssignmentOperator::Subtraction) {
|
||||
|
|
@ -354,7 +352,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
|||
|
||||
fn try_fold_new_expression(
|
||||
new_expr: &mut NewExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
// `new Object` -> `{}`
|
||||
if new_expr.arguments.is_empty()
|
||||
|
|
@ -413,7 +411,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
|||
|
||||
fn try_fold_literal_constructor_call_expression(
|
||||
call_expr: &mut CallExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
// `Object()` -> `{}`
|
||||
if call_expr.arguments.is_empty()
|
||||
|
|
@ -467,7 +465,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
|||
|
||||
fn try_fold_simple_function_call(
|
||||
call_expr: &mut CallExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Option<Expression<'a>> {
|
||||
if call_expr.optional || call_expr.arguments.len() != 1 {
|
||||
return None;
|
||||
|
|
@ -521,7 +519,7 @@ impl<'a> PeepholeSubstituteAlternateSyntax {
|
|||
fn try_fold_chain_call_expression(
|
||||
&mut self,
|
||||
call_expr: &mut CallExpression<'a>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) {
|
||||
// `window.Object?.()` -> `Object?.()`
|
||||
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
|
||||
fn array_constructor_call(
|
||||
arguments: Vec<'a, Argument<'a>>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Expression<'a> {
|
||||
let callee = ctx.ast.expression_identifier_reference(SPAN, "Array");
|
||||
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
|
||||
fn array_literal(
|
||||
elements: Vec<'a, ArrayExpressionElement<'a>>,
|
||||
ctx: &mut TraverseCtx<'a>,
|
||||
ctx: Ctx<'a, 'b>,
|
||||
) -> Expression<'a> {
|
||||
ctx.ast.expression_array(SPAN, elements, None)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,47 +3,61 @@ mod is_literal_value;
|
|||
mod may_have_side_effects;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::ops::Deref;
|
||||
|
||||
use num_bigint::BigInt;
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_ecmascript::{StringToBigInt, ToBigInt, ToBoolean, ToJsString, ToNumber};
|
||||
use oxc_semantic::{IsGlobalReference, ScopeTree, SymbolTable};
|
||||
use oxc_ecmascript::ConstantEvaluation;
|
||||
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};
|
||||
|
||||
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 {
|
||||
num.fract() == 0.0
|
||||
}
|
||||
|
||||
pub trait NodeUtil<'a> {
|
||||
fn symbols(&self) -> &SymbolTable;
|
||||
|
||||
#[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,
|
||||
}
|
||||
impl<'a, 'b> Ctx<'a, 'b> {
|
||||
fn symbols(&self) -> &SymbolTable {
|
||||
self.0.symbols()
|
||||
}
|
||||
|
||||
fn is_identifier_undefined(&self, ident: &IdentifierReference) -> bool {
|
||||
if ident.name == "undefined" && ident.is_global_reference(self.symbols()) {
|
||||
return true;
|
||||
}
|
||||
false
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
/// 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> {
|
||||
let value = self.get_number_value(expr);
|
||||
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
|
||||
|
|
@ -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)
|
||||
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);
|
||||
// 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
|
||||
|
|
@ -73,7 +104,7 @@ pub trait NodeUtil<'a> {
|
|||
/// 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`.
|
||||
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);
|
||||
// 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
|
||||
|
|
@ -85,22 +116,8 @@ pub trait NodeUtil<'a> {
|
|||
None
|
||||
}
|
||||
|
||||
// port from [closure compiler](https://github.com/google/closure-compiler/blob/a4c880032fba961f7a6c06ef99daa3641810bfdd/src/com/google/javascript/jscomp/NodeUtil.java#L109)
|
||||
// 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.
|
||||
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> {
|
||||
#[expect(clippy::unused_self)]
|
||||
pub fn get_bigint_value(self, expr: &Expression<'a>) -> Option<BigInt> {
|
||||
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
|
||||
/// String, this method effectively emulates the `String()` JavaScript cast function.
|
||||
/// 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()
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,14 +45,6 @@ impl Tri {
|
|||
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 {
|
||||
match self {
|
||||
Self::True => 1,
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ fn test(source_text: &str, expected: &str) {
|
|||
crate::test(&source_text, expected, options);
|
||||
}
|
||||
|
||||
// fn test_same(source_text: &str) {
|
||||
// test(source_text, source_text);
|
||||
// }
|
||||
fn test_same(source_text: &str) {
|
||||
test(source_text, source_text);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dce_if_statement() {
|
||||
|
|
@ -63,8 +63,7 @@ fn dce_if_statement() {
|
|||
|
||||
// Shadowed `undefined` as a variable should not be erased.
|
||||
// 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() { { bar } }", "function foo() { bar }");
|
||||
|
|
|
|||
Loading…
Reference in a new issue