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}