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