diff --git a/crates/oxc_ecmascript/src/constant_evaluation/value_type.rs b/crates/oxc_ecmascript/src/constant_evaluation/value_type.rs index 504460c67ac8d..9ff470aa55e21 100644 --- a/crates/oxc_ecmascript/src/constant_evaluation/value_type.rs +++ b/crates/oxc_ecmascript/src/constant_evaluation/value_type.rs @@ -1,4 +1,7 @@ -use oxc_ast::ast::{BinaryExpression, ConditionalExpression, Expression, LogicalExpression}; +use oxc_ast::ast::{ + AssignmentExpression, AssignmentOperator, BinaryExpression, ConditionalExpression, Expression, + LogicalExpression, +}; use oxc_syntax::operator::{BinaryOperator, UnaryOperator}; /// JavaScript Language Type @@ -97,7 +100,7 @@ impl<'a> From<&Expression<'a>> for ValueType { Expression::SequenceExpression(e) => { e.expressions.last().map_or(ValueType::Undetermined, Self::from) } - Expression::AssignmentExpression(e) => Self::from(&e.right), + Expression::AssignmentExpression(e) => Self::from(&**e), Expression::ConditionalExpression(e) => Self::from(&**e), Expression::LogicalExpression(e) => Self::from(&**e), _ => Self::Undetermined, @@ -167,6 +170,45 @@ impl<'a> From<&BinaryExpression<'a>> for ValueType { } } +impl<'a> From<&AssignmentExpression<'a>> for ValueType { + fn from(e: &AssignmentExpression<'a>) -> Self { + match e.operator { + AssignmentOperator::Assign => Self::from(&e.right), + AssignmentOperator::Addition => { + let right = Self::from(&e.right); + if right.is_string() { + Self::String + } else { + Self::Undetermined + } + } + AssignmentOperator::Subtraction + | AssignmentOperator::Multiplication + | AssignmentOperator::Division + | AssignmentOperator::Remainder + | AssignmentOperator::ShiftLeft + | AssignmentOperator::BitwiseOR + | AssignmentOperator::ShiftRight + | AssignmentOperator::BitwiseXOR + | AssignmentOperator::BitwiseAnd + | AssignmentOperator::Exponential => { + let right = Self::from(&e.right); + if right.is_bigint() { + Self::BigInt + } else if !(right.is_object() || right.is_undetermined()) { + Self::Number + } else { + Self::Undetermined + } + } + AssignmentOperator::ShiftRightZeroFill => Self::Number, + AssignmentOperator::LogicalAnd + | AssignmentOperator::LogicalOr + | AssignmentOperator::LogicalNullish => Self::Undetermined, + } + } +} + impl<'a> From<&ConditionalExpression<'a>> for ValueType { fn from(e: &ConditionalExpression<'a>) -> Self { let left = Self::from(&e.consequent); diff --git a/crates/oxc_minifier/tests/ecmascript/value_type.rs b/crates/oxc_minifier/tests/ecmascript/value_type.rs index d638e5b348efb..9fb5528637926 100644 --- a/crates/oxc_minifier/tests/ecmascript/value_type.rs +++ b/crates/oxc_minifier/tests/ecmascript/value_type.rs @@ -149,8 +149,32 @@ fn assignment_tests() { test("a = 1n", ValueType::BigInt); test("a = foo", ValueType::Undetermined); - // test("a += 1", ValueType::Undetermined); - // test("a += 1n", ValueType::Undetermined); + test("a += ''", ValueType::String); + // if `a` is a string, the result is string + // if `a` is an object, the result can be string or number or bigint + // if `a` is not a string nor an object, the result can be number or bigint + test("a += 1", ValueType::Undetermined); + test("a += 1n", ValueType::Undetermined); + + test("a -= 1", ValueType::Number); // an error is thrown if `a` is converted to bigint + test("a -= undefined", ValueType::Number); + test("a -= null", ValueType::Number); + test("a -= ''", ValueType::Number); + test("a -= true", ValueType::Number); + test("a -= 1n", ValueType::BigInt); // an error is thrown if `a` is converted to number + test("a -= {}", ValueType::Undetermined); // number or bigint + test("a *= 1", ValueType::Number); + test("a /= 1", ValueType::Number); + test("a %= 1", ValueType::Number); + test("a <<= 1", ValueType::Number); + test("a |= 1", ValueType::Number); + test("a >>= 1", ValueType::Number); + test("a ^= 1", ValueType::Number); + test("a &= 1", ValueType::Number); + test("a **= 1", ValueType::Number); + + test("a >>>= 1", ValueType::Number); + test("a >>>= 1n", ValueType::Number); // throws an error } #[test]