mirror of
https://github.com/danbulant/oxc
synced 2026-05-24 12:21:58 +00:00
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:
parent
77ddab863f
commit
e56188037c
15 changed files with 475 additions and 201 deletions
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
363
crates/oxc_ecmascript/src/constant_evaluation/mod.rs
Normal file
363
crates/oxc_ecmascript/src/constant_evaluation/mod.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.
|
||||
73
crates/oxc_ecmascript/src/constant_evaluation/value.rs
Normal file
73
crates/oxc_ecmascript/src/constant_evaluation/value.rs
Normal 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")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#[allow(clippy::wildcard_imports)]
|
||||
use oxc_ast::ast::*;
|
||||
use oxc_syntax::operator::UnaryOperator;
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use oxc_ast::ast::*;
|
||||
use oxc_ast::ast::{Expression, ForStatementLeft, UnaryExpression};
|
||||
|
||||
use super::check_for_state_change::CheckForStateChange;
|
||||
|
||||
5
crates/oxc_ecmascript/src/side_effects/mod.rs
Normal file
5
crates/oxc_ecmascript/src/side_effects/mod.rs
Normal 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;
|
||||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
///
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ mod keep_var;
|
|||
mod node_util;
|
||||
mod options;
|
||||
mod tri;
|
||||
mod value_type;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tester;
|
||||
|
|
|
|||
|
|
@ -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>);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue