decy_codegen/enum_gen.rs
1//! Enum generation from tagged unions (DECY-081).
2//!
3//! Transforms C tagged unions into type-safe Rust enums with pattern matching.
4//!
5//! # Overview
6//!
7//! This module generates idiomatic Rust `enum` definitions from C tagged union patterns.
8//! It takes the tagged union metadata extracted by the analyzer and produces clean,
9//! type-safe Rust code that eliminates the unsafe union access pattern.
10//!
11//! # C Tagged Union Pattern
12//!
13//! C code often uses the "tagged union" pattern for variant types:
14//!
15//! ```c
16//! enum ValueType { TYPE_INT, TYPE_FLOAT, TYPE_STRING };
17//!
18//! struct Value {
19//! enum ValueType tag; // Discriminant
20//! union { // Payload
21//! int int_value;
22//! float float_value;
23//! char* string_value;
24//! } data;
25//! };
26//! ```
27//!
28//! This is unsafe because:
29//! - The compiler doesn't verify tag matches union field access
30//! - Reading wrong union field causes undefined behavior
31//! - No exhaustiveness checking for tag values
32//!
33//! # Rust Enum Transformation
34//!
35//! This module transforms the unsafe C pattern into safe Rust:
36//!
37//! ```rust
38//! #[derive(Debug, Clone, PartialEq)]
39//! pub enum Value {
40//! Int(i32),
41//! Float(f32),
42//! String(String),
43//! }
44//! ```
45//!
46//! Benefits:
47//! - Type-safe: Compiler ensures tag matches payload
48//! - Exhaustive: Pattern matching requires all variants
49//! - Zero unsafe code in generated output
50//!
51//! # Example
52//!
53//! ```no_run
54//! use decy_analyzer::tagged_union_analysis::TaggedUnionAnalyzer;
55//! use decy_codegen::enum_gen::EnumGenerator;
56//! use decy_hir::{HirStruct, HirStructField, HirType};
57//!
58//! // C: struct Value { enum Tag tag; union { int i; float f; } data; };
59//! let struct_def = HirStruct::new(
60//! "Value".to_string(),
61//! vec![
62//! HirStructField::new("tag".to_string(), HirType::Enum("Tag".to_string())),
63//! HirStructField::new("data".to_string(), HirType::Union(vec![
64//! ("i".to_string(), HirType::Int),
65//! ("f".to_string(), HirType::Float),
66//! ])),
67//! ],
68//! );
69//!
70//! // Analyze tagged union
71//! let analyzer = TaggedUnionAnalyzer::new();
72//! let info = analyzer.analyze_struct(&struct_def).unwrap();
73//!
74//! // Generate Rust enum
75//! let generator = EnumGenerator::new();
76//! let rust_enum = generator.generate_enum(&info);
77//!
78//! // Result:
79//! // #[derive(Debug, Clone, PartialEq)]
80//! // pub enum Value {
81//! // Int(i32),
82//! // Float(f32),
83//! // }
84//! ```
85//!
86//! # Variant Naming
87//!
88//! The generator produces PascalCase variant names from C union field names:
89//!
90//! - Short names (≤2 chars): Derived from type (e.g., `i` with `int` → `Int`)
91//! - Long names: Converted to PascalCase (e.g., `int_value` → `IntValue`)
92//! - Type-based fallback: When field name is non-descriptive
93//!
94//! # Type Mapping
95//!
96//! C types are mapped to safe Rust equivalents:
97//!
98//! | C Type | Rust Type |
99//! |-------------|-----------|
100//! | `int` | `i32` |
101//! | `float` | `f32` |
102//! | `double` | `f64` |
103//! | `char` | `u8` |
104//! | `char*` | `String` |
105//! | `void` | `()` |
106//!
107//! # Quality Guarantees
108//!
109//! - ✅ Zero unsafe code in generated output
110//! - ✅ All variants derive Debug, Clone, PartialEq
111//! - ✅ Public visibility for API usage
112//! - ✅ Valid Rust syntax (parseable by rustc)
113//! - ✅ Exhaustive pattern matching enforced
114
115use decy_analyzer::tagged_union_analysis::TaggedUnionInfo;
116use decy_hir::HirType;
117
118/// Generator for Rust enums from C tagged unions.
119pub struct EnumGenerator;
120
121impl EnumGenerator {
122 /// Create a new enum generator.
123 pub fn new() -> Self {
124 Self
125 }
126
127 /// Generate a Rust enum from tagged union info.
128 ///
129 /// # Arguments
130 ///
131 /// * `info` - Tagged union information from analysis
132 ///
133 /// # Returns
134 ///
135 /// Rust enum definition as a string
136 pub fn generate_enum(&self, info: &TaggedUnionInfo) -> String {
137 let mut result = String::new();
138
139 // Add derive macros
140 result.push_str("#[derive(Debug, Clone, PartialEq)]\n");
141
142 // Add enum declaration
143 result.push_str(&format!("pub enum {} {{\n", info.struct_name));
144
145 // Generate variants
146 for (idx, variant) in info.variants.iter().enumerate() {
147 let variant_name = Self::capitalize_variant_name(&variant.name, &variant.payload_type);
148 let variant_type = Self::map_hir_type_to_rust(&variant.payload_type);
149
150 // Handle void types as unit variants
151 if matches!(variant.payload_type, HirType::Void) {
152 result.push_str(&format!(" {},\n", variant_name));
153 } else {
154 result.push_str(&format!(" {}({})", variant_name, variant_type));
155 if idx < info.variants.len() - 1 {
156 result.push_str(",\n");
157 } else {
158 result.push('\n');
159 }
160 }
161 }
162
163 result.push('}');
164 result
165 }
166
167 /// Capitalize and clean up variant name to PascalCase.
168 ///
169 /// For short names (<=2 chars), derives a better name from the type.
170 /// For longer names, converts to PascalCase.
171 fn capitalize_variant_name(name: &str, payload_type: &HirType) -> String {
172 // For very short names, derive from type
173 if name.len() <= 2 {
174 return Self::type_based_variant_name(payload_type);
175 }
176
177 // Split by underscore and capitalize each part
178 let parts: Vec<String> = name
179 .split('_')
180 .filter(|s| !s.is_empty())
181 .map(|part| {
182 let mut chars = part.chars();
183 match chars.next() {
184 None => String::new(),
185 Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
186 }
187 })
188 .collect();
189
190 if parts.is_empty() {
191 // Fallback: derive from type
192 Self::type_based_variant_name(payload_type)
193 } else {
194 parts.join("")
195 }
196 }
197
198 /// Generate a variant name based on the payload type.
199 fn type_based_variant_name(payload_type: &HirType) -> String {
200 match payload_type {
201 HirType::Void => "None".to_string(),
202 HirType::Int => "Int".to_string(),
203 HirType::Float => "Float".to_string(),
204 HirType::Double => "Double".to_string(),
205 HirType::Char => "Char".to_string(),
206 HirType::Pointer(inner) if matches!(**inner, HirType::Char) => "String".to_string(),
207 HirType::Pointer(inner) if matches!(**inner, HirType::Void) => "Pointer".to_string(),
208 HirType::Pointer(_) => "Pointer".to_string(),
209 HirType::Box(_) => "Boxed".to_string(),
210 HirType::Vec(_) => "Vec".to_string(),
211 HirType::Option(_) => "Option".to_string(),
212 HirType::Reference { .. } => "Ref".to_string(),
213 HirType::Struct(name) => name.clone(),
214 HirType::Enum(name) => name.clone(),
215 HirType::Union(_) => "Union".to_string(),
216 HirType::Array { .. } => "Array".to_string(),
217 HirType::FunctionPointer { .. } => "Function".to_string(),
218 HirType::StringLiteral | HirType::OwnedString | HirType::StringReference => {
219 "String".to_string()
220 }
221 }
222 }
223
224 /// Map HIR type to Rust type string.
225 fn map_hir_type_to_rust(hir_type: &HirType) -> String {
226 match hir_type {
227 HirType::Void => "()".to_string(),
228 HirType::Int => "i32".to_string(),
229 HirType::Float => "f32".to_string(),
230 HirType::Double => "f64".to_string(),
231 HirType::Char => "u8".to_string(),
232 HirType::Pointer(inner) => {
233 // Check if it's char* which should be String
234 if matches!(**inner, HirType::Char) {
235 "String".to_string()
236 } else if matches!(**inner, HirType::Void) {
237 "*mut ()".to_string()
238 } else {
239 format!("*mut {}", Self::map_hir_type_to_rust(inner))
240 }
241 }
242 HirType::Box(inner) => format!("Box<{}>", Self::map_hir_type_to_rust(inner)),
243 HirType::Vec(inner) => format!("Vec<{}>", Self::map_hir_type_to_rust(inner)),
244 HirType::Option(inner) => format!("Option<{}>", Self::map_hir_type_to_rust(inner)),
245 HirType::Reference { inner, mutable } => {
246 if *mutable {
247 format!("&mut {}", Self::map_hir_type_to_rust(inner))
248 } else {
249 format!("&{}", Self::map_hir_type_to_rust(inner))
250 }
251 }
252 HirType::Struct(name) => name.clone(),
253 HirType::Enum(name) => name.clone(),
254 HirType::Union(_) => "/* Union */".to_string(),
255 HirType::Array { element_type, size } => {
256 if let Some(n) = size {
257 format!("[{}; {}]", Self::map_hir_type_to_rust(element_type), n)
258 } else {
259 format!("[{}]", Self::map_hir_type_to_rust(element_type))
260 }
261 }
262 HirType::FunctionPointer {
263 param_types,
264 return_type,
265 } => {
266 let params: Vec<String> =
267 param_types.iter().map(Self::map_hir_type_to_rust).collect();
268 let params_str = params.join(", ");
269 if matches!(**return_type, HirType::Void) {
270 format!("fn({})", params_str)
271 } else {
272 format!(
273 "fn({}) -> {}",
274 params_str,
275 Self::map_hir_type_to_rust(return_type)
276 )
277 }
278 }
279 HirType::StringLiteral => "&str".to_string(),
280 HirType::OwnedString => "String".to_string(),
281 HirType::StringReference => "&str".to_string(),
282 }
283 }
284}
285
286impl Default for EnumGenerator {
287 fn default() -> Self {
288 Self::new()
289 }
290}