feat(ecmascript): add constant_evaluation and side_effects code (#6550)

I intend to move most of the constant evaluation code into the crate.
This commit is contained in:
Boshen 2024-10-15 05:15:46 +00:00
parent 77ddab863f
commit e56188037c
15 changed files with 475 additions and 201 deletions

View file

@ -23,7 +23,7 @@ doctest = false
[dependencies]
oxc_ast = { workspace = true }
oxc_span = { workspace = true }
oxc_syntax = { workspace = true }
oxc_syntax = { workspace = true, features = ["to_js_string"] }
num-bigint = { workspace = true }
num-traits = { workspace = true }

View file

@ -1,171 +0,0 @@
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,
}
}
}

View file

@ -1,3 +1,4 @@
#[allow(clippy::wildcard_imports)]
use oxc_ast::ast::*;
/// Returns true if this is a literal value. We define a literal value as any node that evaluates

View file

@ -0,0 +1,363 @@
mod is_litral_value;
mod r#type;
mod value;
use std::borrow::Cow;
use num_bigint::BigInt;
use num_traits::{One, Zero};
#[allow(clippy::wildcard_imports)]
use oxc_ast::ast::*;
use crate::{side_effects::MayHaveSideEffects, ToInt32, ToJsString};
pub use self::{is_litral_value::IsLiteralValue, r#type::ValueType, value::ConstantValue};
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<'a>> {
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 get_side_free_number_value(&self, expr: &Expression<'a>) -> Option<f64> {
let value = self.eval_to_number(expr);
// Calculating the number value, if any, is likely to be faster than calculating side effects,
// and there are only a very few cases where we can compute a number value, but there could
// also be side effects. e.g. `void doSomething()` has value NaN, regardless of the behavior
// of `doSomething()`
if value.is_some() && expr.may_have_side_effects() {
None
} else {
value
}
}
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::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_to_big_int(&self, expr: &Expression<'a>) -> Option<BigInt> {
match expr {
Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
UnaryOperator::UnaryPlus => self.eval_to_big_int(&unary_expr.argument),
UnaryOperator::UnaryNegation => {
self.eval_to_big_int(&unary_expr.argument).map(|v| -v)
}
_ => None,
},
Expression::BigIntLiteral(_) => {
use crate::ToBigInt;
expr.to_big_int()
}
_ => None,
}
}
fn eval_expression(&self, expr: &Expression<'a>) -> Option<ConstantValue<'a>> {
match expr {
Expression::BinaryExpression(e) => self.eval_binary_expression(e),
Expression::LogicalExpression(e) => self.eval_logical_expression(e),
Expression::UnaryExpression(e) => self.eval_unary_expression(e),
Expression::Identifier(ident) => self.resolve_binding(ident),
Expression::NumericLiteral(lit) => Some(ConstantValue::Number(lit.value)),
Expression::StringLiteral(lit) => {
Some(ConstantValue::String(Cow::Borrowed(lit.value.as_str())))
}
_ => None,
}
}
fn eval_binary_expression(&self, expr: &BinaryExpression<'a>) -> Option<ConstantValue<'a>> {
match expr.operator {
BinaryOperator::Addition => {
let left = &expr.left;
let right = &expr.right;
if left.may_have_side_effects() || right.may_have_side_effects() {
return None;
}
let left_type = ValueType::from(left);
let right_type = ValueType::from(right);
if left_type.is_string() || right_type.is_string() {
let lval = self.eval_expression(&expr.left)?;
let rval = self.eval_expression(&expr.right)?;
let lstr = lval.to_js_string()?;
let rstr = rval.to_js_string()?;
return Some(ConstantValue::String(lstr + rstr));
}
if left_type.is_number() || right_type.is_number() {
let lval = self.eval_expression(&expr.left)?;
let rval = self.eval_expression(&expr.right)?;
let lnum = lval.into_number()?;
let rnum = rval.into_number()?;
return Some(ConstantValue::Number(lnum + rnum));
}
None
}
BinaryOperator::Subtraction
| BinaryOperator::Division
| BinaryOperator::Remainder
| BinaryOperator::Multiplication
| BinaryOperator::Exponential => {
let lval = self.eval_to_number(&expr.left)?;
let rval = self.eval_to_number(&expr.right)?;
let val = match expr.operator {
BinaryOperator::Subtraction => lval - rval,
BinaryOperator::Division => {
if rval.is_zero() {
if lval.is_sign_positive() {
f64::INFINITY
} else {
f64::NEG_INFINITY
}
} else {
lval / rval
}
}
BinaryOperator::Remainder => {
if !rval.is_zero() && rval.is_finite() {
lval % rval
} else if rval.is_infinite() {
f64::NAN
} else {
return None;
}
}
BinaryOperator::Multiplication => lval * rval,
BinaryOperator::Exponential => lval.powf(rval),
_ => unreachable!(),
};
Some(ConstantValue::Number(val))
}
#[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
BinaryOperator::ShiftLeft
| BinaryOperator::ShiftRight
| BinaryOperator::ShiftRightZeroFill => {
let left_num = self.get_side_free_number_value(&expr.left);
let right_num = self.get_side_free_number_value(&expr.right);
if let (Some(left_val), Some(right_val)) = (left_num, right_num) {
if left_val.fract() != 0.0 || right_val.fract() != 0.0 {
return None;
}
// only the lower 5 bits are used when shifting, so don't do anything
// if the shift amount is outside [0,32)
if !(0.0..32.0).contains(&right_val) {
return None;
}
let right_val_int = right_val as u32;
let bits = left_val.to_int_32();
let result_val: f64 = match expr.operator {
BinaryOperator::ShiftLeft => f64::from(bits.wrapping_shl(right_val_int)),
BinaryOperator::ShiftRight => f64::from(bits.wrapping_shr(right_val_int)),
BinaryOperator::ShiftRightZeroFill => {
// JavaScript always treats the result of >>> as unsigned.
// We must force Rust to do the same here.
let bits = bits as u32;
let res = bits.wrapping_shr(right_val_int);
f64::from(res)
}
_ => unreachable!(),
};
return Some(ConstantValue::Number(result_val));
}
None
}
_ => None,
}
}
fn eval_logical_expression(&self, expr: &LogicalExpression<'a>) -> Option<ConstantValue<'a>> {
match expr.operator {
LogicalOperator::And => {
if self.eval_to_boolean(&expr.left) == Some(true) {
self.eval_expression(&expr.right)
} else {
self.eval_expression(&expr.left)
}
}
_ => None,
}
}
fn eval_unary_expression(&self, expr: &UnaryExpression<'a>) -> Option<ConstantValue<'a>> {
match expr.operator {
UnaryOperator::Typeof => {
if !expr.argument.is_literal_value(true) {
return None;
}
let s = match &expr.argument {
Expression::FunctionExpression(_) => "function",
Expression::StringLiteral(_) => "string",
Expression::NumericLiteral(_) => "number",
Expression::BooleanLiteral(_) => "boolean",
Expression::NullLiteral(_)
| Expression::ObjectExpression(_)
| Expression::ArrayExpression(_) => "object",
Expression::UnaryExpression(e) if e.operator == UnaryOperator::Void => {
"undefined"
}
Expression::BigIntLiteral(_) => "bigint",
Expression::Identifier(ident) => match ident.name.as_str() {
"undefined" if self.is_global_reference(ident) => "undefined",
"NaN" | "Infinity" if self.is_global_reference(ident) => "number",
_ => return None,
},
_ => return None,
};
Some(ConstantValue::String(Cow::Borrowed(s)))
}
UnaryOperator::Void => {
if (!expr.argument.is_number() || !expr.argument.is_number_0())
&& !expr.may_have_side_effects()
{
return Some(ConstantValue::Undefined);
}
None
}
UnaryOperator::LogicalNot => {
// Don't fold !0 and !1 back to false.
if let Expression::NumericLiteral(n) = &expr.argument {
if n.value.is_zero() || n.value.is_one() {
return None;
}
}
self.eval_to_boolean(&expr.argument).map(|b| !b).map(ConstantValue::Boolean)
}
UnaryOperator::UnaryPlus => {
self.eval_to_number(&expr.argument).map(ConstantValue::Number)
}
UnaryOperator::UnaryNegation => {
let ty = ValueType::from(&expr.argument);
match ty {
ValueType::BigInt => {
self.eval_to_big_int(&expr.argument).map(|v| -v).map(ConstantValue::BigInt)
}
ValueType::Number => self
.eval_to_number(&expr.argument)
.map(|v| if v.is_nan() { v } else { -v })
.map(ConstantValue::Number),
_ => None,
}
}
UnaryOperator::BitwiseNot => {
let ty = ValueType::from(&expr.argument);
match ty {
ValueType::BigInt => {
self.eval_to_big_int(&expr.argument).map(|v| !v).map(ConstantValue::BigInt)
}
#[expect(clippy::cast_lossless)]
ValueType::Number => self
.eval_to_number(&expr.argument)
.map(|v| !v.to_int_32())
.map(|v| v as f64)
.map(ConstantValue::Number),
_ => None,
}
}
UnaryOperator::Delete => None,
}
}
}

View file

@ -1,4 +1,4 @@
use oxc_ast::ast::*;
use oxc_ast::ast::Expression;
use oxc_syntax::operator::{BinaryOperator, UnaryOperator};
/// JavaScript Language Type
@ -16,6 +16,16 @@ pub enum ValueType {
Undetermined,
}
impl ValueType {
pub fn is_string(self) -> bool {
matches!(self, Self::String)
}
pub fn is_number(self) -> bool {
matches!(self, Self::Number)
}
}
/// `get_known_value_type`
///
/// Evaluate and attempt to determine which primitive value type it could resolve to.

View file

@ -0,0 +1,73 @@
use std::borrow::Cow;
use num_bigint::BigInt;
use crate::ToJsString;
#[derive(Debug, PartialEq)]
pub enum ConstantValue<'a> {
Number(f64),
BigInt(BigInt),
String(Cow<'a, str>),
Boolean(bool),
Undefined,
}
impl<'a> ConstantValue<'a> {
pub fn is_number(&self) -> bool {
matches!(self, Self::Number(_))
}
pub fn is_big_int(&self) -> bool {
matches!(self, Self::BigInt(_))
}
pub fn is_string(&self) -> bool {
matches!(self, Self::String(_))
}
pub fn is_boolean(&self) -> bool {
matches!(self, Self::Boolean(_))
}
pub fn is_undefined(&self) -> bool {
matches!(self, Self::Undefined)
}
pub fn into_string(self) -> Option<Cow<'a, str>> {
match self {
Self::String(s) => Some(s),
_ => None,
}
}
pub fn into_number(self) -> Option<f64> {
match self {
Self::Number(s) => Some(s),
_ => None,
}
}
pub fn into_boolean(self) -> Option<bool> {
match self {
Self::Boolean(s) => Some(s),
_ => None,
}
}
}
impl<'a> ToJsString<'a> for ConstantValue<'a> {
fn to_js_string(&self) -> Option<Cow<'a, str>> {
match self {
Self::Number(n) => {
use oxc_syntax::number::ToJsString;
Some(Cow::Owned(n.to_js_string()))
}
// FIXME: to js number string
Self::BigInt(n) => Some(Cow::Owned(n.to_string() + "n")),
Self::String(s) => Some(s.clone()),
Self::Boolean(b) => Some(Cow::Borrowed(if *b { "true" } else { "false" })),
Self::Undefined => Some(Cow::Borrowed("undefined")),
}
}
}

View file

@ -18,21 +18,16 @@ mod to_number;
mod to_string;
// Constant Evaluation
mod constant_evaluation;
pub mod constant_evaluation;
// Side Effects
pub mod side_effects;
pub use self::{
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,
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,
to_string::ToJsString,
};

View file

@ -1,3 +1,4 @@
#[allow(clippy::wildcard_imports)]
use oxc_ast::ast::*;
use oxc_syntax::operator::UnaryOperator;

View file

@ -1,4 +1,4 @@
use oxc_ast::ast::*;
use oxc_ast::ast::{Expression, ForStatementLeft, UnaryExpression};
use super::check_for_state_change::CheckForStateChange;

View file

@ -0,0 +1,5 @@
mod check_for_state_change;
mod may_have_side_effects;
pub use check_for_state_change::CheckForStateChange;
pub use may_have_side_effects::MayHaveSideEffects;

View file

@ -6,6 +6,10 @@ use num_traits::Zero;
use oxc_ast::ast::*;
use oxc_ecmascript::ToInt32;
use oxc_ecmascript::{
constant_evaluation::{IsLiteralValue, ValueType},
side_effects::MayHaveSideEffects,
};
use oxc_span::{GetSpan, Span, SPAN};
use oxc_syntax::{
number::NumberBase,
@ -14,9 +18,8 @@ use oxc_syntax::{
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
use crate::{
node_util::{is_exact_int64, Ctx, IsLiteralValue, MayHaveSideEffects},
node_util::{is_exact_int64, Ctx},
tri::Tri,
value_type::ValueType,
CompressorPass,
};

View file

@ -1,10 +1,10 @@
use oxc_allocator::Vec;
use oxc_ast::{ast::*, Visit};
use oxc_ecmascript::ConstantEvaluation;
use oxc_ecmascript::constant_evaluation::{ConstantEvaluation, IsLiteralValue};
use oxc_span::SPAN;
use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
use crate::node_util::{Ctx, IsLiteralValue};
use crate::node_util::Ctx;
use crate::{keep_var::KeepVar, CompressorPass};
/// Remove Dead Code from the AST.

View file

@ -1,9 +1,10 @@
use oxc_allocator::Vec;
use oxc_ast::ast::*;
use oxc_ecmascript::side_effects::MayHaveSideEffects;
use oxc_span::SPAN;
use oxc_traverse::{Traverse, TraverseCtx};
use crate::{node_util::MayHaveSideEffects, CompressorPass};
use crate::CompressorPass;
/// Statement Fusion
///

View file

@ -8,7 +8,6 @@ mod keep_var;
mod node_util;
mod options;
mod tri;
mod value_type;
#[cfg(test)]
mod tester;

View file

@ -1,19 +1,13 @@
mod check_for_state_change;
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::ConstantEvaluation;
use oxc_ecmascript::{constant_evaluation::ConstantEvaluation, side_effects::MayHaveSideEffects};
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};
#[derive(Clone, Copy)]
pub struct Ctx<'a, 'b>(pub &'b TraverseCtx<'a>);