refactor(minifier): unify ValueType (#6545)

This commit is contained in:
Boshen 2024-10-14 04:21:42 +00:00
parent 521d295830
commit 67ad08a056
4 changed files with 63 additions and 80 deletions

View file

@ -14,9 +14,9 @@ use oxc_syntax::{
use oxc_traverse::{Ancestor, Traverse, TraverseCtx}; use oxc_traverse::{Ancestor, Traverse, TraverseCtx};
use crate::{ use crate::{
node_util::{is_exact_int64, IsLiteralValue, MayHaveSideEffects, NodeUtil, ValueType}, node_util::{is_exact_int64, IsLiteralValue, MayHaveSideEffects, NodeUtil},
tri::Tri, tri::Tri,
ty::Ty, value_type::ValueType,
CompressorPass, CompressorPass,
}; };
@ -365,9 +365,9 @@ impl<'a> PeepholeFoldConstants {
) -> Option<Expression<'a>> { ) -> Option<Expression<'a>> {
debug_assert_eq!(logical_expr.operator, LogicalOperator::Coalesce); debug_assert_eq!(logical_expr.operator, LogicalOperator::Coalesce);
let left = &logical_expr.left; let left = &logical_expr.left;
let left_val = ctx.get_known_value_type(left); let left_val = ValueType::from(left);
match left_val { match left_val {
ValueType::Null | ValueType::Void => { ValueType::Null | ValueType::Undefined => {
Some(if left.may_have_side_effects() { Some(if left.may_have_side_effects() {
// e.g. `(a(), null) ?? 1` => `(a(), null, 1)` // e.g. `(a(), null) ?? 1` => `(a(), null, 1)`
let expressions = ctx.ast.vec_from_iter([ let expressions = ctx.ast.vec_from_iter([
@ -381,7 +381,7 @@ impl<'a> PeepholeFoldConstants {
}) })
} }
ValueType::Number ValueType::Number
| ValueType::Bigint | ValueType::BigInt
| ValueType::String | ValueType::String
| ValueType::Boolean | ValueType::Boolean
| ValueType::Object => { | ValueType::Object => {
@ -435,13 +435,13 @@ impl<'a> PeepholeFoldConstants {
return None; return None;
} }
let left_type = Ty::from(left); let left_type = ValueType::from(left);
let right_type = Ty::from(right); let right_type = ValueType::from(right);
match (left_type, right_type) { match (left_type, right_type) {
(Ty::Undetermined, _) | (_, Ty::Undetermined) => None, (ValueType::Undetermined, _) | (_, ValueType::Undetermined) => None,
// string concatenation // 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 // no need to use get_side_effect_free_string_value b/c we checked for side effects
// at the beginning // at the beginning
let left_string = ctx.get_string_value(left)?; let left_string = ctx.get_string_value(left)?;
@ -452,9 +452,9 @@ impl<'a> PeepholeFoldConstants {
}, },
// number addition // 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 // 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 left_number = ctx.get_number_value(left)?;
let right_number = ctx.get_number_value(right)?; let right_number = ctx.get_number_value(right)?;
let Ok(value) = TryInto::<f64>::try_into(left_number + right_number) else { return None }; let Ok(value) = TryInto::<f64>::try_into(left_number + right_number) else { return None };
@ -651,17 +651,22 @@ impl<'a> PeepholeFoldConstants {
right_expr: &'b Expression<'a>, right_expr: &'b Expression<'a>,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Tri { ) -> Tri {
let left = Ty::from(left_expr); let left = ValueType::from(left_expr);
let right = Ty::from(right_expr); let right = ValueType::from(right_expr);
if left != Ty::Undetermined && right != Ty::Undetermined { if left != ValueType::Undetermined && right != ValueType::Undetermined {
if left == right { if left == right {
return Self::try_strict_equality_comparison(left_expr, right_expr, ctx); 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; 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); let right_number = ctx.get_side_free_number_value(right_expr);
if let Some(num) = right_number { if let Some(num) = right_number {
@ -682,7 +687,9 @@ impl<'a> PeepholeFoldConstants {
return Tri::Unknown; 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); let left_number = ctx.get_side_free_number_value(left_expr);
if let Some(num) = left_number { if let Some(num) = left_number {
@ -703,7 +710,7 @@ impl<'a> PeepholeFoldConstants {
return Tri::Unknown; 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 left_bigint = ctx.get_side_free_bigint_value(left_expr);
let right_bigint = ctx.get_side_free_bigint_value(right_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; 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; return Tri::Unknown;
} }
@ -732,11 +743,11 @@ impl<'a> PeepholeFoldConstants {
will_negative: bool, will_negative: bool,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Tri { ) -> Tri {
let left = Ty::from(left_expr); let left = ValueType::from(left_expr);
let right = Ty::from(right_expr); let right = ValueType::from(right_expr);
// First, check for a string comparison. // 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 left_string = ctx.get_side_free_string_value(left_expr);
let right_string = ctx.get_side_free_string_value(right_expr); let right_string = ctx.get_side_free_string_value(right_expr);
if let (Some(left_string), Some(right_string)) = (left_string, right_string) { if let (Some(left_string), Some(right_string)) = (left_string, right_string) {
@ -842,15 +853,15 @@ impl<'a> PeepholeFoldConstants {
right_expr: &'b Expression<'a>, right_expr: &'b Expression<'a>,
ctx: &mut TraverseCtx<'a>, ctx: &mut TraverseCtx<'a>,
) -> Tri { ) -> Tri {
let left = Ty::from(left_expr); let left = ValueType::from(left_expr);
let right = Ty::from(right_expr); let right = ValueType::from(right_expr);
if left != Ty::Undetermined && right != Ty::Undetermined { if left != ValueType::Undetermined && right != ValueType::Undetermined {
// Strict equality can only be true for values of the same type. // Strict equality can only be true for values of the same type.
if left != right { if left != right {
return Tri::False; return Tri::False;
} }
return match left { return match left {
Ty::Number => { ValueType::Number => {
let left_number = ctx.get_side_free_number_value(left_expr); let left_number = ctx.get_side_free_number_value(left_expr);
let right_number = ctx.get_side_free_number_value(right_expr); let right_number = ctx.get_side_free_number_value(right_expr);
@ -864,7 +875,7 @@ impl<'a> PeepholeFoldConstants {
Tri::Unknown Tri::Unknown
} }
Ty::Str => { ValueType::String => {
let left_string = ctx.get_side_free_string_value(left_expr); let left_string = ctx.get_side_free_string_value(left_expr);
let right_string = ctx.get_side_free_string_value(right_expr); let right_string = ctx.get_side_free_string_value(right_expr);
if let (Some(left_string), Some(right_string)) = (left_string, right_string) { if let (Some(left_string), Some(right_string)) = (left_string, right_string) {
@ -895,7 +906,7 @@ impl<'a> PeepholeFoldConstants {
Tri::Unknown Tri::Unknown
} }
Ty::Void | Ty::Null => Tri::True, ValueType::Undefined | ValueType::Null => Tri::True,
_ => Tri::Unknown, _ => Tri::Unknown,
}; };
} }

View file

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

View file

@ -17,18 +17,6 @@ pub fn is_exact_int64(num: f64) -> bool {
num.fract() == 0.0 num.fract() == 0.0
} }
#[derive(Debug, Eq, PartialEq)]
pub enum ValueType {
Undetermined,
Null,
Void,
Number,
Bigint,
String,
Boolean,
Object,
}
pub trait NodeUtil<'a> { pub trait NodeUtil<'a> {
fn symbols(&self) -> &SymbolTable; fn symbols(&self) -> &SymbolTable;
@ -128,27 +116,4 @@ pub trait NodeUtil<'a> {
fn get_string_bigint_value(&self, raw_string: &str) -> Option<BigInt> { fn get_string_bigint_value(&self, raw_string: &str) -> Option<BigInt> {
raw_string.string_to_big_int() 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,
}
}
} }

View file

@ -5,18 +5,25 @@ use oxc_syntax::operator::{BinaryOperator, UnaryOperator};
/// ///
/// <https://tc39.es/ecma262/#sec-ecmascript-language-types> /// <https://tc39.es/ecma262/#sec-ecmascript-language-types>
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Ty { pub enum ValueType {
BigInt, Undefined, // a.k.a `Void` in closure compiler
Boolean,
Null, Null,
Number, Number,
BigInt,
String,
Boolean,
Object, Object,
Str,
Void,
Undetermined, 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 { fn from(expr: &Expression<'a>) -> Self {
// TODO: complete this // TODO: complete this
match expr { match expr {
@ -24,18 +31,18 @@ impl<'a> From<&Expression<'a>> for Ty {
Expression::BooleanLiteral(_) => Self::Boolean, Expression::BooleanLiteral(_) => Self::Boolean,
Expression::NullLiteral(_) => Self::Null, Expression::NullLiteral(_) => Self::Null,
Expression::NumericLiteral(_) => Self::Number, Expression::NumericLiteral(_) => Self::Number,
Expression::StringLiteral(_) => Self::Str, Expression::StringLiteral(_) => Self::String,
Expression::ObjectExpression(_) Expression::ObjectExpression(_)
| Expression::ArrayExpression(_) | Expression::ArrayExpression(_)
| Expression::RegExpLiteral(_) | Expression::RegExpLiteral(_)
| Expression::FunctionExpression(_) => Self::Object, | Expression::FunctionExpression(_) => Self::Object,
Expression::Identifier(ident) => match ident.name.as_str() { Expression::Identifier(ident) => match ident.name.as_str() {
"undefined" => Self::Void, "undefined" => Self::Undefined,
"NaN" | "Infinity" => Self::Number, "NaN" | "Infinity" => Self::Number,
_ => Self::Undetermined, _ => Self::Undetermined,
}, },
Expression::UnaryExpression(unary_expr) => match unary_expr.operator { Expression::UnaryExpression(unary_expr) => match unary_expr.operator {
UnaryOperator::Void => Self::Void, UnaryOperator::Void => Self::Undefined,
UnaryOperator::UnaryNegation => { UnaryOperator::UnaryNegation => {
let argument_ty = Self::from(&unary_expr.argument); let argument_ty = Self::from(&unary_expr.argument);
if argument_ty == Self::BigInt { if argument_ty == Self::BigInt {
@ -45,29 +52,29 @@ impl<'a> From<&Expression<'a>> for Ty {
} }
UnaryOperator::UnaryPlus => Self::Number, UnaryOperator::UnaryPlus => Self::Number,
UnaryOperator::LogicalNot => Self::Boolean, UnaryOperator::LogicalNot => Self::Boolean,
UnaryOperator::Typeof => Self::Str, UnaryOperator::Typeof => Self::String,
_ => Self::Undetermined, _ => Self::Undetermined,
}, },
Expression::BinaryExpression(binary_expr) => match binary_expr.operator { Expression::BinaryExpression(binary_expr) => match binary_expr.operator {
BinaryOperator::Addition => { BinaryOperator::Addition => {
let left_ty = Self::from(&binary_expr.left); let left_ty = Self::from(&binary_expr.left);
let right_ty = Self::from(&binary_expr.right); let right_ty = Self::from(&binary_expr.right);
if left_ty == Self::String || right_ty == Self::String {
if left_ty == Self::Str || right_ty == Self::Str { return Self::String;
return Self::Str;
} }
// There are some pretty weird cases for object types: // There are some pretty weird cases for object types:
// {} + [] === "0" // {} + [] === "0"
// [] + {} === "[object Object]" // [] + {} === "[object Object]"
if left_ty == Self::Object || right_ty == Self::Object { if left_ty == Self::Object || right_ty == Self::Object {
return Self::Undetermined; return Self::Undetermined;
} }
Self::Undetermined Self::Undetermined
} }
_ => Self::Undetermined, _ => Self::Undetermined,
}, },
Expression::SequenceExpression(e) => {
e.expressions.last().map_or(ValueType::Undetermined, Self::from)
}
_ => Self::Undetermined, _ => Self::Undetermined,
} }
} }