diff --git a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs index 73fb7fefa1366..8a5285b4a321d 100644 --- a/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs +++ b/crates/oxc_minifier/src/ast_passes/peephole_fold_constants.rs @@ -14,9 +14,9 @@ use oxc_syntax::{ use oxc_traverse::{Ancestor, Traverse, TraverseCtx}; use crate::{ - node_util::{is_exact_int64, IsLiteralValue, MayHaveSideEffects, NodeUtil, ValueType}, + node_util::{is_exact_int64, IsLiteralValue, MayHaveSideEffects, NodeUtil}, tri::Tri, - ty::Ty, + value_type::ValueType, CompressorPass, }; @@ -365,9 +365,9 @@ impl<'a> PeepholeFoldConstants { ) -> Option> { debug_assert_eq!(logical_expr.operator, LogicalOperator::Coalesce); let left = &logical_expr.left; - let left_val = ctx.get_known_value_type(left); + let left_val = ValueType::from(left); match left_val { - ValueType::Null | ValueType::Void => { + ValueType::Null | ValueType::Undefined => { Some(if left.may_have_side_effects() { // e.g. `(a(), null) ?? 1` => `(a(), null, 1)` let expressions = ctx.ast.vec_from_iter([ @@ -381,7 +381,7 @@ impl<'a> PeepholeFoldConstants { }) } ValueType::Number - | ValueType::Bigint + | ValueType::BigInt | ValueType::String | ValueType::Boolean | ValueType::Object => { @@ -435,13 +435,13 @@ impl<'a> PeepholeFoldConstants { return None; } - let left_type = Ty::from(left); - let right_type = Ty::from(right); + let left_type = ValueType::from(left); + let right_type = ValueType::from(right); match (left_type, right_type) { - (Ty::Undetermined, _) | (_, Ty::Undetermined) => None, + (ValueType::Undetermined, _) | (_, ValueType::Undetermined) => None, // string concatenation - (Ty::Str, _) | (_, Ty::Str) => { + (ValueType::String, _) | (_, ValueType::String) => { // no need to use get_side_effect_free_string_value b/c we checked for side effects // at the beginning let left_string = ctx.get_string_value(left)?; @@ -452,9 +452,9 @@ impl<'a> PeepholeFoldConstants { }, // number addition - (Ty::Number, _) | (_, Ty::Number) + (ValueType::Number, _) | (_, ValueType::Number) // when added, booleans get treated as numbers where `true` is 1 and `false` is 0 - | (Ty::Boolean, Ty::Boolean) => { + | (ValueType::Boolean, ValueType::Boolean) => { let left_number = ctx.get_number_value(left)?; let right_number = ctx.get_number_value(right)?; let Ok(value) = TryInto::::try_into(left_number + right_number) else { return None }; @@ -651,17 +651,22 @@ impl<'a> PeepholeFoldConstants { right_expr: &'b Expression<'a>, ctx: &mut TraverseCtx<'a>, ) -> Tri { - let left = Ty::from(left_expr); - let right = Ty::from(right_expr); - if left != Ty::Undetermined && right != Ty::Undetermined { + let left = ValueType::from(left_expr); + let right = ValueType::from(right_expr); + if left != ValueType::Undetermined && right != ValueType::Undetermined { if left == right { return Self::try_strict_equality_comparison(left_expr, right_expr, ctx); } - if matches!((left, right), (Ty::Null, Ty::Void) | (Ty::Void, Ty::Null)) { + if matches!( + (left, right), + (ValueType::Null, ValueType::Undefined) | (ValueType::Undefined, ValueType::Null) + ) { return Tri::True; } - if matches!((left, right), (Ty::Number, Ty::Str)) || matches!(right, Ty::Boolean) { + if matches!((left, right), (ValueType::Number, ValueType::String)) + || matches!(right, ValueType::Boolean) + { let right_number = ctx.get_side_free_number_value(right_expr); if let Some(num) = right_number { @@ -682,7 +687,9 @@ impl<'a> PeepholeFoldConstants { return Tri::Unknown; } - if matches!((left, right), (Ty::Str, Ty::Number)) || matches!(left, Ty::Boolean) { + if matches!((left, right), (ValueType::String, ValueType::Number)) + || matches!(left, ValueType::Boolean) + { let left_number = ctx.get_side_free_number_value(left_expr); if let Some(num) = left_number { @@ -703,7 +710,7 @@ impl<'a> PeepholeFoldConstants { return Tri::Unknown; } - if matches!(left, Ty::BigInt) || matches!(right, Ty::BigInt) { + if matches!(left, ValueType::BigInt) || matches!(right, ValueType::BigInt) { let left_bigint = ctx.get_side_free_bigint_value(left_expr); let right_bigint = ctx.get_side_free_bigint_value(right_expr); @@ -712,11 +719,15 @@ impl<'a> PeepholeFoldConstants { } } - if matches!(left, Ty::Str | Ty::Number) && matches!(right, Ty::Object) { + if matches!(left, ValueType::String | ValueType::Number) + && matches!(right, ValueType::Object) + { return Tri::Unknown; } - if matches!(left, Ty::Object) && matches!(right, Ty::Str | Ty::Number) { + if matches!(left, ValueType::Object) + && matches!(right, ValueType::String | ValueType::Number) + { return Tri::Unknown; } @@ -732,11 +743,11 @@ impl<'a> PeepholeFoldConstants { will_negative: bool, ctx: &mut TraverseCtx<'a>, ) -> Tri { - let left = Ty::from(left_expr); - let right = Ty::from(right_expr); + let left = ValueType::from(left_expr); + let right = ValueType::from(right_expr); // First, check for a string comparison. - if left == Ty::Str && right == Ty::Str { + if left == ValueType::String && right == ValueType::String { let left_string = ctx.get_side_free_string_value(left_expr); let right_string = ctx.get_side_free_string_value(right_expr); if let (Some(left_string), Some(right_string)) = (left_string, right_string) { @@ -842,15 +853,15 @@ impl<'a> PeepholeFoldConstants { right_expr: &'b Expression<'a>, ctx: &mut TraverseCtx<'a>, ) -> Tri { - let left = Ty::from(left_expr); - let right = Ty::from(right_expr); - if left != Ty::Undetermined && right != Ty::Undetermined { + let left = ValueType::from(left_expr); + let right = ValueType::from(right_expr); + if left != ValueType::Undetermined && right != ValueType::Undetermined { // Strict equality can only be true for values of the same type. if left != right { return Tri::False; } return match left { - Ty::Number => { + ValueType::Number => { let left_number = ctx.get_side_free_number_value(left_expr); let right_number = ctx.get_side_free_number_value(right_expr); @@ -864,7 +875,7 @@ impl<'a> PeepholeFoldConstants { Tri::Unknown } - Ty::Str => { + ValueType::String => { let left_string = ctx.get_side_free_string_value(left_expr); let right_string = ctx.get_side_free_string_value(right_expr); if let (Some(left_string), Some(right_string)) = (left_string, right_string) { @@ -895,7 +906,7 @@ impl<'a> PeepholeFoldConstants { Tri::Unknown } - Ty::Void | Ty::Null => Tri::True, + ValueType::Undefined | ValueType::Null => Tri::True, _ => Tri::Unknown, }; } diff --git a/crates/oxc_minifier/src/lib.rs b/crates/oxc_minifier/src/lib.rs index d2041e3cbf89c..241121a572e7f 100644 --- a/crates/oxc_minifier/src/lib.rs +++ b/crates/oxc_minifier/src/lib.rs @@ -8,7 +8,7 @@ mod keep_var; mod node_util; mod options; mod tri; -mod ty; +mod value_type; #[cfg(test)] mod tester; diff --git a/crates/oxc_minifier/src/node_util/mod.rs b/crates/oxc_minifier/src/node_util/mod.rs index f01a90ccb4b90..ccb8e20025edb 100644 --- a/crates/oxc_minifier/src/node_util/mod.rs +++ b/crates/oxc_minifier/src/node_util/mod.rs @@ -17,18 +17,6 @@ pub fn is_exact_int64(num: f64) -> bool { num.fract() == 0.0 } -#[derive(Debug, Eq, PartialEq)] -pub enum ValueType { - Undetermined, - Null, - Void, - Number, - Bigint, - String, - Boolean, - Object, -} - pub trait NodeUtil<'a> { fn symbols(&self) -> &SymbolTable; @@ -128,27 +116,4 @@ pub trait NodeUtil<'a> { fn get_string_bigint_value(&self, raw_string: &str) -> Option { raw_string.string_to_big_int() } - - /// Evaluate and attempt to determine which primitive value type it could resolve to. - /// Without proper type information some assumptions had to be made for operations that could - /// result in a BigInt or a Number. If there is not enough information available to determine one - /// or the other then we assume Number in order to maintain historical behavior of the compiler and - /// avoid breaking projects that relied on this behavior. - fn get_known_value_type(&self, e: &Expression<'_>) -> ValueType { - match e { - Expression::NumericLiteral(_) => ValueType::Number, - Expression::NullLiteral(_) => ValueType::Null, - Expression::ArrayExpression(_) | Expression::ObjectExpression(_) => ValueType::Object, - Expression::BooleanLiteral(_) => ValueType::Boolean, - Expression::Identifier(ident) if self.is_identifier_undefined(ident) => ValueType::Void, - Expression::SequenceExpression(e) => e - .expressions - .last() - .map_or(ValueType::Undetermined, |e| self.get_known_value_type(e)), - Expression::BigIntLiteral(_) => ValueType::Bigint, - Expression::StringLiteral(_) | Expression::TemplateLiteral(_) => ValueType::String, - // TODO: complete this - _ => ValueType::Undetermined, - } - } } diff --git a/crates/oxc_minifier/src/ty.rs b/crates/oxc_minifier/src/value_type.rs similarity index 68% rename from crates/oxc_minifier/src/ty.rs rename to crates/oxc_minifier/src/value_type.rs index 445a42e1e06ba..24e4ae34e1c55 100644 --- a/crates/oxc_minifier/src/ty.rs +++ b/crates/oxc_minifier/src/value_type.rs @@ -5,18 +5,25 @@ use oxc_syntax::operator::{BinaryOperator, UnaryOperator}; /// /// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Ty { - BigInt, - Boolean, +pub enum ValueType { + Undefined, // a.k.a `Void` in closure compiler Null, Number, + BigInt, + String, + Boolean, Object, - Str, - Void, Undetermined, } -impl<'a> From<&Expression<'a>> for Ty { +/// `get_known_value_type` +/// +/// Evaluate and attempt to determine which primitive value type it could resolve to. +/// Without proper type information some assumptions had to be made for operations that could +/// result in a BigInt or a Number. If there is not enough information available to determine one +/// or the other then we assume Number in order to maintain historical behavior of the compiler and +/// avoid breaking projects that relied on this behavior. +impl<'a> From<&Expression<'a>> for ValueType { fn from(expr: &Expression<'a>) -> Self { // TODO: complete this match expr { @@ -24,18 +31,18 @@ impl<'a> From<&Expression<'a>> for Ty { Expression::BooleanLiteral(_) => Self::Boolean, Expression::NullLiteral(_) => Self::Null, Expression::NumericLiteral(_) => Self::Number, - Expression::StringLiteral(_) => Self::Str, + Expression::StringLiteral(_) => Self::String, Expression::ObjectExpression(_) | Expression::ArrayExpression(_) | Expression::RegExpLiteral(_) | Expression::FunctionExpression(_) => Self::Object, Expression::Identifier(ident) => match ident.name.as_str() { - "undefined" => Self::Void, + "undefined" => Self::Undefined, "NaN" | "Infinity" => Self::Number, _ => Self::Undetermined, }, Expression::UnaryExpression(unary_expr) => match unary_expr.operator { - UnaryOperator::Void => Self::Void, + UnaryOperator::Void => Self::Undefined, UnaryOperator::UnaryNegation => { let argument_ty = Self::from(&unary_expr.argument); if argument_ty == Self::BigInt { @@ -45,29 +52,29 @@ impl<'a> From<&Expression<'a>> for Ty { } UnaryOperator::UnaryPlus => Self::Number, UnaryOperator::LogicalNot => Self::Boolean, - UnaryOperator::Typeof => Self::Str, + UnaryOperator::Typeof => Self::String, _ => Self::Undetermined, }, Expression::BinaryExpression(binary_expr) => match binary_expr.operator { BinaryOperator::Addition => { let left_ty = Self::from(&binary_expr.left); let right_ty = Self::from(&binary_expr.right); - - if left_ty == Self::Str || right_ty == Self::Str { - return Self::Str; + if left_ty == Self::String || right_ty == Self::String { + return Self::String; } - // There are some pretty weird cases for object types: // {} + [] === "0" // [] + {} === "[object Object]" if left_ty == Self::Object || right_ty == Self::Object { return Self::Undetermined; } - Self::Undetermined } _ => Self::Undetermined, }, + Expression::SequenceExpression(e) => { + e.expressions.last().map_or(ValueType::Undetermined, Self::from) + } _ => Self::Undetermined, } }