Skip to main content

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}