Skip to content

Commit 17597d7

Browse files
feat: add const enum semantic data
1 parent e77a48e commit 17597d7

File tree

5 files changed

+691
-1
lines changed

5 files changed

+691
-1
lines changed

Cargo.lock

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

crates/oxc_semantic/Cargo.toml

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

2222
[dependencies]
23+
num-bigint = { workspace = true }
2324
oxc_allocator = { workspace = true }
2425
oxc_ast = { workspace = true }
2526
oxc_ast_visit = { workspace = true }

crates/oxc_semantic/src/builder.rs

Lines changed: 218 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ use std::{
55
mem,
66
};
77

8+
use num_bigint::BigInt;
9+
810
use rustc_hash::FxHashMap;
911

1012
use oxc_allocator::Address;
@@ -27,6 +29,7 @@ use oxc_syntax::{
2729
#[cfg(feature = "linter")]
2830
use crate::jsdoc::JSDocBuilder;
2931
use crate::{
32+
const_enum::{ConstEnumTable, ConstEnumMemberValue, ConstEnumMemberInfo, ConstEnumInfo},
3033
Semantic,
3134
binder::{Binder, ModuleInstanceState},
3235
checker,
@@ -108,6 +111,9 @@ pub struct SemanticBuilder<'a> {
108111

109112
pub(crate) class_table_builder: ClassTableBuilder<'a>,
110113

114+
/// Table for storing const enum information
115+
pub(crate) const_enum_table: ConstEnumTable<'a>,
116+
111117
#[cfg(feature = "cfg")]
112118
ast_node_records: Vec<NodeId>,
113119
}
@@ -154,6 +160,7 @@ impl<'a> SemanticBuilder<'a> {
154160
#[cfg(not(feature = "cfg"))]
155161
cfg: (),
156162
class_table_builder: ClassTableBuilder::new(),
163+
const_enum_table: ConstEnumTable::new(),
157164
#[cfg(feature = "cfg")]
158165
ast_node_records: Vec::new(),
159166
}
@@ -296,6 +303,7 @@ impl<'a> SemanticBuilder<'a> {
296303
nodes: self.nodes,
297304
scoping: self.scoping,
298305
classes: self.class_table_builder.build(),
306+
const_enums: self.const_enum_table,
299307
#[cfg(feature = "linter")]
300308
jsdoc,
301309
unused_labels: self.unused_labels.labels,
@@ -2146,7 +2154,11 @@ impl<'a> SemanticBuilder<'a> {
21462154
}
21472155
AstKind::TSEnumDeclaration(enum_declaration) => {
21482156
enum_declaration.bind(self);
2149-
// TODO: const enum?
2157+
2158+
// Process const enums
2159+
if enum_declaration.r#const {
2160+
self.process_const_enum(enum_declaration);
2161+
}
21502162
}
21512163
AstKind::TSEnumMember(enum_member) => {
21522164
enum_member.bind(self);
@@ -2232,4 +2244,209 @@ impl<'a> SemanticBuilder<'a> {
22322244
mem::take(&mut self.current_reference_flags)
22332245
}
22342246
}
2247+
2248+
/// Process a const enum declaration and evaluate its members
2249+
fn process_const_enum(&mut self, enum_declaration: &TSEnumDeclaration<'a>) {
2250+
// Get the symbol ID for this enum
2251+
let symbol_id = enum_declaration.id.symbol_id.get().expect("enum should have symbol ID");
2252+
2253+
let mut members = std::collections::HashMap::new();
2254+
let mut current_value: f64 = -1.0; // Start at -1, first auto-increment will make it 0
2255+
2256+
for member in &enum_declaration.body.members {
2257+
let member_name = match &member.id {
2258+
TSEnumMemberName::Identifier(ident) => ident.name.as_str(),
2259+
TSEnumMemberName::String(string) => string.value.as_str(),
2260+
TSEnumMemberName::ComputedString(string) => string.value.as_str(),
2261+
TSEnumMemberName::ComputedTemplateString(template) => {
2262+
// For computed template strings, we need to evaluate them
2263+
if template.expressions.is_empty() {
2264+
if let Some(quasi) = template.quasis.first() {
2265+
quasi.value.raw.as_str()
2266+
} else {
2267+
continue;
2268+
}
2269+
} else {
2270+
// Skip template literals with expressions for now
2271+
continue;
2272+
}
2273+
}
2274+
};
2275+
2276+
let value = if let Some(initializer) = &member.initializer {
2277+
// Evaluate the initializer expression
2278+
let mut visited = std::vec::Vec::new();
2279+
if let Some(evaluated_value) = self.evaluate_const_enum_member(initializer, Some(symbol_id), &mut visited) {
2280+
// Update current_value based on the evaluated value
2281+
match &evaluated_value {
2282+
ConstEnumMemberValue::Number(n) => current_value = *n,
2283+
_ => {} // Don't change current_value for non-numeric values
2284+
}
2285+
evaluated_value
2286+
} else {
2287+
// If evaluation fails, fall back to current_value + 1
2288+
current_value += 1.0;
2289+
ConstEnumMemberValue::Number(current_value)
2290+
}
2291+
} else {
2292+
// Auto-increment the value
2293+
current_value += 1.0;
2294+
ConstEnumMemberValue::Number(current_value)
2295+
};
2296+
2297+
let member_info = ConstEnumMemberInfo {
2298+
name: member_name,
2299+
value,
2300+
span: member.span,
2301+
has_initializer: member.initializer.is_some(),
2302+
};
2303+
2304+
members.insert(member_name, member_info);
2305+
}
2306+
2307+
let enum_info = ConstEnumInfo {
2308+
symbol_id,
2309+
members,
2310+
span: enum_declaration.span,
2311+
};
2312+
2313+
self.const_enum_table.add_enum(symbol_id, enum_info);
2314+
}
2315+
2316+
/// Evaluate a const enum member's value with improved JavaScript semantics
2317+
fn evaluate_const_enum_member(
2318+
&self,
2319+
expression: &Expression<'a>,
2320+
current_enum: Option<SymbolId>,
2321+
_visited: &mut std::vec::Vec<&'a str>,
2322+
) -> Option<ConstEnumMemberValue<'a>> {
2323+
match expression {
2324+
Expression::StringLiteral(string) => Some(ConstEnumMemberValue::String(string.value.as_str())),
2325+
Expression::NumericLiteral(number) => Some(ConstEnumMemberValue::Number(number.value)),
2326+
Expression::BooleanLiteral(boolean) => Some(ConstEnumMemberValue::Boolean(boolean.value)),
2327+
Expression::BigIntLiteral(bigint) => {
2328+
bigint.value.parse::<BigInt>().ok().map(ConstEnumMemberValue::BigInt)
2329+
}
2330+
Expression::UnaryExpression(unary) => {
2331+
if let Some(argument) = self.evaluate_const_enum_member(&unary.argument, current_enum, _visited) {
2332+
self.evaluate_unary_operation(unary, argument)
2333+
} else {
2334+
None
2335+
}
2336+
}
2337+
Expression::BinaryExpression(binary) => {
2338+
if let (Some(left), Some(right)) = (
2339+
self.evaluate_const_enum_member(&binary.left, current_enum, _visited),
2340+
self.evaluate_const_enum_member(&binary.right, current_enum, _visited),
2341+
) {
2342+
self.evaluate_binary_operation(binary, left, right)
2343+
} else {
2344+
None
2345+
}
2346+
}
2347+
Expression::Identifier(ident) => {
2348+
// Try to resolve this as a reference to another const enum member
2349+
let name = ident.name.as_str();
2350+
2351+
if let Some(current_enum_id) = current_enum {
2352+
if let Some(enum_info) = self.const_enum_table.get_enum(current_enum_id) {
2353+
if let Some(member_info) = enum_info.members.get(name) {
2354+
return Some(member_info.value.clone());
2355+
}
2356+
}
2357+
}
2358+
None
2359+
}
2360+
_ => None,
2361+
}
2362+
}
2363+
2364+
/// Evaluate unary operations with proper JavaScript semantics
2365+
fn evaluate_unary_operation(
2366+
&self,
2367+
unary: &UnaryExpression<'a>,
2368+
argument: ConstEnumMemberValue<'a>,
2369+
) -> Option<ConstEnumMemberValue<'a>> {
2370+
match unary.operator {
2371+
UnaryOperator::UnaryNegation => {
2372+
match argument {
2373+
ConstEnumMemberValue::Number(n) => Some(ConstEnumMemberValue::Number(-n)),
2374+
_ => None,
2375+
}
2376+
}
2377+
UnaryOperator::UnaryPlus => {
2378+
match argument {
2379+
ConstEnumMemberValue::Number(n) => Some(ConstEnumMemberValue::Number(n)),
2380+
_ => None,
2381+
}
2382+
}
2383+
UnaryOperator::LogicalNot => {
2384+
match argument {
2385+
ConstEnumMemberValue::Boolean(b) => Some(ConstEnumMemberValue::Boolean(!b)),
2386+
_ => None,
2387+
}
2388+
}
2389+
_ => None,
2390+
}
2391+
}
2392+
2393+
/// Evaluate binary operations with proper JavaScript semantics
2394+
fn evaluate_binary_operation(
2395+
&self,
2396+
binary: &BinaryExpression<'a>,
2397+
left: ConstEnumMemberValue<'a>,
2398+
right: ConstEnumMemberValue<'a>,
2399+
) -> Option<ConstEnumMemberValue<'a>> {
2400+
match binary.operator {
2401+
BinaryOperator::Addition => {
2402+
match (&left, &right) {
2403+
(ConstEnumMemberValue::Number(l), ConstEnumMemberValue::Number(r)) => {
2404+
Some(ConstEnumMemberValue::Number(l + r))
2405+
}
2406+
_ => None,
2407+
}
2408+
}
2409+
BinaryOperator::Subtraction => {
2410+
match (&left, &right) {
2411+
(ConstEnumMemberValue::Number(l), ConstEnumMemberValue::Number(r)) => {
2412+
Some(ConstEnumMemberValue::Number(l - r))
2413+
}
2414+
_ => None,
2415+
}
2416+
}
2417+
BinaryOperator::Multiplication => {
2418+
match (&left, &right) {
2419+
(ConstEnumMemberValue::Number(l), ConstEnumMemberValue::Number(r)) => {
2420+
Some(ConstEnumMemberValue::Number(l * r))
2421+
}
2422+
_ => None,
2423+
}
2424+
}
2425+
BinaryOperator::Division => {
2426+
match (&left, &right) {
2427+
(ConstEnumMemberValue::Number(l), ConstEnumMemberValue::Number(r)) => {
2428+
if *r == 0.0 { None } else { Some(ConstEnumMemberValue::Number(l / r)) }
2429+
}
2430+
_ => None,
2431+
}
2432+
}
2433+
BinaryOperator::ShiftLeft => {
2434+
match (&left, &right) {
2435+
(ConstEnumMemberValue::Number(l), ConstEnumMemberValue::Number(r)) => {
2436+
Some(ConstEnumMemberValue::Number(((*l as i64) << (*r as i64)) as f64))
2437+
}
2438+
_ => None,
2439+
}
2440+
}
2441+
BinaryOperator::BitwiseOR => {
2442+
match (&left, &right) {
2443+
(ConstEnumMemberValue::Number(l), ConstEnumMemberValue::Number(r)) => {
2444+
Some(ConstEnumMemberValue::Number(((*l as i64) | (*r as i64)) as f64))
2445+
}
2446+
_ => None,
2447+
}
2448+
}
2449+
_ => None,
2450+
}
2451+
}
22352452
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//! Const enum value evaluation and storage
2+
//!
3+
//! This module provides functionality for evaluating and storing const enum values
4+
//! during semantic analysis. Const enums are compiled away and their members are
5+
//! inlined as literal values.
6+
7+
use std::collections::HashMap;
8+
use num_bigint::BigInt;
9+
use oxc_span::Span;
10+
use oxc_syntax::symbol::SymbolId;
11+
12+
/// Represents a computed const enum member value
13+
#[derive(Debug, Clone)]
14+
pub enum ConstEnumMemberValue<'a> {
15+
/// String literal value
16+
String(&'a str),
17+
/// Numeric literal value (f64 to handle both integers and floats)
18+
Number(f64),
19+
/// BigInt value
20+
BigInt(BigInt),
21+
/// Boolean value
22+
Boolean(bool),
23+
/// Computed value from other enum members (not stored for now)
24+
Computed,
25+
}
26+
27+
impl<'a> PartialEq for ConstEnumMemberValue<'a> {
28+
fn eq(&self, other: &Self) -> bool {
29+
match (self, other) {
30+
(Self::String(l), Self::String(r)) => l == r,
31+
(Self::Number(l), Self::Number(r)) => l == r,
32+
(Self::BigInt(l), Self::BigInt(r)) => l == r,
33+
(Self::Boolean(l), Self::Boolean(r)) => l == r,
34+
(Self::Computed, Self::Computed) => true,
35+
_ => false,
36+
}
37+
}
38+
}
39+
40+
/// Information about a const enum member
41+
#[derive(Debug, Clone)]
42+
pub struct ConstEnumMemberInfo<'a> {
43+
/// Name of the enum member
44+
pub name: &'a str,
45+
/// Computed value of the member
46+
pub value: ConstEnumMemberValue<'a>,
47+
/// Span of the member declaration
48+
pub span: Span,
49+
/// Whether this member has an explicit initializer
50+
pub has_initializer: bool,
51+
}
52+
53+
/// Information about a const enum
54+
#[derive(Debug, Clone)]
55+
pub struct ConstEnumInfo<'a> {
56+
/// Symbol ID of the const enum
57+
pub symbol_id: SymbolId,
58+
/// Members of the const enum
59+
pub members: HashMap<&'a str, ConstEnumMemberInfo<'a>>,
60+
/// Span of the enum declaration
61+
pub span: Span,
62+
}
63+
64+
/// Storage for all const enum information in a program
65+
#[derive(Debug, Default, Clone)]
66+
pub struct ConstEnumTable<'a> {
67+
/// Map of const enum symbol IDs to their information
68+
pub enums: HashMap<SymbolId, ConstEnumInfo<'a>>,
69+
}
70+
71+
impl<'a> ConstEnumTable<'a> {
72+
/// Create a new const enum table
73+
pub fn new() -> Self {
74+
Self::default()
75+
}
76+
77+
/// Add a const enum to the table
78+
pub fn add_enum(&mut self, symbol_id: SymbolId, enum_info: ConstEnumInfo<'a>) {
79+
self.enums.insert(symbol_id, enum_info);
80+
}
81+
82+
/// Get const enum information by symbol ID
83+
pub fn get_enum(&self, symbol_id: SymbolId) -> Option<&ConstEnumInfo<'a>> {
84+
self.enums.get(&symbol_id)
85+
}
86+
87+
/// Get a const enum member value
88+
pub fn get_member_value(
89+
&self,
90+
symbol_id: SymbolId,
91+
member_name: &str,
92+
) -> Option<&ConstEnumMemberValue<'a>> {
93+
self.enums
94+
.get(&symbol_id)
95+
.and_then(|enum_info| enum_info.members.get(member_name))
96+
.map(|member| &member.value)
97+
}
98+
99+
/// Check if a symbol represents a const enum
100+
pub fn is_const_enum(&self, symbol_id: SymbolId) -> bool {
101+
self.enums.contains_key(&symbol_id)
102+
}
103+
104+
/// Get all const enums
105+
pub fn enums(&self) -> impl Iterator<Item = (&SymbolId, &ConstEnumInfo<'a>)> {
106+
self.enums.iter()
107+
}
108+
}
109+
110+

0 commit comments

Comments
 (0)