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