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