Skip to content

Commit 9bd8d7d

Browse files
Update crates/oxc_semantic/src/lib.rs
Co-authored-by: Copilot <[email protected]> Signed-off-by: IWANABETHATGUY <[email protected]>
1 parent 161f98a commit 9bd8d7d

File tree

9 files changed

+328
-362
lines changed

9 files changed

+328
-362
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/oxc_ecmascript/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ oxc_syntax = { workspace = true, features = ["to_js_string"] }
2929
cow-utils = { workspace = true }
3030
num-bigint = { workspace = true }
3131
num-traits = { workspace = true }
32+
rustc-hash = { workspace = true }
Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
//! Enum constant value evaluation
2+
//!
3+
//! This module provides reusable logic for evaluating TypeScript enum member values.
4+
//! It's used by both the TypeScript transformer and the semantic analyzer.
5+
//!
6+
//! Based on TypeScript's and Babel's enum transformation implementation.
7+
8+
use oxc_allocator::StringBuilder;
9+
use oxc_ast::{
10+
AstBuilder,
11+
ast::{BinaryExpression, Expression, UnaryExpression, match_member_expression},
12+
};
13+
use oxc_span::Atom;
14+
use oxc_syntax::{
15+
number::ToJsString,
16+
operator::{BinaryOperator, UnaryOperator},
17+
};
18+
use rustc_hash::FxHashMap;
19+
20+
use crate::{ToInt32, ToUint32};
21+
22+
/// Constant value for enum members during evaluation.
23+
#[derive(Debug, Clone, Copy)]
24+
pub enum ConstantValue<'a> {
25+
Number(f64),
26+
String(Atom<'a>),
27+
}
28+
29+
/// Enum member values (or None if it can't be evaluated at build time) keyed by member names
30+
pub type PrevMembers<'a> = FxHashMap<Atom<'a>, Option<ConstantValue<'a>>>;
31+
32+
/// Evaluator for enum constant values.
33+
/// This is a port of TypeScript's enum value evaluation logic.
34+
pub struct EnumEvaluator<'a> {
35+
ast: AstBuilder<'a>,
36+
/// Map of enum names to their members (for cross-enum references)
37+
enums: Option<&'a FxHashMap<Atom<'a>, PrevMembers<'a>>>,
38+
}
39+
40+
impl<'a> EnumEvaluator<'a> {
41+
/// Create a new evaluator with access to all enum definitions (for cross-enum references)
42+
pub fn new_with_enums(
43+
ast: AstBuilder<'a>,
44+
enums: &'a FxHashMap<Atom<'a>, PrevMembers<'a>>,
45+
) -> Self {
46+
Self { ast, enums: Some(enums) }
47+
}
48+
49+
/// Create a new evaluator without cross-enum reference support
50+
pub fn new(ast: AstBuilder<'a>) -> Self {
51+
Self { ast, enums: None }
52+
}
53+
54+
/// Evaluate the expression to a constant value.
55+
/// Refer to [babel](https://github.com/babel/babel/blob/610897a9a96c5e344e77ca9665df7613d2f88358/packages/babel-plugin-transform-typescript/src/enum.ts#L241C1-L394C2)
56+
pub fn computed_constant_value(
57+
&self,
58+
expr: &Expression<'a>,
59+
prev_members: &PrevMembers<'a>,
60+
) -> Option<ConstantValue<'a>> {
61+
self.evaluate(expr, prev_members)
62+
}
63+
64+
fn evaluate_ref(
65+
&self,
66+
expr: &Expression<'a>,
67+
prev_members: &PrevMembers<'a>,
68+
) -> Option<ConstantValue<'a>> {
69+
match expr {
70+
match_member_expression!(Expression) => {
71+
let expr = expr.to_member_expression();
72+
let Expression::Identifier(ident) = expr.object() else { return None };
73+
74+
// Look up in all enums if available (for cross-enum references)
75+
if let Some(enums) = self.enums {
76+
let members = enums.get(&ident.name)?;
77+
let property = expr.static_property_name()?;
78+
*members.get(property)?
79+
} else {
80+
None
81+
}
82+
}
83+
Expression::Identifier(ident) => {
84+
if ident.name == "Infinity" {
85+
return Some(ConstantValue::Number(f64::INFINITY));
86+
} else if ident.name == "NaN" {
87+
return Some(ConstantValue::Number(f64::NAN));
88+
}
89+
90+
if let Some(value) = prev_members.get(&ident.name) {
91+
return *value;
92+
}
93+
94+
// TODO:
95+
// This is a bit tricky because we need to find the BindingIdentifier that corresponds to the identifier reference.
96+
// and then we may to evaluate the initializer of the BindingIdentifier.
97+
// finally, we can get the value of the identifier and call the `computed_constant_value` function.
98+
// See https://github.com/babel/babel/blob/610897a9a96c5e344e77ca9665df7613d2f88358/packages/babel-plugin-transform-typescript/src/enum.ts#L327-L329
99+
None
100+
}
101+
_ => None,
102+
}
103+
}
104+
105+
fn evaluate(
106+
&self,
107+
expr: &Expression<'a>,
108+
prev_members: &PrevMembers<'a>,
109+
) -> Option<ConstantValue<'a>> {
110+
match expr {
111+
Expression::Identifier(_)
112+
| Expression::ComputedMemberExpression(_)
113+
| Expression::StaticMemberExpression(_)
114+
| Expression::PrivateFieldExpression(_) => self.evaluate_ref(expr, prev_members),
115+
Expression::BinaryExpression(expr) => self.eval_binary_expression(expr, prev_members),
116+
Expression::UnaryExpression(expr) => self.eval_unary_expression(expr, prev_members),
117+
Expression::NumericLiteral(lit) => Some(ConstantValue::Number(lit.value)),
118+
Expression::StringLiteral(lit) => Some(ConstantValue::String(lit.value)),
119+
Expression::TemplateLiteral(lit) => {
120+
let value = if let Some(quasi) = lit.single_quasi() {
121+
quasi
122+
} else {
123+
let mut value = StringBuilder::new_in(self.ast.allocator);
124+
for (quasi, expr) in lit.quasis.iter().zip(&lit.expressions) {
125+
value.push_str(&quasi.value.cooked.unwrap_or(quasi.value.raw));
126+
if let Some(ConstantValue::String(str)) = self.evaluate(expr, prev_members)
127+
{
128+
value.push_str(&str);
129+
}
130+
}
131+
self.ast.atom(value.into_str())
132+
};
133+
Some(ConstantValue::String(value))
134+
}
135+
Expression::ParenthesizedExpression(expr) => {
136+
self.evaluate(&expr.expression, prev_members)
137+
}
138+
_ => None,
139+
}
140+
}
141+
142+
fn eval_binary_expression(
143+
&self,
144+
expr: &BinaryExpression<'a>,
145+
prev_members: &PrevMembers<'a>,
146+
) -> Option<ConstantValue<'a>> {
147+
let left = self.evaluate(&expr.left, prev_members)?;
148+
let right = self.evaluate(&expr.right, prev_members)?;
149+
150+
if matches!(expr.operator, BinaryOperator::Addition)
151+
&& (matches!(left, ConstantValue::String(_))
152+
|| matches!(right, ConstantValue::String(_)))
153+
{
154+
let left_string = match left {
155+
ConstantValue::String(str) => str,
156+
ConstantValue::Number(v) => self.ast.atom(&v.to_js_string()),
157+
};
158+
159+
let right_string = match right {
160+
ConstantValue::String(str) => str,
161+
ConstantValue::Number(v) => self.ast.atom(&v.to_js_string()),
162+
};
163+
164+
return Some(ConstantValue::String(
165+
self.ast.atom_from_strs_array([&left_string, &right_string]),
166+
));
167+
}
168+
169+
let left = match left {
170+
ConstantValue::Number(v) => v,
171+
ConstantValue::String(_) => return None,
172+
};
173+
174+
let right = match right {
175+
ConstantValue::Number(v) => v,
176+
ConstantValue::String(_) => return None,
177+
};
178+
179+
match expr.operator {
180+
BinaryOperator::ShiftRight => Some(ConstantValue::Number(f64::from(
181+
left.to_int_32().wrapping_shr(right.to_uint_32()),
182+
))),
183+
BinaryOperator::ShiftRightZeroFill => Some(ConstantValue::Number(f64::from(
184+
(left.to_uint_32()).wrapping_shr(right.to_uint_32()),
185+
))),
186+
BinaryOperator::ShiftLeft => Some(ConstantValue::Number(f64::from(
187+
left.to_int_32().wrapping_shl(right.to_uint_32()),
188+
))),
189+
BinaryOperator::BitwiseXOR => {
190+
Some(ConstantValue::Number(f64::from(left.to_int_32() ^ right.to_int_32())))
191+
}
192+
BinaryOperator::BitwiseOR => {
193+
Some(ConstantValue::Number(f64::from(left.to_int_32() | right.to_int_32())))
194+
}
195+
BinaryOperator::BitwiseAnd => {
196+
Some(ConstantValue::Number(f64::from(left.to_int_32() & right.to_int_32())))
197+
}
198+
BinaryOperator::Multiplication => Some(ConstantValue::Number(left * right)),
199+
BinaryOperator::Division => Some(ConstantValue::Number(left / right)),
200+
BinaryOperator::Addition => Some(ConstantValue::Number(left + right)),
201+
BinaryOperator::Subtraction => Some(ConstantValue::Number(left - right)),
202+
BinaryOperator::Remainder => Some(ConstantValue::Number(left % right)),
203+
BinaryOperator::Exponential => Some(ConstantValue::Number(left.powf(right))),
204+
_ => None,
205+
}
206+
}
207+
208+
fn eval_unary_expression(
209+
&self,
210+
expr: &UnaryExpression<'a>,
211+
prev_members: &PrevMembers<'a>,
212+
) -> Option<ConstantValue<'a>> {
213+
let value = self.evaluate(&expr.argument, prev_members)?;
214+
215+
let value = match value {
216+
ConstantValue::Number(value) => value,
217+
ConstantValue::String(_) => {
218+
let value = if expr.operator == UnaryOperator::UnaryNegation {
219+
ConstantValue::Number(f64::NAN)
220+
} else if expr.operator == UnaryOperator::BitwiseNot {
221+
ConstantValue::Number(-1.0)
222+
} else {
223+
value
224+
};
225+
return Some(value);
226+
}
227+
};
228+
229+
match expr.operator {
230+
UnaryOperator::UnaryPlus => Some(ConstantValue::Number(value)),
231+
UnaryOperator::UnaryNegation => Some(ConstantValue::Number(-value)),
232+
UnaryOperator::BitwiseNot => Some(ConstantValue::Number(f64::from(!value.to_int_32()))),
233+
_ => None,
234+
}
235+
}
236+
}

crates/oxc_ecmascript/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ mod to_string;
2929
mod to_integer_index;
3030

3131
pub mod constant_evaluation;
32+
pub mod enum_evaluation;
3233
mod global_context;
3334
pub mod side_effects;
3435

crates/oxc_semantic/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ workspace = true
2020
doctest = true
2121

2222
[dependencies]
23-
num-bigint = { workspace = true }
2423
oxc_allocator = { workspace = true }
2524
oxc_ast = { workspace = true }
2625
oxc_ast_visit = { workspace = true }

0 commit comments

Comments
 (0)