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 fn evaluate_value_to(
42 &self,
43 ctx: &impl ConstantEvaluationCtx<'a>,
44 target_ty: Option<ValueType>,
45 ) -> Option<ConstantValue<'a>>;
46
47 fn evaluate_value(&self, ctx: &impl ConstantEvaluationCtx<'a>) -> Option<ConstantValue<'a>> {
51 self.evaluate_value_to(ctx, None)
52 }
53
54 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 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 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 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 (!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 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 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 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 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 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 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}