oxc_ecmascript/constant_evaluation/
mod.rs

1mod call_expr;
2mod equality_comparison;
3mod is_int32_or_uint32;
4mod is_literal_value;
5mod url_encoding;
6mod value;
7mod value_type;
8
9pub use is_int32_or_uint32::IsInt32OrUint32;
10pub use is_literal_value::IsLiteralValue;
11pub use value::ConstantValue;
12pub use value_type::{DetermineValueType, ValueType};
13
14use std::borrow::Cow;
15
16use num_bigint::BigInt;
17use num_traits::{ToPrimitive, Zero};
18use oxc_ast::{AstBuilder, ast::*};
19
20use equality_comparison::{abstract_equality_comparison, strict_equality_comparison};
21
22use crate::{
23    ToBigInt, ToBoolean, ToInt32, ToJsString as ToJsStringTrait, ToNumber, ToUint32,
24    is_less_than::is_less_than,
25    side_effects::{MayHaveSideEffects, MayHaveSideEffectsContext},
26    to_numeric::ToNumeric,
27};
28
29pub trait ConstantEvaluationCtx<'a>: MayHaveSideEffectsContext<'a> {
30    fn ast(&self) -> AstBuilder<'a>;
31}
32
33pub trait ConstantEvaluation<'a>: MayHaveSideEffects<'a> {
34    /// Evaluate the expression to a constant value.
35    ///
36    /// Use the specific functions (e.g. [`ConstantEvaluation::evaluate_value_to_boolean`], [`ConstantEvaluation::evaluate_value`]).
37    ///
38    /// - target_ty: How the result will be used.
39    ///   For example, if the result will be converted to a boolean,
40    ///   passing `Some(ValueType::Boolean)` will allow us to utilize that information.
41    fn evaluate_value_to(
42        &self,
43        ctx: &impl ConstantEvaluationCtx<'a>,
44        target_ty: Option<ValueType>,
45    ) -> Option<ConstantValue<'a>>;
46
47    /// Evaluate the expression to a constant value.
48    ///
49    /// If you know the result will be converted to a specific type, use other functions (e.g. [`ConstantEvaluation::evaluate_value_to_boolean`]).
50    fn evaluate_value(&self, ctx: &impl ConstantEvaluationCtx<'a>) -> Option<ConstantValue<'a>> {
51        self.evaluate_value_to(ctx, None)
52    }
53
54    /// Evaluate the expression to a constant value and convert it to a number.
55    fn evaluate_value_to_number(&self, ctx: &impl ConstantEvaluationCtx<'a>) -> Option<f64> {
56        self.evaluate_value_to(ctx, Some(ValueType::Number))?.to_number(ctx)
57    }
58
59    /// Evaluate the expression to a constant value and convert it to a bigint.
60    fn evaluate_value_to_bigint(&self, ctx: &impl ConstantEvaluationCtx<'a>) -> Option<BigInt> {
61        self.evaluate_value_to(ctx, Some(ValueType::BigInt))?.into_bigint()
62    }
63
64    /// Evaluate the expression to a constant value and convert it to a boolean.
65    fn evaluate_value_to_boolean(&self, ctx: &impl ConstantEvaluationCtx<'a>) -> Option<bool> {
66        self.evaluate_value_to(ctx, Some(ValueType::Boolean))?.to_boolean(ctx)
67    }
68
69    /// Evaluate the expression to a constant value and convert it to a string.
70    fn evaluate_value_to_string(
71        &self,
72        ctx: &impl ConstantEvaluationCtx<'a>,
73    ) -> Option<Cow<'a, str>> {
74        self.evaluate_value_to(ctx, Some(ValueType::String))?.to_js_string(ctx)
75    }
76
77    fn get_side_free_number_value(&self, ctx: &impl ConstantEvaluationCtx<'a>) -> Option<f64> {
78        let value = self.evaluate_value_to_number(ctx)?;
79        // Calculating the number value, if any, is likely to be faster than calculating side effects,
80        // and there are only a very few cases where we can compute a number value, but there could
81        // also be side effects. e.g. `void doSomething()` has value NaN, regardless of the behavior
82        // of `doSomething()`
83        (!self.may_have_side_effects(ctx)).then_some(value)
84    }
85
86    fn get_side_free_bigint_value(&self, ctx: &impl ConstantEvaluationCtx<'a>) -> Option<BigInt> {
87        let value = self.evaluate_value_to_bigint(ctx)?;
88        (!self.may_have_side_effects(ctx)).then_some(value)
89    }
90
91    fn get_side_free_boolean_value(&self, ctx: &impl ConstantEvaluationCtx<'a>) -> Option<bool> {
92        let value = self.evaluate_value_to_boolean(ctx)?;
93        (!self.may_have_side_effects(ctx)).then_some(value)
94    }
95
96    fn get_side_free_string_value(
97        &self,
98        ctx: &impl ConstantEvaluationCtx<'a>,
99    ) -> Option<Cow<'a, str>> {
100        let value = self.evaluate_value_to_string(ctx)?;
101        (!self.may_have_side_effects(ctx)).then_some(value)
102    }
103}
104
105impl<'a, T: ConstantEvaluation<'a>> ConstantEvaluation<'a> for Option<T> {
106    fn evaluate_value(&self, ctx: &impl ConstantEvaluationCtx<'a>) -> Option<ConstantValue<'a>> {
107        self.as_ref().and_then(|t| t.evaluate_value(ctx))
108    }
109
110    fn evaluate_value_to(
111        &self,
112        ctx: &impl ConstantEvaluationCtx<'a>,
113        target_ty: Option<ValueType>,
114    ) -> Option<ConstantValue<'a>> {
115        self.as_ref().and_then(|t| t.evaluate_value_to(ctx, target_ty))
116    }
117}
118
119impl<'a> ConstantEvaluation<'a> for IdentifierReference<'a> {
120    fn evaluate_value_to(
121        &self,
122        ctx: &impl ConstantEvaluationCtx<'a>,
123        _target_ty: Option<ValueType>,
124    ) -> Option<ConstantValue<'a>> {
125        match self.name.as_str() {
126            "undefined" if ctx.is_global_reference(self) => Some(ConstantValue::Undefined),
127            "NaN" if ctx.is_global_reference(self) => Some(ConstantValue::Number(f64::NAN)),
128            "Infinity" if ctx.is_global_reference(self) => {
129                Some(ConstantValue::Number(f64::INFINITY))
130            }
131            _ => self
132                .reference_id
133                .get()
134                .and_then(|reference_id| ctx.get_constant_value_for_reference_id(reference_id)),
135        }
136    }
137}
138
139impl<'a> ConstantEvaluation<'a> for Expression<'a> {
140    fn evaluate_value_to(
141        &self,
142        ctx: &impl ConstantEvaluationCtx<'a>,
143        target_ty: Option<ValueType>,
144    ) -> Option<ConstantValue<'a>> {
145        let result = match target_ty {
146            Some(ValueType::Boolean) => self.to_boolean(ctx).map(ConstantValue::Boolean),
147            Some(ValueType::Number) => self.to_number(ctx).map(ConstantValue::Number),
148            Some(ValueType::BigInt) => self.to_big_int(ctx).map(ConstantValue::BigInt),
149            Some(ValueType::String) => self.to_js_string(ctx).map(ConstantValue::String),
150            _ => None,
151        };
152        if result.is_some() {
153            return result;
154        }
155
156        match self {
157            Expression::BinaryExpression(e) => e.evaluate_value_to(ctx, target_ty),
158            Expression::LogicalExpression(e) => e.evaluate_value_to(ctx, target_ty),
159            Expression::UnaryExpression(e) => e.evaluate_value_to(ctx, target_ty),
160            Expression::Identifier(ident) => ident.evaluate_value_to(ctx, target_ty),
161            Expression::NumericLiteral(lit) => Some(ConstantValue::Number(lit.value)),
162            Expression::NullLiteral(_) => Some(ConstantValue::Null),
163            Expression::BooleanLiteral(lit) => Some(ConstantValue::Boolean(lit.value)),
164            Expression::BigIntLiteral(lit) => lit.to_big_int(ctx).map(ConstantValue::BigInt),
165            Expression::StringLiteral(lit) => {
166                Some(ConstantValue::String(Cow::Borrowed(lit.value.as_str())))
167            }
168            Expression::StaticMemberExpression(e) => e.evaluate_value_to(ctx, target_ty),
169            Expression::ComputedMemberExpression(e) => e.evaluate_value_to(ctx, target_ty),
170            Expression::CallExpression(e) => e.evaluate_value_to(ctx, target_ty),
171            Expression::SequenceExpression(e) => {
172                // For sequence expression, the value is the value of the RHS.
173                e.expressions.last().and_then(|e| e.evaluate_value_to(ctx, target_ty))
174            }
175            _ => None,
176        }
177    }
178}
179
180impl<'a> ConstantEvaluation<'a> for BinaryExpression<'a> {
181    fn evaluate_value_to(
182        &self,
183        ctx: &impl ConstantEvaluationCtx<'a>,
184        target_ty: Option<ValueType>,
185    ) -> Option<ConstantValue<'a>> {
186        // FIXME: skipped for now to avoid performance regression, can be removed
187        if target_ty == Some(ValueType::Boolean) {
188            return None;
189        }
190
191        binary_operation_evaluate_value_to(self.operator, &self.left, &self.right, ctx, target_ty)
192    }
193}
194
195pub fn binary_operation_evaluate_value<'a, Ctx: ConstantEvaluationCtx<'a>>(
196    operator: BinaryOperator,
197    left: &Expression<'a>,
198    right: &Expression<'a>,
199    ctx: &Ctx,
200) -> Option<ConstantValue<'a>> {
201    binary_operation_evaluate_value_to(operator, left, right, ctx, None)
202}
203
204fn binary_operation_evaluate_value_to<'a>(
205    operator: BinaryOperator,
206    left: &Expression<'a>,
207    right: &Expression<'a>,
208    ctx: &impl ConstantEvaluationCtx<'a>,
209    _target_ty: Option<ValueType>,
210) -> Option<ConstantValue<'a>> {
211    match operator {
212        BinaryOperator::Addition => {
213            use crate::to_primitive::ToPrimitive;
214            let left_to_primitive = left.to_primitive(ctx);
215            let right_to_primitive = right.to_primitive(ctx);
216            if left_to_primitive.is_string() == Some(true)
217                || right_to_primitive.is_string() == Some(true)
218            {
219                let lval = left.evaluate_value_to_string(ctx)?;
220                let rval = right.evaluate_value_to_string(ctx)?;
221                return Some(ConstantValue::String(lval + rval));
222            }
223            let left_to_numeric_type = left_to_primitive.to_numeric(ctx);
224            let right_to_numeric_type = right_to_primitive.to_numeric(ctx);
225            if left_to_numeric_type.is_number() || right_to_numeric_type.is_number() {
226                let lval = left.evaluate_value_to_number(ctx)?;
227                let rval = right.evaluate_value_to_number(ctx)?;
228                return Some(ConstantValue::Number(lval + rval));
229            }
230            if left_to_numeric_type.is_bigint() && right_to_numeric_type.is_bigint() {
231                let lval = left.evaluate_value_to_bigint(ctx)?;
232                let rval = right.evaluate_value_to_bigint(ctx)?;
233                return Some(ConstantValue::BigInt(lval + rval));
234            }
235            None
236        }
237        BinaryOperator::Subtraction => {
238            let lval = left.evaluate_value_to_number(ctx)?;
239            let rval = right.evaluate_value_to_number(ctx)?;
240            Some(ConstantValue::Number(lval - rval))
241        }
242        BinaryOperator::Division => {
243            let lval = left.evaluate_value_to_number(ctx)?;
244            let rval = right.evaluate_value_to_number(ctx)?;
245            Some(ConstantValue::Number(lval / rval))
246        }
247        BinaryOperator::Remainder => {
248            let lval = left.evaluate_value_to_number(ctx)?;
249            let rval = right.evaluate_value_to_number(ctx)?;
250            Some(ConstantValue::Number(if rval.is_zero() { f64::NAN } else { lval % rval }))
251        }
252        BinaryOperator::Multiplication => {
253            let lval = left.evaluate_value_to_number(ctx)?;
254            let rval = right.evaluate_value_to_number(ctx)?;
255            Some(ConstantValue::Number(lval * rval))
256        }
257        BinaryOperator::Exponential => {
258            let lval = left.evaluate_value_to_number(ctx)?;
259            let rval = right.evaluate_value_to_number(ctx)?;
260            let result = lval.powf(rval);
261            // For now, ignore the result if it large or has a decimal part
262            // so that the output does not become bigger than the input.
263            if result.is_finite() && (result.fract() != 0.0 || result.log10() > 4.0) {
264                return None;
265            }
266            Some(ConstantValue::Number(result))
267        }
268        BinaryOperator::ShiftLeft => {
269            let left = left.evaluate_value_to_number(ctx)?;
270            let right = right.evaluate_value_to_number(ctx)?;
271            let left = left.to_int_32();
272            let right = right.to_uint_32() & 31;
273            Some(ConstantValue::Number(f64::from(left << right)))
274        }
275        BinaryOperator::ShiftRight => {
276            let left = left.evaluate_value_to_number(ctx)?;
277            let right = right.evaluate_value_to_number(ctx)?;
278            let left = left.to_int_32();
279            let right = right.to_uint_32() & 31;
280            Some(ConstantValue::Number(f64::from(left >> right)))
281        }
282        BinaryOperator::ShiftRightZeroFill => {
283            let left = left.evaluate_value_to_number(ctx)?;
284            let right = right.evaluate_value_to_number(ctx)?;
285            let left = left.to_uint_32();
286            let right = right.to_uint_32() & 31;
287            Some(ConstantValue::Number(f64::from(left >> right)))
288        }
289        BinaryOperator::LessThan => is_less_than(ctx, left, right).map(|value| match value {
290            ConstantValue::Undefined => ConstantValue::Boolean(false),
291            _ => value,
292        }),
293        BinaryOperator::GreaterThan => is_less_than(ctx, right, left).map(|value| match value {
294            ConstantValue::Undefined => ConstantValue::Boolean(false),
295            _ => value,
296        }),
297        BinaryOperator::LessEqualThan => is_less_than(ctx, right, left).map(|value| match value {
298            ConstantValue::Boolean(true) | ConstantValue::Undefined => {
299                ConstantValue::Boolean(false)
300            }
301            ConstantValue::Boolean(false) => ConstantValue::Boolean(true),
302            _ => unreachable!(),
303        }),
304        BinaryOperator::GreaterEqualThan => {
305            is_less_than(ctx, left, right).map(|value| match value {
306                ConstantValue::Boolean(true) | ConstantValue::Undefined => {
307                    ConstantValue::Boolean(false)
308                }
309                ConstantValue::Boolean(false) => ConstantValue::Boolean(true),
310                _ => unreachable!(),
311            })
312        }
313        BinaryOperator::BitwiseAnd => {
314            if left.value_type(ctx).is_bigint() && right.value_type(ctx).is_bigint() {
315                let left_biginit = left.evaluate_value_to_bigint(ctx)?;
316                let right_bigint = right.evaluate_value_to_bigint(ctx)?;
317                return Some(ConstantValue::BigInt(left_biginit & right_bigint));
318            }
319            let left_int = left.evaluate_value_to_number(ctx)?.to_int_32();
320            let right_int = right.evaluate_value_to_number(ctx)?.to_int_32();
321            Some(ConstantValue::Number(f64::from(left_int & right_int)))
322        }
323        BinaryOperator::BitwiseOR => {
324            if left.value_type(ctx).is_bigint() && right.value_type(ctx).is_bigint() {
325                let left_biginit = left.evaluate_value_to_bigint(ctx)?;
326                let right_bigint = right.evaluate_value_to_bigint(ctx)?;
327                return Some(ConstantValue::BigInt(left_biginit | right_bigint));
328            }
329            let left_int = left.evaluate_value_to_number(ctx)?.to_int_32();
330            let right_int = right.evaluate_value_to_number(ctx)?.to_int_32();
331            Some(ConstantValue::Number(f64::from(left_int | right_int)))
332        }
333        BinaryOperator::BitwiseXOR => {
334            if left.value_type(ctx).is_bigint() && right.value_type(ctx).is_bigint() {
335                let left_biginit = left.evaluate_value_to_bigint(ctx)?;
336                let right_bigint = right.evaluate_value_to_bigint(ctx)?;
337                return Some(ConstantValue::BigInt(left_biginit ^ right_bigint));
338            }
339            let left_int = left.evaluate_value_to_number(ctx)?.to_int_32();
340            let right_int = right.evaluate_value_to_number(ctx)?.to_int_32();
341            Some(ConstantValue::Number(f64::from(left_int ^ right_int)))
342        }
343        BinaryOperator::Instanceof => {
344            if let Expression::Identifier(right_ident) = right {
345                let name = right_ident.name.as_str();
346                if matches!(name, "Object" | "Number" | "Boolean" | "String")
347                    && ctx.is_global_reference(right_ident)
348                {
349                    let left_ty = left.value_type(ctx);
350                    if left_ty.is_undetermined() {
351                        return None;
352                    }
353                    if name == "Object" {
354                        if !left_ty.is_object() {
355                            return Some(ConstantValue::Boolean(false));
356                        }
357                        if matches!(
358                            left,
359                            Expression::ArrayExpression(_)
360                                | Expression::RegExpLiteral(_)
361                                | Expression::FunctionExpression(_)
362                                | Expression::ArrowFunctionExpression(_)
363                                | Expression::ClassExpression(_)
364                        ) | matches!(left, Expression::ObjectExpression(obj_expr) if obj_expr.properties.is_empty())
365                        {
366                            return Some(ConstantValue::Boolean(true));
367                        }
368                        // NOTE: `{ __proto__: null } instanceof Object` is false
369                        return None;
370                    }
371                    return Some(ConstantValue::Boolean(false));
372                }
373            }
374            None
375        }
376        BinaryOperator::StrictEquality => {
377            let value = strict_equality_comparison(ctx, left, right)?;
378            Some(ConstantValue::Boolean(value))
379        }
380        BinaryOperator::StrictInequality => {
381            let value = strict_equality_comparison(ctx, left, right)?;
382            Some(ConstantValue::Boolean(!value))
383        }
384        BinaryOperator::Equality => {
385            let value = abstract_equality_comparison(ctx, left, right)?;
386            Some(ConstantValue::Boolean(value))
387        }
388        BinaryOperator::Inequality => {
389            let value = abstract_equality_comparison(ctx, left, right)?;
390            Some(ConstantValue::Boolean(!value))
391        }
392        BinaryOperator::In => None,
393    }
394}
395
396impl<'a> ConstantEvaluation<'a> for LogicalExpression<'a> {
397    fn evaluate_value_to(
398        &self,
399        ctx: &impl ConstantEvaluationCtx<'a>,
400        target_ty: Option<ValueType>,
401    ) -> Option<ConstantValue<'a>> {
402        match self.operator {
403            LogicalOperator::And => match self.left.evaluate_value_to_boolean(ctx) {
404                Some(true) => self.right.evaluate_value(ctx),
405                Some(false) => self.left.evaluate_value(ctx),
406                None => {
407                    // ToBoolean(a && false) -> false
408                    if target_ty == Some(ValueType::Boolean)
409                        && self.right.evaluate_value_to_boolean(ctx) == Some(false)
410                    {
411                        Some(ConstantValue::Boolean(false))
412                    } else {
413                        None
414                    }
415                }
416            },
417            LogicalOperator::Or => match self.left.evaluate_value_to_boolean(ctx) {
418                Some(true) => self.left.evaluate_value(ctx),
419                Some(false) => self.right.evaluate_value(ctx),
420                None => {
421                    // ToBoolean(a || true) -> true
422                    if target_ty == Some(ValueType::Boolean)
423                        && self.right.evaluate_value_to_boolean(ctx) == Some(true)
424                    {
425                        Some(ConstantValue::Boolean(true))
426                    } else {
427                        None
428                    }
429                }
430            },
431            LogicalOperator::Coalesce => None,
432        }
433    }
434}
435
436impl<'a> ConstantEvaluation<'a> for UnaryExpression<'a> {
437    fn evaluate_value_to(
438        &self,
439        ctx: &impl ConstantEvaluationCtx<'a>,
440        _target_ty: Option<ValueType>,
441    ) -> Option<ConstantValue<'a>> {
442        match self.operator {
443            UnaryOperator::Typeof => {
444                let arg_ty = self.argument.value_type(ctx);
445                let s = match arg_ty {
446                    ValueType::BigInt => "bigint",
447                    ValueType::Number => "number",
448                    ValueType::String => "string",
449                    ValueType::Boolean => "boolean",
450                    ValueType::Undefined => "undefined",
451                    ValueType::Null => "object",
452                    _ => match &self.argument {
453                        Expression::ObjectExpression(_) | Expression::ArrayExpression(_) => {
454                            "object"
455                        }
456                        Expression::ClassExpression(_)
457                        | Expression::FunctionExpression(_)
458                        | Expression::ArrowFunctionExpression(_) => "function",
459                        _ => return None,
460                    },
461                };
462                Some(ConstantValue::String(Cow::Borrowed(s)))
463            }
464            UnaryOperator::Void => Some(ConstantValue::Undefined),
465            UnaryOperator::LogicalNot => {
466                self.argument.evaluate_value_to_boolean(ctx).map(|b| !b).map(ConstantValue::Boolean)
467            }
468            UnaryOperator::UnaryPlus => {
469                self.argument.evaluate_value_to_number(ctx).map(ConstantValue::Number)
470            }
471            UnaryOperator::UnaryNegation => match self.argument.value_type(ctx) {
472                ValueType::BigInt => self
473                    .argument
474                    .evaluate_value_to_bigint(ctx)
475                    .map(|v| -v)
476                    .map(ConstantValue::BigInt),
477                ValueType::Number => self
478                    .argument
479                    .evaluate_value_to_number(ctx)
480                    .map(|v| if v.is_nan() { v } else { -v })
481                    .map(ConstantValue::Number),
482                ValueType::Undefined => Some(ConstantValue::Number(f64::NAN)),
483                ValueType::Null => Some(ConstantValue::Number(-0.0)),
484                _ => None,
485            },
486            UnaryOperator::BitwiseNot => match self.argument.value_type(ctx) {
487                ValueType::BigInt => self
488                    .argument
489                    .evaluate_value_to_bigint(ctx)
490                    .map(|v| !v)
491                    .map(ConstantValue::BigInt),
492                #[expect(clippy::cast_lossless)]
493                _ => self
494                    .argument
495                    .evaluate_value_to_number(ctx)
496                    .map(|v| (!v.to_int_32()) as f64)
497                    .map(ConstantValue::Number),
498            },
499            UnaryOperator::Delete => None,
500        }
501    }
502}
503
504impl<'a> ConstantEvaluation<'a> for StaticMemberExpression<'a> {
505    fn evaluate_value_to(
506        &self,
507        ctx: &impl ConstantEvaluationCtx<'a>,
508        _target_ty: Option<ValueType>,
509    ) -> Option<ConstantValue<'a>> {
510        match self.property.name.as_str() {
511            "length" => evaluate_value_length(&self.object, ctx),
512            _ => None,
513        }
514    }
515}
516
517impl<'a> ConstantEvaluation<'a> for ComputedMemberExpression<'a> {
518    fn evaluate_value_to(
519        &self,
520        ctx: &impl ConstantEvaluationCtx<'a>,
521        _target_ty: Option<ValueType>,
522    ) -> Option<ConstantValue<'a>> {
523        match &self.expression {
524            Expression::StringLiteral(s) if s.value == "length" => {
525                evaluate_value_length(&self.object, ctx)
526            }
527            _ => None,
528        }
529    }
530}
531
532fn evaluate_value_length<'a>(
533    object: &Expression<'a>,
534    ctx: &impl ConstantEvaluationCtx<'a>,
535) -> Option<ConstantValue<'a>> {
536    if let Some(ConstantValue::String(s)) = object.evaluate_value(ctx) {
537        Some(ConstantValue::Number(s.encode_utf16().count().to_f64().unwrap()))
538    } else if let Expression::ArrayExpression(arr) = object {
539        if arr.elements.iter().any(|e| matches!(e, ArrayExpressionElement::SpreadElement(_))) {
540            None
541        } else {
542            Some(ConstantValue::Number(arr.elements.len().to_f64().unwrap()))
543        }
544    } else {
545        None
546    }
547}
548
549impl<'a> ConstantEvaluation<'a> for CallExpression<'a> {
550    fn evaluate_value_to(
551        &self,
552        ctx: &impl ConstantEvaluationCtx<'a>,
553        _target_ty: Option<ValueType>,
554    ) -> Option<ConstantValue<'a>> {
555        call_expr::try_fold_known_global_methods(&self.callee, &self.arguments, ctx)
556    }
557}