Skip to main content

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::Bool => "Bool".to_string(),
203            HirType::Int => "Int".to_string(),
204            HirType::UnsignedInt => "UInt".to_string(), // DECY-158
205            HirType::Float => "Float".to_string(),
206            HirType::Double => "Double".to_string(),
207            HirType::Char => "Char".to_string(),
208            HirType::SignedChar => "SignedChar".to_string(), // DECY-250
209            HirType::Pointer(inner) if matches!(**inner, HirType::Char) => "String".to_string(),
210            HirType::Pointer(inner) if matches!(**inner, HirType::Void) => "Pointer".to_string(),
211            HirType::Pointer(_) => "Pointer".to_string(),
212            HirType::Box(_) => "Boxed".to_string(),
213            HirType::Vec(_) => "Vec".to_string(),
214            HirType::Option(_) => "Option".to_string(),
215            HirType::Reference { .. } => "Ref".to_string(),
216            HirType::Struct(name) => name.clone(),
217            HirType::Enum(name) => name.clone(),
218            HirType::Union(_) => "Union".to_string(),
219            HirType::Array { .. } => "Array".to_string(),
220            HirType::FunctionPointer { .. } => "Function".to_string(),
221            HirType::StringLiteral | HirType::OwnedString | HirType::StringReference => {
222                "String".to_string()
223            }
224            // DECY-172: Type aliases use the alias name as variant name
225            HirType::TypeAlias(name) => name.clone(),
226        }
227    }
228
229    /// Map HIR type to Rust type string.
230    fn map_hir_type_to_rust(hir_type: &HirType) -> String {
231        match hir_type {
232            HirType::Void => "()".to_string(),
233            HirType::Bool => "bool".to_string(),
234            HirType::Int => "i32".to_string(),
235            HirType::UnsignedInt => "u32".to_string(), // DECY-158
236            HirType::Float => "f32".to_string(),
237            HirType::Double => "f64".to_string(),
238            HirType::Char => "u8".to_string(),
239            HirType::SignedChar => "i8".to_string(), // DECY-250
240            HirType::Pointer(inner) => {
241                // Check if it's char* which should be String
242                if matches!(**inner, HirType::Char) {
243                    "String".to_string()
244                } else if matches!(**inner, HirType::Void) {
245                    "*mut ()".to_string()
246                } else {
247                    format!("*mut {}", Self::map_hir_type_to_rust(inner))
248                }
249            }
250            HirType::Box(inner) => format!("Box<{}>", Self::map_hir_type_to_rust(inner)),
251            HirType::Vec(inner) => format!("Vec<{}>", Self::map_hir_type_to_rust(inner)),
252            HirType::Option(inner) => format!("Option<{}>", Self::map_hir_type_to_rust(inner)),
253            HirType::Reference { inner, mutable } => {
254                if *mutable {
255                    format!("&mut {}", Self::map_hir_type_to_rust(inner))
256                } else {
257                    format!("&{}", Self::map_hir_type_to_rust(inner))
258                }
259            }
260            HirType::Struct(name) => name.clone(),
261            HirType::Enum(name) => name.clone(),
262            HirType::Union(_) => "/* Union */".to_string(),
263            HirType::Array { element_type, size } => {
264                if let Some(n) = size {
265                    format!("[{}; {}]", Self::map_hir_type_to_rust(element_type), n)
266                } else {
267                    format!("[{}]", Self::map_hir_type_to_rust(element_type))
268                }
269            }
270            HirType::FunctionPointer { param_types, return_type } => {
271                let params: Vec<String> =
272                    param_types.iter().map(Self::map_hir_type_to_rust).collect();
273                let params_str = params.join(", ");
274                if matches!(**return_type, HirType::Void) {
275                    format!("fn({})", params_str)
276                } else {
277                    format!("fn({}) -> {}", params_str, Self::map_hir_type_to_rust(return_type))
278                }
279            }
280            HirType::StringLiteral => "&str".to_string(),
281            HirType::OwnedString => "String".to_string(),
282            HirType::StringReference => "&str".to_string(),
283            // DECY-172: Preserve typedef names
284            HirType::TypeAlias(name) => name.clone(),
285        }
286    }
287}
288
289impl Default for EnumGenerator {
290    fn default() -> Self {
291        Self::new()
292    }
293}
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298    use decy_analyzer::tagged_union_analysis::{TaggedUnionInfo, VariantInfo};
299
300    // ============================================================================
301    // ENUM GENERATOR CONSTRUCTION TESTS
302    // ============================================================================
303
304    #[test]
305    fn test_enum_generator_new() {
306        let gen = EnumGenerator::new();
307        // Generator is a unit struct, just verify construction
308        assert!(std::mem::size_of_val(&gen) == 0);
309    }
310
311    #[test]
312    fn test_enum_generator_default() {
313        let gen: EnumGenerator = Default::default();
314        assert!(std::mem::size_of_val(&gen) == 0);
315    }
316
317    // ============================================================================
318    // ENUM GENERATION TESTS
319    // ============================================================================
320
321    #[test]
322    fn test_generate_enum_single_variant() {
323        let gen = EnumGenerator::new();
324        let info = TaggedUnionInfo {
325            struct_name: "Value".to_string(),
326            tag_field_name: "tag".to_string(),
327            union_field_name: "data".to_string(),
328            variants: vec![VariantInfo {
329                name: "int_value".to_string(),
330                payload_type: HirType::Int,
331            }],
332        };
333
334        let result = gen.generate_enum(&info);
335        assert!(result.contains("#[derive(Debug, Clone, PartialEq)]"));
336        assert!(result.contains("pub enum Value"));
337        assert!(result.contains("IntValue(i32)"));
338    }
339
340    #[test]
341    fn test_generate_enum_multiple_variants() {
342        let gen = EnumGenerator::new();
343        let info = TaggedUnionInfo {
344            struct_name: "Value".to_string(),
345            tag_field_name: "tag".to_string(),
346            union_field_name: "data".to_string(),
347            variants: vec![
348                VariantInfo { name: "int_value".to_string(), payload_type: HirType::Int },
349                VariantInfo { name: "float_value".to_string(), payload_type: HirType::Float },
350                VariantInfo { name: "double_value".to_string(), payload_type: HirType::Double },
351            ],
352        };
353
354        let result = gen.generate_enum(&info);
355        assert!(result.contains("IntValue(i32),"));
356        assert!(result.contains("FloatValue(f32),"));
357        assert!(result.contains("DoubleValue(f64)"));
358    }
359
360    #[test]
361    fn test_generate_enum_void_variant() {
362        let gen = EnumGenerator::new();
363        let info = TaggedUnionInfo {
364            struct_name: "Option".to_string(),
365            tag_field_name: "tag".to_string(),
366            union_field_name: "data".to_string(),
367            variants: vec![
368                VariantInfo { name: "none".to_string(), payload_type: HirType::Void },
369                VariantInfo { name: "some_int".to_string(), payload_type: HirType::Int },
370            ],
371        };
372
373        let result = gen.generate_enum(&info);
374        // Void variants should be unit variants (no payload)
375        assert!(result.contains("None,"));
376        assert!(result.contains("SomeInt(i32)"));
377    }
378
379    #[test]
380    fn test_generate_enum_last_variant_no_trailing_comma() {
381        let gen = EnumGenerator::new();
382        let info = TaggedUnionInfo {
383            struct_name: "Test".to_string(),
384            tag_field_name: "tag".to_string(),
385            union_field_name: "data".to_string(),
386            variants: vec![VariantInfo { name: "value".to_string(), payload_type: HirType::Int }],
387        };
388
389        let result = gen.generate_enum(&info);
390        // Last non-void variant should not have trailing comma
391        assert!(result.contains("Value(i32)\n}"));
392    }
393
394    // ============================================================================
395    // VARIANT NAME CAPITALIZATION TESTS
396    // ============================================================================
397
398    #[test]
399    fn test_capitalize_variant_name_snake_case() {
400        let result = EnumGenerator::capitalize_variant_name("int_value", &HirType::Int);
401        assert_eq!(result, "IntValue");
402    }
403
404    #[test]
405    fn test_capitalize_variant_name_short_name_uses_type() {
406        // Short names (<=2 chars) should derive from type
407        let result = EnumGenerator::capitalize_variant_name("i", &HirType::Int);
408        assert_eq!(result, "Int");
409
410        let result = EnumGenerator::capitalize_variant_name("f", &HirType::Float);
411        assert_eq!(result, "Float");
412
413        let result = EnumGenerator::capitalize_variant_name("d", &HirType::Double);
414        assert_eq!(result, "Double");
415    }
416
417    #[test]
418    fn test_capitalize_variant_name_two_char_name() {
419        let result = EnumGenerator::capitalize_variant_name("id", &HirType::Int);
420        assert_eq!(result, "Int"); // 2 chars is still "short"
421    }
422
423    #[test]
424    fn test_capitalize_variant_name_three_char_name() {
425        let result = EnumGenerator::capitalize_variant_name("val", &HirType::Int);
426        assert_eq!(result, "Val"); // 3 chars is not short
427    }
428
429    #[test]
430    fn test_capitalize_variant_name_empty_parts_fallback() {
431        // Name with only underscores should fall back to type-based name
432        let result = EnumGenerator::capitalize_variant_name("___", &HirType::Int);
433        assert_eq!(result, "Int");
434    }
435
436    #[test]
437    fn test_capitalize_variant_name_single_word() {
438        let result = EnumGenerator::capitalize_variant_name("value", &HirType::Int);
439        assert_eq!(result, "Value");
440    }
441
442    #[test]
443    fn test_capitalize_variant_name_multiple_underscores() {
444        let result = EnumGenerator::capitalize_variant_name("my__long__name", &HirType::Int);
445        assert_eq!(result, "MyLongName");
446    }
447
448    // ============================================================================
449    // TYPE-BASED VARIANT NAME TESTS
450    // ============================================================================
451
452    #[test]
453    fn test_type_based_variant_name_primitives() {
454        assert_eq!(EnumGenerator::type_based_variant_name(&HirType::Void), "None");
455        assert_eq!(EnumGenerator::type_based_variant_name(&HirType::Int), "Int");
456        assert_eq!(EnumGenerator::type_based_variant_name(&HirType::UnsignedInt), "UInt");
457        assert_eq!(EnumGenerator::type_based_variant_name(&HirType::Float), "Float");
458        assert_eq!(EnumGenerator::type_based_variant_name(&HirType::Double), "Double");
459        assert_eq!(EnumGenerator::type_based_variant_name(&HirType::Char), "Char");
460    }
461
462    #[test]
463    fn test_type_based_variant_name_char_pointer_is_string() {
464        let char_ptr = HirType::Pointer(Box::new(HirType::Char));
465        assert_eq!(EnumGenerator::type_based_variant_name(&char_ptr), "String");
466    }
467
468    #[test]
469    fn test_type_based_variant_name_void_pointer() {
470        let void_ptr = HirType::Pointer(Box::new(HirType::Void));
471        assert_eq!(EnumGenerator::type_based_variant_name(&void_ptr), "Pointer");
472    }
473
474    #[test]
475    fn test_type_based_variant_name_other_pointer() {
476        let int_ptr = HirType::Pointer(Box::new(HirType::Int));
477        assert_eq!(EnumGenerator::type_based_variant_name(&int_ptr), "Pointer");
478    }
479
480    #[test]
481    fn test_type_based_variant_name_box() {
482        let boxed = HirType::Box(Box::new(HirType::Int));
483        assert_eq!(EnumGenerator::type_based_variant_name(&boxed), "Boxed");
484    }
485
486    #[test]
487    fn test_type_based_variant_name_vec() {
488        let vec = HirType::Vec(Box::new(HirType::Int));
489        assert_eq!(EnumGenerator::type_based_variant_name(&vec), "Vec");
490    }
491
492    #[test]
493    fn test_type_based_variant_name_option() {
494        let opt = HirType::Option(Box::new(HirType::Int));
495        assert_eq!(EnumGenerator::type_based_variant_name(&opt), "Option");
496    }
497
498    #[test]
499    fn test_type_based_variant_name_reference() {
500        let ref_type = HirType::Reference { inner: Box::new(HirType::Int), mutable: false };
501        assert_eq!(EnumGenerator::type_based_variant_name(&ref_type), "Ref");
502    }
503
504    #[test]
505    fn test_type_based_variant_name_struct() {
506        let struct_type = HirType::Struct("MyStruct".to_string());
507        assert_eq!(EnumGenerator::type_based_variant_name(&struct_type), "MyStruct");
508    }
509
510    #[test]
511    fn test_type_based_variant_name_enum() {
512        let enum_type = HirType::Enum("MyEnum".to_string());
513        assert_eq!(EnumGenerator::type_based_variant_name(&enum_type), "MyEnum");
514    }
515
516    #[test]
517    fn test_type_based_variant_name_union() {
518        let union_type = HirType::Union(vec![]);
519        assert_eq!(EnumGenerator::type_based_variant_name(&union_type), "Union");
520    }
521
522    #[test]
523    fn test_type_based_variant_name_array() {
524        let array = HirType::Array { element_type: Box::new(HirType::Int), size: Some(10) };
525        assert_eq!(EnumGenerator::type_based_variant_name(&array), "Array");
526    }
527
528    #[test]
529    fn test_type_based_variant_name_function_pointer() {
530        let fn_ptr = HirType::FunctionPointer {
531            param_types: vec![HirType::Int],
532            return_type: Box::new(HirType::Void),
533        };
534        assert_eq!(EnumGenerator::type_based_variant_name(&fn_ptr), "Function");
535    }
536
537    #[test]
538    fn test_type_based_variant_name_string_types() {
539        assert_eq!(EnumGenerator::type_based_variant_name(&HirType::StringLiteral), "String");
540        assert_eq!(EnumGenerator::type_based_variant_name(&HirType::OwnedString), "String");
541        assert_eq!(EnumGenerator::type_based_variant_name(&HirType::StringReference), "String");
542    }
543
544    #[test]
545    fn test_type_based_variant_name_type_alias() {
546        let alias = HirType::TypeAlias("size_t".to_string());
547        assert_eq!(EnumGenerator::type_based_variant_name(&alias), "size_t");
548    }
549
550    // ============================================================================
551    // TYPE MAPPING TESTS
552    // ============================================================================
553
554    #[test]
555    fn test_map_hir_type_primitives() {
556        assert_eq!(EnumGenerator::map_hir_type_to_rust(&HirType::Void), "()");
557        assert_eq!(EnumGenerator::map_hir_type_to_rust(&HirType::Int), "i32");
558        assert_eq!(EnumGenerator::map_hir_type_to_rust(&HirType::UnsignedInt), "u32");
559        assert_eq!(EnumGenerator::map_hir_type_to_rust(&HirType::Float), "f32");
560        assert_eq!(EnumGenerator::map_hir_type_to_rust(&HirType::Double), "f64");
561        assert_eq!(EnumGenerator::map_hir_type_to_rust(&HirType::Char), "u8");
562    }
563
564    #[test]
565    fn test_map_hir_type_char_pointer_to_string() {
566        let char_ptr = HirType::Pointer(Box::new(HirType::Char));
567        assert_eq!(EnumGenerator::map_hir_type_to_rust(&char_ptr), "String");
568    }
569
570    #[test]
571    fn test_map_hir_type_void_pointer() {
572        let void_ptr = HirType::Pointer(Box::new(HirType::Void));
573        assert_eq!(EnumGenerator::map_hir_type_to_rust(&void_ptr), "*mut ()");
574    }
575
576    #[test]
577    fn test_map_hir_type_other_pointer() {
578        let int_ptr = HirType::Pointer(Box::new(HirType::Int));
579        assert_eq!(EnumGenerator::map_hir_type_to_rust(&int_ptr), "*mut i32");
580    }
581
582    #[test]
583    fn test_map_hir_type_box() {
584        let boxed = HirType::Box(Box::new(HirType::Int));
585        assert_eq!(EnumGenerator::map_hir_type_to_rust(&boxed), "Box<i32>");
586    }
587
588    #[test]
589    fn test_map_hir_type_vec() {
590        let vec = HirType::Vec(Box::new(HirType::Float));
591        assert_eq!(EnumGenerator::map_hir_type_to_rust(&vec), "Vec<f32>");
592    }
593
594    #[test]
595    fn test_map_hir_type_option() {
596        let opt = HirType::Option(Box::new(HirType::Double));
597        assert_eq!(EnumGenerator::map_hir_type_to_rust(&opt), "Option<f64>");
598    }
599
600    #[test]
601    fn test_map_hir_type_immutable_reference() {
602        let ref_type = HirType::Reference { inner: Box::new(HirType::Int), mutable: false };
603        assert_eq!(EnumGenerator::map_hir_type_to_rust(&ref_type), "&i32");
604    }
605
606    #[test]
607    fn test_map_hir_type_mutable_reference() {
608        let ref_type = HirType::Reference { inner: Box::new(HirType::Int), mutable: true };
609        assert_eq!(EnumGenerator::map_hir_type_to_rust(&ref_type), "&mut i32");
610    }
611
612    #[test]
613    fn test_map_hir_type_struct() {
614        let struct_type = HirType::Struct("Point".to_string());
615        assert_eq!(EnumGenerator::map_hir_type_to_rust(&struct_type), "Point");
616    }
617
618    #[test]
619    fn test_map_hir_type_enum() {
620        let enum_type = HirType::Enum("Color".to_string());
621        assert_eq!(EnumGenerator::map_hir_type_to_rust(&enum_type), "Color");
622    }
623
624    #[test]
625    fn test_map_hir_type_union() {
626        let union_type = HirType::Union(vec![]);
627        assert_eq!(EnumGenerator::map_hir_type_to_rust(&union_type), "/* Union */");
628    }
629
630    #[test]
631    fn test_map_hir_type_array_with_size() {
632        let array = HirType::Array { element_type: Box::new(HirType::Int), size: Some(10) };
633        assert_eq!(EnumGenerator::map_hir_type_to_rust(&array), "[i32; 10]");
634    }
635
636    #[test]
637    fn test_map_hir_type_array_without_size() {
638        let array = HirType::Array { element_type: Box::new(HirType::Int), size: None };
639        assert_eq!(EnumGenerator::map_hir_type_to_rust(&array), "[i32]");
640    }
641
642    #[test]
643    fn test_map_hir_type_function_pointer_void_return() {
644        let fn_ptr = HirType::FunctionPointer {
645            param_types: vec![HirType::Int, HirType::Float],
646            return_type: Box::new(HirType::Void),
647        };
648        assert_eq!(EnumGenerator::map_hir_type_to_rust(&fn_ptr), "fn(i32, f32)");
649    }
650
651    #[test]
652    fn test_map_hir_type_function_pointer_with_return() {
653        let fn_ptr = HirType::FunctionPointer {
654            param_types: vec![HirType::Int],
655            return_type: Box::new(HirType::Int),
656        };
657        assert_eq!(EnumGenerator::map_hir_type_to_rust(&fn_ptr), "fn(i32) -> i32");
658    }
659
660    #[test]
661    fn test_map_hir_type_function_pointer_no_params() {
662        let fn_ptr =
663            HirType::FunctionPointer { param_types: vec![], return_type: Box::new(HirType::Int) };
664        assert_eq!(EnumGenerator::map_hir_type_to_rust(&fn_ptr), "fn() -> i32");
665    }
666
667    #[test]
668    fn test_map_hir_type_string_types() {
669        assert_eq!(EnumGenerator::map_hir_type_to_rust(&HirType::StringLiteral), "&str");
670        assert_eq!(EnumGenerator::map_hir_type_to_rust(&HirType::OwnedString), "String");
671        assert_eq!(EnumGenerator::map_hir_type_to_rust(&HirType::StringReference), "&str");
672    }
673
674    #[test]
675    fn test_map_hir_type_type_alias() {
676        let alias = HirType::TypeAlias("size_t".to_string());
677        assert_eq!(EnumGenerator::map_hir_type_to_rust(&alias), "size_t");
678    }
679
680    // ============================================================================
681    // INTEGRATION TESTS - COMPLETE ENUM GENERATION
682    // ============================================================================
683
684    #[test]
685    fn test_generate_enum_complete_value_type() {
686        let gen = EnumGenerator::new();
687        let info = TaggedUnionInfo {
688            struct_name: "Value".to_string(),
689            tag_field_name: "type".to_string(),
690            union_field_name: "data".to_string(),
691            variants: vec![
692                VariantInfo {
693                    name: "i".to_string(), // Short name (1 char) → uses type
694                    payload_type: HirType::Int,
695                },
696                VariantInfo {
697                    name: "f".to_string(), // Short name (1 char) → uses type
698                    payload_type: HirType::Float,
699                },
700                VariantInfo {
701                    name: "s".to_string(), // Short name (1 char) → uses type
702                    payload_type: HirType::Pointer(Box::new(HirType::Char)),
703                },
704            ],
705        };
706
707        let result = gen.generate_enum(&info);
708
709        // Check derives
710        assert!(result.contains("#[derive(Debug, Clone, PartialEq)]"));
711
712        // Check enum declaration
713        assert!(result.contains("pub enum Value {"));
714
715        // Short names (<=2 chars) should use type-based variants
716        assert!(result.contains("Int(i32)"));
717        assert!(result.contains("Float(f32)"));
718        assert!(result.contains("String(String)")); // char* → String (type-based)
719    }
720
721    #[test]
722    fn test_generate_enum_with_nested_types() {
723        let gen = EnumGenerator::new();
724        let info = TaggedUnionInfo {
725            struct_name: "Container".to_string(),
726            tag_field_name: "kind".to_string(),
727            union_field_name: "data".to_string(),
728            variants: vec![
729                VariantInfo {
730                    name: "boxed_int".to_string(),
731                    payload_type: HirType::Box(Box::new(HirType::Int)),
732                },
733                VariantInfo {
734                    name: "vec_float".to_string(),
735                    payload_type: HirType::Vec(Box::new(HirType::Float)),
736                },
737            ],
738        };
739
740        let result = gen.generate_enum(&info);
741        assert!(result.contains("BoxedInt(Box<i32>)"));
742        assert!(result.contains("VecFloat(Vec<f32>)"));
743    }
744}