decy_analyzer/tagged_union_analysis.rs
1//! Tagged union pattern detection (DECY-080).
2//!
3//! Detects C tagged union patterns and extracts variant information.
4//!
5//! # Overview
6//!
7//! This module analyzes C structs to detect the "tagged union" pattern, where a struct
8//! combines an enum discriminant (tag) with a union containing variant data. This is a
9//! common C idiom for creating variant types, but it's unsafe because the compiler doesn't
10//! verify that the tag matches the union field being accessed.
11//!
12//! # Tagged Union Pattern
13//!
14//! A typical C tagged union looks like:
15//!
16//! ```c
17//! enum ValueType { TYPE_INT, TYPE_FLOAT, TYPE_STRING };
18//!
19//! struct Value {
20//! enum ValueType tag; // Discriminant
21//! union { // Payload
22//! int int_val;
23//! float float_val;
24//! char* string_val;
25//! } data;
26//! };
27//! ```
28//!
29//! This module detects such patterns and extracts the tag field, union field, and variant
30//! information, enabling transformation to Rust's type-safe `enum` with pattern matching.
31//!
32//! # Example
33//!
34//! ```no_run
35//! use decy_analyzer::tagged_union_analysis::TaggedUnionAnalyzer;
36//! use decy_hir::{HirStruct, HirStructField, HirType};
37//!
38//! // C struct: struct Value { enum Tag tag; union { int i; float f; } data; };
39//! let struct_def = HirStruct::new(
40//! "Value".to_string(),
41//! vec![
42//! HirStructField::new("tag".to_string(), HirType::Enum("Tag".to_string())),
43//! HirStructField::new("data".to_string(), HirType::Union(vec![
44//! ("i".to_string(), HirType::Int),
45//! ("f".to_string(), HirType::Float),
46//! ])),
47//! ],
48//! );
49//!
50//! let analyzer = TaggedUnionAnalyzer::new();
51//! let info = analyzer.analyze_struct(&struct_def);
52//!
53//! assert!(info.is_some());
54//! let info = info.unwrap();
55//! assert_eq!(info.struct_name, "Value");
56//! assert_eq!(info.tag_field_name, "tag");
57//! assert_eq!(info.union_field_name, "data");
58//! assert_eq!(info.variants.len(), 2);
59//! ```
60//!
61//! # Algorithm
62//!
63//! The detection algorithm:
64//!
65//! 1. Scan struct fields for the first enum type (tag discriminant)
66//! 2. Scan struct fields for the first union type (variant payload)
67//! 3. If both exist and the union is non-empty, extract variant metadata
68//! 4. Return `TaggedUnionInfo` with complete information for code generation
69//!
70//! Empty unions are rejected because they represent invalid tagged unions.
71
72use decy_hir::{HirStruct, HirType};
73
74/// Information about a variant in a tagged union.
75///
76/// Each variant corresponds to a field in the C union, representing one possible
77/// type that the tagged union can hold.
78#[derive(Debug, Clone, PartialEq, Eq)]
79pub struct VariantInfo {
80 /// Name of the variant (union field name).
81 ///
82 /// Example: `"int_val"` for `union { int int_val; float float_val; }`
83 pub name: String,
84
85 /// Type of the variant payload.
86 ///
87 /// Example: `HirType::Int` for the `int int_val` field
88 pub payload_type: HirType,
89}
90
91/// Information about a detected tagged union.
92///
93/// Contains all metadata needed to transform a C tagged union into a Rust enum.
94#[derive(Debug, Clone, PartialEq)]
95pub struct TaggedUnionInfo {
96 /// Name of the struct.
97 ///
98 /// Example: `"Value"` for `struct Value { ... }`
99 pub struct_name: String,
100
101 /// Name of the tag field (enum discriminant).
102 ///
103 /// Example: `"tag"` for `enum Tag tag;`
104 pub tag_field_name: String,
105
106 /// Name of the union field (variant payload).
107 ///
108 /// Example: `"data"` for `union { ... } data;`
109 pub union_field_name: String,
110
111 /// List of variants extracted from the union.
112 ///
113 /// Each variant represents one possible type/value for the tagged union.
114 pub variants: Vec<VariantInfo>,
115}
116
117/// Analyzes structs to detect tagged union patterns.
118///
119/// This analyzer identifies C structs that follow the tagged union idiom and extracts
120/// the necessary metadata for safe transformation to Rust enums.
121pub struct TaggedUnionAnalyzer;
122
123impl TaggedUnionAnalyzer {
124 /// Create a new tagged union analyzer.
125 ///
126 /// # Examples
127 ///
128 /// ```
129 /// use decy_analyzer::tagged_union_analysis::TaggedUnionAnalyzer;
130 ///
131 /// let analyzer = TaggedUnionAnalyzer::new();
132 /// ```
133 pub fn new() -> Self {
134 Self
135 }
136
137 /// Analyze a struct to detect if it's a tagged union pattern.
138 ///
139 /// Returns `Some(TaggedUnionInfo)` if the struct matches the pattern:
140 /// - Has at least one enum field (tag)
141 /// - Has at least one union field (data)
142 /// - Union is non-empty
143 ///
144 /// Returns `None` if the struct doesn't match the pattern.
145 pub fn analyze_struct(&self, struct_def: &HirStruct) -> Option<TaggedUnionInfo> {
146 let fields = struct_def.fields();
147
148 // Find the first enum field (tag)
149 let tag_field = fields
150 .iter()
151 .find(|f| matches!(f.field_type(), HirType::Enum(_)))?;
152
153 // Find the first union field (data)
154 let union_field = fields
155 .iter()
156 .find(|f| matches!(f.field_type(), HirType::Union(_)))?;
157
158 // Extract union variants
159 if let HirType::Union(variants) = union_field.field_type() {
160 // Empty union is not a valid tagged union
161 if variants.is_empty() {
162 return None;
163 }
164
165 // Build variant info
166 let variant_infos: Vec<VariantInfo> = variants
167 .iter()
168 .map(|(name, payload_type)| VariantInfo {
169 name: name.clone(),
170 payload_type: payload_type.clone(),
171 })
172 .collect();
173
174 Some(TaggedUnionInfo {
175 struct_name: struct_def.name().to_string(),
176 tag_field_name: tag_field.name().to_string(),
177 union_field_name: union_field.name().to_string(),
178 variants: variant_infos,
179 })
180 } else {
181 None
182 }
183 }
184}
185
186impl Default for TaggedUnionAnalyzer {
187 fn default() -> Self {
188 Self::new()
189 }
190}