oxc_ecmascript/constant_evaluation/
is_literal_value.rs

1use oxc_ast::ast::*;
2
3use crate::GlobalContext;
4
5/// Returns true if this is a literal value.
6///
7/// We define a literal value as any node that evaluates
8/// to the same thing regardless of when or where it is evaluated. So `/xyz/` and `[3, 5]` are
9/// literals, but the name a is not.
10///
11/// Function literals do not meet this definition, because they lexically capture variables. For
12/// example, if you have `function() { return a; }`.
13/// If it is evaluated in a different scope, then it captures a different variable. Even if
14/// the function did not read any captured variables directly, it would still fail this definition,
15/// because it affects the lifecycle of variables in the enclosing scope.
16///
17/// However, a function literal with respect to a particular scope is a literal.
18/// If `include_functions` is true, all function expressions will be treated as literals.
19pub trait IsLiteralValue<'a, 'b> {
20    fn is_literal_value(&self, include_functions: bool, ctx: &impl GlobalContext<'a>) -> bool;
21}
22
23impl<'a> IsLiteralValue<'a, '_> for Expression<'a> {
24    fn is_literal_value(&self, include_functions: bool, ctx: &impl GlobalContext<'a>) -> bool {
25        match self {
26            Self::BooleanLiteral(_)
27            | Self::NullLiteral(_)
28            | Self::NumericLiteral(_)
29            | Self::BigIntLiteral(_)
30            | Self::RegExpLiteral(_)
31            | Self::StringLiteral(_) => true,
32            Self::TemplateLiteral(lit) => lit.is_literal_value(include_functions, ctx),
33            Self::Identifier(ident) => {
34                matches!(ident.name.as_str(), "undefined" | "Infinity" | "NaN")
35                    && ctx.is_global_reference(ident)
36            }
37            Self::ArrayExpression(expr) => expr.is_literal_value(include_functions, ctx),
38            Self::ObjectExpression(expr) => expr.is_literal_value(include_functions, ctx),
39            Self::FunctionExpression(_) | Self::ArrowFunctionExpression(_) => include_functions,
40            Self::UnaryExpression(e) => e.is_literal_value(include_functions, ctx),
41            Self::BinaryExpression(e) => e.is_literal_value(include_functions, ctx),
42            Self::LogicalExpression(e) => {
43                e.left.is_literal_value(include_functions, ctx)
44                    && e.right.is_literal_value(include_functions, ctx)
45            }
46            Self::ConditionalExpression(e) => {
47                e.test.is_literal_value(include_functions, ctx)
48                    && e.consequent.is_literal_value(include_functions, ctx)
49                    && e.alternate.is_literal_value(include_functions, ctx)
50            }
51            Self::ParenthesizedExpression(e) => {
52                e.expression.is_literal_value(include_functions, ctx)
53            }
54            Self::SequenceExpression(e) => {
55                e.expressions.iter().all(|expr| expr.is_literal_value(include_functions, ctx))
56            }
57            _ => false,
58        }
59    }
60}
61
62impl<'a> IsLiteralValue<'a, '_> for TemplateLiteral<'a> {
63    fn is_literal_value(&self, _include_functions: bool, _ctx: &impl GlobalContext<'a>) -> bool {
64        self.is_no_substitution_template()
65    }
66}
67
68impl<'a> IsLiteralValue<'a, '_> for ArrayExpression<'a> {
69    fn is_literal_value(&self, include_functions: bool, ctx: &impl GlobalContext<'a>) -> bool {
70        self.elements.iter().all(|element| element.is_literal_value(include_functions, ctx))
71    }
72}
73
74impl<'a> IsLiteralValue<'a, '_> for ObjectExpression<'a> {
75    fn is_literal_value(&self, include_functions: bool, ctx: &impl GlobalContext<'a>) -> bool {
76        self.properties.iter().all(|property| property.is_literal_value(include_functions, ctx))
77    }
78}
79
80impl<'a> IsLiteralValue<'a, '_> for UnaryExpression<'a> {
81    fn is_literal_value(&self, include_functions: bool, ctx: &impl GlobalContext<'a>) -> bool {
82        match self.operator {
83            UnaryOperator::Void | UnaryOperator::LogicalNot | UnaryOperator::Typeof => {
84                self.argument.is_literal_value(include_functions, ctx)
85            }
86            UnaryOperator::UnaryPlus => {
87                can_convert_to_number_transparently(&self.argument, include_functions, ctx)
88            }
89            UnaryOperator::UnaryNegation | UnaryOperator::BitwiseNot => {
90                can_convert_to_number_transparently(&self.argument, include_functions, ctx)
91                    || matches!(self.argument, Expression::BigIntLiteral(_))
92            }
93            UnaryOperator::Delete => false,
94        }
95    }
96}
97
98impl<'a> IsLiteralValue<'a, '_> for BinaryExpression<'a> {
99    fn is_literal_value(&self, include_functions: bool, ctx: &impl GlobalContext<'a>) -> bool {
100        match self.operator {
101            BinaryOperator::StrictEquality | BinaryOperator::StrictInequality => {
102                self.left.is_literal_value(include_functions, ctx)
103                    && self.right.is_literal_value(include_functions, ctx)
104            }
105            BinaryOperator::Addition => {
106                if (is_immutable_string(&self.left, include_functions, ctx)
107                    && can_convert_to_string_transparently(&self.right, include_functions, ctx))
108                    || (is_immutable_string(&self.right, include_functions, ctx)
109                        && can_convert_to_string_transparently(&self.left, include_functions, ctx))
110                {
111                    return true;
112                }
113                (matches!(&self.left, Expression::NumericLiteral(_))
114                    && matches!(&self.right, Expression::NumericLiteral(_)))
115                    | (matches!(&self.left, Expression::BigIntLiteral(_))
116                        && matches!(&self.right, Expression::BigIntLiteral(_)))
117            }
118            BinaryOperator::Subtraction
119            | BinaryOperator::Multiplication
120            | BinaryOperator::Division
121            | BinaryOperator::Remainder
122            | BinaryOperator::Exponential
123            | BinaryOperator::ShiftLeft
124            | BinaryOperator::ShiftRight
125            | BinaryOperator::ShiftRightZeroFill
126            | BinaryOperator::BitwiseOR
127            | BinaryOperator::BitwiseXOR
128            | BinaryOperator::BitwiseAnd => {
129                if (matches!(&self.left, Expression::NumericLiteral(_))
130                    && can_convert_to_number_transparently(&self.right, include_functions, ctx))
131                    || (matches!(&self.right, Expression::NumericLiteral(_))
132                        && can_convert_to_number_transparently(&self.left, include_functions, ctx))
133                {
134                    return true;
135                }
136                let (Expression::BigIntLiteral(_), Expression::BigIntLiteral(right)) =
137                    (&self.left, &self.right)
138                else {
139                    return false;
140                };
141                // 1n / 0n, 1n % 0n, 1n ** (-1n) throws an error
142                match self.operator {
143                    BinaryOperator::ShiftRightZeroFill => false,
144                    BinaryOperator::Exponential => !right.is_negative(),
145                    BinaryOperator::Division | BinaryOperator::Remainder => !right.is_zero(),
146                    _ => true,
147                }
148            }
149            BinaryOperator::LessThan
150            | BinaryOperator::LessEqualThan
151            | BinaryOperator::GreaterThan
152            | BinaryOperator::GreaterEqualThan
153            | BinaryOperator::Equality
154            | BinaryOperator::Inequality
155            | BinaryOperator::In
156            | BinaryOperator::Instanceof => false,
157        }
158    }
159}
160
161impl<'a> IsLiteralValue<'a, '_> for ArrayExpressionElement<'a> {
162    fn is_literal_value(&self, include_functions: bool, ctx: &impl GlobalContext<'a>) -> bool {
163        match self {
164            // spread element triggers `Symbol.iterator` call
165            Self::SpreadElement(_) => false,
166            Self::Elision(_) => true,
167            match_expression!(Self) => {
168                self.to_expression().is_literal_value(include_functions, ctx)
169            }
170        }
171    }
172}
173
174impl<'a> IsLiteralValue<'a, '_> for ObjectPropertyKind<'a> {
175    fn is_literal_value(&self, include_functions: bool, ctx: &impl GlobalContext<'a>) -> bool {
176        match self {
177            Self::ObjectProperty(property) => property.is_literal_value(include_functions, ctx),
178            Self::SpreadProperty(property) => match &property.argument {
179                Expression::ArrayExpression(expr) => expr.is_literal_value(include_functions, ctx),
180                Expression::StringLiteral(_) => true,
181                Expression::TemplateLiteral(lit) => lit.is_literal_value(include_functions, ctx),
182                Expression::ObjectExpression(expr) => expr.is_literal_value(include_functions, ctx),
183                _ => false,
184            },
185        }
186    }
187}
188
189impl<'a> IsLiteralValue<'a, '_> for ObjectProperty<'a> {
190    fn is_literal_value(&self, include_functions: bool, ctx: &impl GlobalContext<'a>) -> bool {
191        self.key.is_literal_value(include_functions, ctx)
192            && self.value.is_literal_value(include_functions, ctx)
193    }
194}
195
196impl<'a> IsLiteralValue<'a, '_> for PropertyKey<'a> {
197    fn is_literal_value(&self, include_functions: bool, ctx: &impl GlobalContext<'a>) -> bool {
198        match self {
199            Self::StaticIdentifier(_) => true,
200            Self::PrivateIdentifier(_) => false,
201            match_expression!(Self) => {
202                can_convert_to_string_transparently(self.to_expression(), include_functions, ctx)
203            }
204        }
205    }
206}
207
208fn can_convert_to_number_transparently<'a>(
209    expr: &Expression<'a>,
210    include_functions: bool,
211    ctx: &impl GlobalContext<'a>,
212) -> bool {
213    match expr {
214        Expression::NumericLiteral(_)
215        | Expression::NullLiteral(_)
216        | Expression::BooleanLiteral(_)
217        | Expression::StringLiteral(_) => true,
218        Expression::TemplateLiteral(lit) => lit.is_literal_value(include_functions, ctx),
219        Expression::Identifier(ident) => {
220            matches!(ident.name.as_str(), "undefined" | "Infinity" | "NaN")
221                && ctx.is_global_reference(ident)
222        }
223        Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_) => {
224            include_functions
225        }
226        Expression::UnaryExpression(e) => match e.operator {
227            UnaryOperator::Void | UnaryOperator::LogicalNot | UnaryOperator::Typeof => {
228                e.argument.is_literal_value(include_functions, ctx)
229            }
230            UnaryOperator::UnaryPlus | UnaryOperator::UnaryNegation | UnaryOperator::BitwiseNot => {
231                can_convert_to_number_transparently(&e.argument, include_functions, ctx)
232            }
233            UnaryOperator::Delete => false,
234        },
235        Expression::BinaryExpression(e) => match e.operator {
236            BinaryOperator::StrictEquality | BinaryOperator::StrictInequality => {
237                e.left.is_literal_value(include_functions, ctx)
238                    && e.right.is_literal_value(include_functions, ctx)
239            }
240            BinaryOperator::Addition => {
241                if (is_immutable_string(&e.left, include_functions, ctx)
242                    && can_convert_to_string_transparently(&e.right, include_functions, ctx))
243                    || (is_immutable_string(&e.right, include_functions, ctx)
244                        && can_convert_to_string_transparently(&e.left, include_functions, ctx))
245                {
246                    return true;
247                }
248                (matches!(&e.left, Expression::NumericLiteral(_))
249                    && matches!(&e.right, Expression::NumericLiteral(_)))
250                    | (matches!(&e.left, Expression::BigIntLiteral(_))
251                        && matches!(&e.right, Expression::BigIntLiteral(_)))
252            }
253            BinaryOperator::Subtraction
254            | BinaryOperator::Multiplication
255            | BinaryOperator::Division
256            | BinaryOperator::Remainder
257            | BinaryOperator::Exponential
258            | BinaryOperator::ShiftLeft
259            | BinaryOperator::ShiftRight
260            | BinaryOperator::ShiftRightZeroFill
261            | BinaryOperator::BitwiseOR
262            | BinaryOperator::BitwiseXOR
263            | BinaryOperator::BitwiseAnd => {
264                if (matches!(&e.left, Expression::NumericLiteral(_))
265                    && can_convert_to_number_transparently(&e.right, include_functions, ctx))
266                    || (matches!(&e.right, Expression::NumericLiteral(_))
267                        && can_convert_to_number_transparently(&e.left, include_functions, ctx))
268                {
269                    return true;
270                }
271                false
272            }
273            BinaryOperator::LessThan
274            | BinaryOperator::LessEqualThan
275            | BinaryOperator::GreaterThan
276            | BinaryOperator::GreaterEqualThan
277            | BinaryOperator::Equality
278            | BinaryOperator::Inequality
279            | BinaryOperator::In
280            | BinaryOperator::Instanceof => false,
281        },
282        Expression::LogicalExpression(e) => {
283            can_convert_to_number_transparently(&e.left, include_functions, ctx)
284                && can_convert_to_number_transparently(&e.right, include_functions, ctx)
285        }
286        Expression::ConditionalExpression(e) => {
287            e.test.is_literal_value(include_functions, ctx)
288                && can_convert_to_number_transparently(&e.consequent, include_functions, ctx)
289                && can_convert_to_number_transparently(&e.alternate, include_functions, ctx)
290        }
291        Expression::ParenthesizedExpression(e) => {
292            can_convert_to_number_transparently(&e.expression, include_functions, ctx)
293        }
294        Expression::SequenceExpression(e) => {
295            can_convert_to_number_transparently(
296                e.expressions.last().expect("should have at least one element"),
297                include_functions,
298                ctx,
299            ) && e
300                .expressions
301                .iter()
302                .rev()
303                .skip(1)
304                .all(|expr| expr.is_literal_value(include_functions, ctx))
305        }
306        _ => false,
307    }
308}
309
310fn can_convert_to_string_transparently<'a>(
311    expr: &Expression<'a>,
312    include_functions: bool,
313    ctx: &impl GlobalContext<'a>,
314) -> bool {
315    match expr {
316        Expression::NumericLiteral(_)
317        | Expression::StringLiteral(_)
318        | Expression::NullLiteral(_)
319        | Expression::BooleanLiteral(_)
320        | Expression::BigIntLiteral(_) => true,
321        Expression::TemplateLiteral(lit) => lit.is_literal_value(include_functions, ctx),
322        Expression::Identifier(ident) => {
323            matches!(ident.name.as_str(), "undefined" | "Infinity" | "NaN")
324                && ctx.is_global_reference(ident)
325        }
326        Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_) => {
327            include_functions
328        }
329        Expression::UnaryExpression(e) => e.is_literal_value(include_functions, ctx),
330        Expression::BinaryExpression(e) => e.is_literal_value(include_functions, ctx),
331        Expression::LogicalExpression(e) => {
332            e.left.is_literal_value(include_functions, ctx)
333                && e.right.is_literal_value(include_functions, ctx)
334        }
335        Expression::ConditionalExpression(e) => {
336            e.test.is_literal_value(include_functions, ctx)
337                && can_convert_to_string_transparently(&e.consequent, include_functions, ctx)
338                && can_convert_to_string_transparently(&e.alternate, include_functions, ctx)
339        }
340        Expression::ParenthesizedExpression(e) => {
341            can_convert_to_string_transparently(&e.expression, include_functions, ctx)
342        }
343        Expression::SequenceExpression(e) => {
344            can_convert_to_string_transparently(
345                e.expressions.last().expect("should have at least one element"),
346                include_functions,
347                ctx,
348            ) && e
349                .expressions
350                .iter()
351                .rev()
352                .skip(1)
353                .all(|expr| expr.is_literal_value(include_functions, ctx))
354        }
355        _ => false,
356    }
357}
358
359fn is_immutable_string<'a>(
360    expr: &Expression<'a>,
361    include_functions: bool,
362    ctx: &impl GlobalContext<'a>,
363) -> bool {
364    match expr {
365        Expression::StringLiteral(_) => true,
366        Expression::TemplateLiteral(lit) => lit.is_literal_value(include_functions, ctx),
367        _ => false,
368    }
369}