decy_codegen/
box_transform.rs

1//! `Box<T>` transformations for replacing malloc/free patterns.
2//!
3//! Transforms malloc() calls into safe Rust `Box::new()` expressions.
4
5use decy_analyzer::patterns::BoxCandidate;
6use decy_hir::{HirExpression, HirStatement, HirType};
7
8/// Transformer for converting malloc patterns to `Box::new()`.
9#[derive(Debug, Clone)]
10pub struct BoxTransformer;
11
12impl BoxTransformer {
13    /// Create a new Box transformer.
14    pub fn new() -> Self {
15        Self
16    }
17
18    /// Transform a malloc expression into a `Box::new()` expression.
19    ///
20    /// Converts patterns like:
21    /// - `malloc(sizeof(T))` → `Box::new(T::default())`
22    /// - `malloc(size)` → `Box::new(T::default())`
23    ///
24    /// For now, we generate a default-initialized Box since we don't have
25    /// sizeof analysis yet. This will be enhanced in future phases.
26    pub fn transform_malloc_to_box(
27        &self,
28        _malloc_expr: &HirExpression,
29        pointee_type: &HirType,
30    ) -> HirExpression {
31        // Generate Box::new(default_value) based on pointee type
32        let default_value = self.default_value_for_type(pointee_type);
33
34        HirExpression::FunctionCall {
35            function: "Box::new".to_string(),
36            arguments: vec![default_value],
37        }
38    }
39
40    /// Generate a default value expression for a type.
41    fn default_value_for_type(&self, hir_type: &HirType) -> HirExpression {
42        match hir_type {
43            HirType::Int => HirExpression::IntLiteral(0),
44            HirType::Float | HirType::Double => {
45                // We'll use 0 for now since we only have IntLiteral
46                // Future: Add FloatLiteral to HirExpression
47                HirExpression::IntLiteral(0)
48            }
49            HirType::Char => HirExpression::IntLiteral(0),
50            HirType::Option(_) => {
51                // Option types default to None
52                HirExpression::NullLiteral
53            }
54            HirType::Void
55            | HirType::Pointer(_)
56            | HirType::Box(_)
57            | HirType::Vec(_)
58            | HirType::Reference { .. }
59            | HirType::Struct(_)
60            | HirType::Enum(_)
61            | HirType::Union(_)
62            | HirType::Array { .. }
63            | HirType::FunctionPointer { .. }
64            | HirType::StringLiteral
65            | HirType::OwnedString
66            | HirType::StringReference => {
67                // Fallback for types that don't have simple defaults
68                HirExpression::IntLiteral(0)
69            }
70        }
71    }
72
73    /// Transform a statement containing malloc into one using Box::new().
74    ///
75    /// Takes a VariableDeclaration or Assignment with malloc and transforms it.
76    /// Converts both the malloc expression AND the variable type from Pointer to Box.
77    pub fn transform_statement(
78        &self,
79        stmt: &HirStatement,
80        _candidate: &BoxCandidate,
81    ) -> HirStatement {
82        match stmt {
83            HirStatement::VariableDeclaration {
84                name,
85                var_type,
86                initializer,
87            } => {
88                if let Some(HirExpression::FunctionCall { function, .. }) = initializer {
89                    if function == "malloc" {
90                        // Extract pointee type from pointer type
91                        if let HirType::Pointer(pointee) = var_type {
92                            let box_expr = self
93                                .transform_malloc_to_box(initializer.as_ref().unwrap(), pointee);
94
95                            // Convert Pointer type to Box type
96                            let box_type = HirType::Box(pointee.clone());
97
98                            return HirStatement::VariableDeclaration {
99                                name: name.clone(),
100                                var_type: box_type,
101                                initializer: Some(box_expr),
102                            };
103                        }
104                    }
105                }
106                stmt.clone()
107            }
108            HirStatement::Assignment { target, value } => {
109                if let HirExpression::FunctionCall { function, .. } = value {
110                    if function == "malloc" {
111                        // For assignments, we don't have type info directly
112                        // Assume int pointer for now (will be enhanced with type inference)
113                        let box_expr = self.transform_malloc_to_box(value, &HirType::Int);
114
115                        return HirStatement::Assignment {
116                            target: target.clone(),
117                            value: box_expr,
118                        };
119                    }
120                }
121                stmt.clone()
122            }
123            _ => stmt.clone(),
124        }
125    }
126}
127
128impl Default for BoxTransformer {
129    fn default() -> Self {
130        Self::new()
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_transform_malloc_to_box_int() {
140        let transformer = BoxTransformer::new();
141        let malloc_expr = HirExpression::FunctionCall {
142            function: "malloc".to_string(),
143            arguments: vec![HirExpression::IntLiteral(100)],
144        };
145
146        let box_expr = transformer.transform_malloc_to_box(&malloc_expr, &HirType::Int);
147
148        match box_expr {
149            HirExpression::FunctionCall {
150                function,
151                arguments,
152            } => {
153                assert_eq!(function, "Box::new");
154                assert_eq!(arguments.len(), 1);
155                assert_eq!(arguments[0], HirExpression::IntLiteral(0));
156            }
157            _ => panic!("Expected FunctionCall"),
158        }
159    }
160
161    #[test]
162    fn test_transform_variable_declaration_with_malloc() {
163        let transformer = BoxTransformer::new();
164        let candidate = BoxCandidate {
165            variable: "ptr".to_string(),
166            malloc_index: 0,
167            free_index: None,
168        };
169
170        let stmt = HirStatement::VariableDeclaration {
171            name: "ptr".to_string(),
172            var_type: HirType::Pointer(Box::new(HirType::Int)),
173            initializer: Some(HirExpression::FunctionCall {
174                function: "malloc".to_string(),
175                arguments: vec![HirExpression::IntLiteral(100)],
176            }),
177        };
178
179        let transformed = transformer.transform_statement(&stmt, &candidate);
180
181        match transformed {
182            HirStatement::VariableDeclaration {
183                name,
184                initializer: Some(HirExpression::FunctionCall { function, .. }),
185                ..
186            } => {
187                assert_eq!(name, "ptr");
188                assert_eq!(function, "Box::new");
189            }
190            _ => panic!("Expected VariableDeclaration with Box::new"),
191        }
192    }
193
194    #[test]
195    fn test_transform_assignment_with_malloc() {
196        let transformer = BoxTransformer::new();
197        let candidate = BoxCandidate {
198            variable: "ptr".to_string(),
199            malloc_index: 0,
200            free_index: None,
201        };
202
203        let stmt = HirStatement::Assignment {
204            target: "ptr".to_string(),
205            value: HirExpression::FunctionCall {
206                function: "malloc".to_string(),
207                arguments: vec![HirExpression::IntLiteral(50)],
208            },
209        };
210
211        let transformed = transformer.transform_statement(&stmt, &candidate);
212
213        match transformed {
214            HirStatement::Assignment {
215                target,
216                value: HirExpression::FunctionCall { function, .. },
217            } => {
218                assert_eq!(target, "ptr");
219                assert_eq!(function, "Box::new");
220            }
221            _ => panic!("Expected Assignment with Box::new"),
222        }
223    }
224
225    #[test]
226    fn test_non_malloc_statement_unchanged() {
227        let transformer = BoxTransformer::new();
228        let candidate = BoxCandidate {
229            variable: "x".to_string(),
230            malloc_index: 0,
231            free_index: None,
232        };
233
234        let stmt = HirStatement::VariableDeclaration {
235            name: "x".to_string(),
236            var_type: HirType::Int,
237            initializer: Some(HirExpression::IntLiteral(42)),
238        };
239
240        let transformed = transformer.transform_statement(&stmt, &candidate);
241        assert_eq!(transformed, stmt);
242    }
243
244    #[test]
245    fn test_default_value_generation() {
246        let transformer = BoxTransformer::new();
247
248        let int_default = transformer.default_value_for_type(&HirType::Int);
249        assert_eq!(int_default, HirExpression::IntLiteral(0));
250
251        let char_default = transformer.default_value_for_type(&HirType::Char);
252        assert_eq!(char_default, HirExpression::IntLiteral(0));
253    }
254
255    #[test]
256    fn test_transform_generates_box_type() {
257        let transformer = BoxTransformer::new();
258        let candidate = BoxCandidate {
259            variable: "ptr".to_string(),
260            malloc_index: 0,
261            free_index: None,
262        };
263
264        let stmt = HirStatement::VariableDeclaration {
265            name: "ptr".to_string(),
266            var_type: HirType::Pointer(Box::new(HirType::Int)),
267            initializer: Some(HirExpression::FunctionCall {
268                function: "malloc".to_string(),
269                arguments: vec![HirExpression::IntLiteral(100)],
270            }),
271        };
272
273        let transformed = transformer.transform_statement(&stmt, &candidate);
274
275        match transformed {
276            HirStatement::VariableDeclaration {
277                name,
278                var_type: HirType::Box(inner),
279                initializer: Some(HirExpression::FunctionCall { function, .. }),
280                ..
281            } => {
282                assert_eq!(name, "ptr");
283                assert_eq!(function, "Box::new");
284                assert_eq!(*inner, HirType::Int);
285            }
286            _ => panic!("Expected VariableDeclaration with Box<T> type"),
287        }
288    }
289
290    #[test]
291    fn test_box_type_with_different_pointee() {
292        let transformer = BoxTransformer::new();
293        let candidate = BoxCandidate {
294            variable: "data".to_string(),
295            malloc_index: 0,
296            free_index: None,
297        };
298
299        let stmt = HirStatement::VariableDeclaration {
300            name: "data".to_string(),
301            var_type: HirType::Pointer(Box::new(HirType::Char)),
302            initializer: Some(HirExpression::FunctionCall {
303                function: "malloc".to_string(),
304                arguments: vec![HirExpression::IntLiteral(50)],
305            }),
306        };
307
308        let transformed = transformer.transform_statement(&stmt, &candidate);
309
310        match transformed {
311            HirStatement::VariableDeclaration {
312                var_type: HirType::Box(inner),
313                ..
314            } => {
315                assert_eq!(*inner, HirType::Char);
316            }
317            _ => panic!("Expected Box<char> type"),
318        }
319    }
320}
321
322#[cfg(test)]
323mod property_tests {
324    use super::*;
325    use proptest::prelude::*;
326
327    proptest! {
328        /// Property: Transform never panics
329        #[test]
330        fn property_transform_never_panics(
331            var_name in "[a-z_][a-z0-9_]{0,10}",
332            size in 1i32..1000
333        ) {
334            let transformer = BoxTransformer::new();
335            let candidate = BoxCandidate {
336                variable: var_name.clone(),
337                malloc_index: 0,
338                free_index: None,
339            };
340
341            let stmt = HirStatement::VariableDeclaration {
342                name: var_name,
343                var_type: HirType::Pointer(Box::new(HirType::Int)),
344                initializer: Some(HirExpression::FunctionCall {
345                    function: "malloc".to_string(),
346                    arguments: vec![HirExpression::IntLiteral(size)],
347                }),
348            };
349
350            let _transformed = transformer.transform_statement(&stmt, &candidate);
351            // If we get here without panic, test passes
352        }
353
354        /// Property: Transformed malloc always becomes Box::new
355        #[test]
356        fn property_malloc_becomes_box_new(
357            size in 1i32..1000
358        ) {
359            let transformer = BoxTransformer::new();
360            let malloc_expr = HirExpression::FunctionCall {
361                function: "malloc".to_string(),
362                arguments: vec![HirExpression::IntLiteral(size)],
363            };
364
365            let box_expr = transformer.transform_malloc_to_box(&malloc_expr, &HirType::Int);
366
367            match box_expr {
368                HirExpression::FunctionCall { function, .. } => {
369                    prop_assert_eq!(function, "Box::new");
370                }
371                _ => prop_assert!(false, "Expected FunctionCall"),
372            }
373        }
374
375        /// Property: Box::new always has exactly one argument
376        #[test]
377        fn property_box_new_has_one_arg(
378            size in 1i32..1000
379        ) {
380            let transformer = BoxTransformer::new();
381            let malloc_expr = HirExpression::FunctionCall {
382                function: "malloc".to_string(),
383                arguments: vec![HirExpression::IntLiteral(size)],
384            };
385
386            let box_expr = transformer.transform_malloc_to_box(&malloc_expr, &HirType::Int);
387
388            match box_expr {
389                HirExpression::FunctionCall { arguments, .. } => {
390                    prop_assert_eq!(arguments.len(), 1);
391                }
392                _ => prop_assert!(false, "Expected FunctionCall"),
393            }
394        }
395
396        /// Property: Transform preserves variable name
397        #[test]
398        fn property_transform_preserves_name(
399            var_name in "[a-z_][a-z0-9_]{0,10}",
400            size in 1i32..1000
401        ) {
402            let transformer = BoxTransformer::new();
403            let candidate = BoxCandidate {
404                variable: var_name.clone(),
405                malloc_index: 0,
406                free_index: None,
407            };
408
409            let stmt = HirStatement::VariableDeclaration {
410                name: var_name.clone(),
411                var_type: HirType::Pointer(Box::new(HirType::Int)),
412                initializer: Some(HirExpression::FunctionCall {
413                    function: "malloc".to_string(),
414                    arguments: vec![HirExpression::IntLiteral(size)],
415                }),
416            };
417
418            let transformed = transformer.transform_statement(&stmt, &candidate);
419
420            match transformed {
421                HirStatement::VariableDeclaration { name, .. } => {
422                    prop_assert_eq!(&name, &var_name);
423                }
424                _ => prop_assert!(false, "Expected VariableDeclaration"),
425            }
426        }
427    }
428}