Skip to main content

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::Bool => HirExpression::IntLiteral(0), // false
44            HirType::Int | HirType::UnsignedInt => HirExpression::IntLiteral(0), // DECY-158
45            HirType::Float | HirType::Double => {
46                // We'll use 0 for now since we only have IntLiteral
47                // Future: Add FloatLiteral to HirExpression
48                HirExpression::IntLiteral(0)
49            }
50            HirType::Char | HirType::SignedChar => HirExpression::IntLiteral(0), // DECY-250
51            HirType::Option(_) => {
52                // Option types default to None
53                HirExpression::NullLiteral
54            }
55            HirType::Void
56            | HirType::Pointer(_)
57            | HirType::Box(_)
58            | HirType::Vec(_)
59            | HirType::Reference { .. }
60            | HirType::Struct(_)
61            | HirType::Enum(_)
62            | HirType::Union(_)
63            | HirType::Array { .. }
64            | HirType::FunctionPointer { .. }
65            | HirType::StringLiteral
66            | HirType::OwnedString
67            | HirType::StringReference
68            | HirType::TypeAlias(_) => {
69                // Fallback for types that don't have simple defaults
70                HirExpression::IntLiteral(0)
71            }
72        }
73    }
74
75    /// Transform a statement containing malloc into one using Box::new().
76    ///
77    /// Takes a VariableDeclaration or Assignment with malloc and transforms it.
78    /// Converts both the malloc expression AND the variable type from Pointer to Box.
79    pub fn transform_statement(
80        &self,
81        stmt: &HirStatement,
82        _candidate: &BoxCandidate,
83    ) -> HirStatement {
84        match stmt {
85            HirStatement::VariableDeclaration { name, var_type, initializer } => {
86                if let Some(HirExpression::FunctionCall { function, .. }) = initializer {
87                    if function == "malloc" {
88                        // Extract pointee type from pointer type
89                        if let HirType::Pointer(pointee) = var_type {
90                            let box_expr = self.transform_malloc_to_box(
91                                initializer.as_ref().expect("initializer present"),
92                                pointee,
93                            );
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 { function, arguments } => {
150                assert_eq!(function, "Box::new");
151                assert_eq!(arguments.len(), 1);
152                assert_eq!(arguments[0], HirExpression::IntLiteral(0));
153            }
154            _ => panic!("Expected FunctionCall"),
155        }
156    }
157
158    #[test]
159    fn test_transform_variable_declaration_with_malloc() {
160        let transformer = BoxTransformer::new();
161        let candidate =
162            BoxCandidate { variable: "ptr".to_string(), malloc_index: 0, free_index: None };
163
164        let stmt = HirStatement::VariableDeclaration {
165            name: "ptr".to_string(),
166            var_type: HirType::Pointer(Box::new(HirType::Int)),
167            initializer: Some(HirExpression::FunctionCall {
168                function: "malloc".to_string(),
169                arguments: vec![HirExpression::IntLiteral(100)],
170            }),
171        };
172
173        let transformed = transformer.transform_statement(&stmt, &candidate);
174
175        match transformed {
176            HirStatement::VariableDeclaration {
177                name,
178                initializer: Some(HirExpression::FunctionCall { function, .. }),
179                ..
180            } => {
181                assert_eq!(name, "ptr");
182                assert_eq!(function, "Box::new");
183            }
184            _ => panic!("Expected VariableDeclaration with Box::new"),
185        }
186    }
187
188    #[test]
189    fn test_transform_assignment_with_malloc() {
190        let transformer = BoxTransformer::new();
191        let candidate =
192            BoxCandidate { variable: "ptr".to_string(), malloc_index: 0, free_index: None };
193
194        let stmt = HirStatement::Assignment {
195            target: "ptr".to_string(),
196            value: HirExpression::FunctionCall {
197                function: "malloc".to_string(),
198                arguments: vec![HirExpression::IntLiteral(50)],
199            },
200        };
201
202        let transformed = transformer.transform_statement(&stmt, &candidate);
203
204        match transformed {
205            HirStatement::Assignment {
206                target,
207                value: HirExpression::FunctionCall { function, .. },
208            } => {
209                assert_eq!(target, "ptr");
210                assert_eq!(function, "Box::new");
211            }
212            _ => panic!("Expected Assignment with Box::new"),
213        }
214    }
215
216    #[test]
217    fn test_non_malloc_statement_unchanged() {
218        let transformer = BoxTransformer::new();
219        let candidate =
220            BoxCandidate { variable: "x".to_string(), malloc_index: 0, free_index: None };
221
222        let stmt = HirStatement::VariableDeclaration {
223            name: "x".to_string(),
224            var_type: HirType::Int,
225            initializer: Some(HirExpression::IntLiteral(42)),
226        };
227
228        let transformed = transformer.transform_statement(&stmt, &candidate);
229        assert_eq!(transformed, stmt);
230    }
231
232    #[test]
233    fn test_default_value_generation() {
234        let transformer = BoxTransformer::new();
235
236        let int_default = transformer.default_value_for_type(&HirType::Int);
237        assert_eq!(int_default, HirExpression::IntLiteral(0));
238
239        let char_default = transformer.default_value_for_type(&HirType::Char);
240        assert_eq!(char_default, HirExpression::IntLiteral(0));
241    }
242
243    #[test]
244    fn test_default_value_float_double() {
245        let transformer = BoxTransformer::new();
246        assert_eq!(
247            transformer.default_value_for_type(&HirType::Float),
248            HirExpression::IntLiteral(0)
249        );
250        assert_eq!(
251            transformer.default_value_for_type(&HirType::Double),
252            HirExpression::IntLiteral(0)
253        );
254    }
255
256    #[test]
257    fn test_default_value_option() {
258        let transformer = BoxTransformer::new();
259        assert_eq!(
260            transformer.default_value_for_type(&HirType::Option(Box::new(HirType::Int))),
261            HirExpression::NullLiteral
262        );
263    }
264
265    #[test]
266    fn test_default_value_fallback_types() {
267        let transformer = BoxTransformer::new();
268        // Struct falls through to the catch-all arm
269        assert_eq!(
270            transformer.default_value_for_type(&HirType::Struct("Foo".to_string())),
271            HirExpression::IntLiteral(0)
272        );
273        // Vec also falls through
274        assert_eq!(
275            transformer.default_value_for_type(&HirType::Vec(Box::new(HirType::Int))),
276            HirExpression::IntLiteral(0)
277        );
278    }
279
280    #[test]
281    fn test_box_transformer_default_trait() {
282        let transformer = BoxTransformer::default();
283        let malloc_expr = HirExpression::FunctionCall {
284            function: "malloc".to_string(),
285            arguments: vec![HirExpression::IntLiteral(64)],
286        };
287        let result = transformer.transform_malloc_to_box(&malloc_expr, &HirType::Int);
288        match result {
289            HirExpression::FunctionCall { function, .. } => assert_eq!(function, "Box::new"),
290            _ => panic!("Expected FunctionCall"),
291        }
292    }
293
294    #[test]
295    fn test_transform_generates_box_type() {
296        let transformer = BoxTransformer::new();
297        let candidate =
298            BoxCandidate { variable: "ptr".to_string(), malloc_index: 0, free_index: None };
299
300        let stmt = HirStatement::VariableDeclaration {
301            name: "ptr".to_string(),
302            var_type: HirType::Pointer(Box::new(HirType::Int)),
303            initializer: Some(HirExpression::FunctionCall {
304                function: "malloc".to_string(),
305                arguments: vec![HirExpression::IntLiteral(100)],
306            }),
307        };
308
309        let transformed = transformer.transform_statement(&stmt, &candidate);
310
311        match transformed {
312            HirStatement::VariableDeclaration {
313                name,
314                var_type: HirType::Box(inner),
315                initializer: Some(HirExpression::FunctionCall { function, .. }),
316                ..
317            } => {
318                assert_eq!(name, "ptr");
319                assert_eq!(function, "Box::new");
320                assert_eq!(*inner, HirType::Int);
321            }
322            _ => panic!("Expected VariableDeclaration with Box<T> type"),
323        }
324    }
325
326    #[test]
327    fn test_box_type_with_different_pointee() {
328        let transformer = BoxTransformer::new();
329        let candidate =
330            BoxCandidate { variable: "data".to_string(), malloc_index: 0, free_index: None };
331
332        let stmt = HirStatement::VariableDeclaration {
333            name: "data".to_string(),
334            var_type: HirType::Pointer(Box::new(HirType::Char)),
335            initializer: Some(HirExpression::FunctionCall {
336                function: "malloc".to_string(),
337                arguments: vec![HirExpression::IntLiteral(50)],
338            }),
339        };
340
341        let transformed = transformer.transform_statement(&stmt, &candidate);
342
343        match transformed {
344            HirStatement::VariableDeclaration { var_type: HirType::Box(inner), .. } => {
345                assert_eq!(*inner, HirType::Char);
346            }
347            _ => panic!("Expected Box<char> type"),
348        }
349    }
350}
351
352#[cfg(test)]
353mod property_tests {
354    use super::*;
355    use proptest::prelude::*;
356
357    proptest! {
358        /// Property: Transform never panics
359        #[test]
360        fn property_transform_never_panics(
361            var_name in "[a-z_][a-z0-9_]{0,10}",
362            size in 1i32..1000
363        ) {
364            let transformer = BoxTransformer::new();
365            let candidate = BoxCandidate {
366                variable: var_name.clone(),
367                malloc_index: 0,
368                free_index: None,
369            };
370
371            let stmt = HirStatement::VariableDeclaration {
372                name: var_name,
373                var_type: HirType::Pointer(Box::new(HirType::Int)),
374                initializer: Some(HirExpression::FunctionCall {
375                    function: "malloc".to_string(),
376                    arguments: vec![HirExpression::IntLiteral(size)],
377                }),
378            };
379
380            let _transformed = transformer.transform_statement(&stmt, &candidate);
381            // If we get here without panic, test passes
382        }
383
384        /// Property: Transformed malloc always becomes Box::new
385        #[test]
386        fn property_malloc_becomes_box_new(
387            size in 1i32..1000
388        ) {
389            let transformer = BoxTransformer::new();
390            let malloc_expr = HirExpression::FunctionCall {
391                function: "malloc".to_string(),
392                arguments: vec![HirExpression::IntLiteral(size)],
393            };
394
395            let box_expr = transformer.transform_malloc_to_box(&malloc_expr, &HirType::Int);
396
397            match box_expr {
398                HirExpression::FunctionCall { function, .. } => {
399                    prop_assert_eq!(function, "Box::new");
400                }
401                _ => prop_assert!(false, "Expected FunctionCall"),
402            }
403        }
404
405        /// Property: Box::new always has exactly one argument
406        #[test]
407        fn property_box_new_has_one_arg(
408            size in 1i32..1000
409        ) {
410            let transformer = BoxTransformer::new();
411            let malloc_expr = HirExpression::FunctionCall {
412                function: "malloc".to_string(),
413                arguments: vec![HirExpression::IntLiteral(size)],
414            };
415
416            let box_expr = transformer.transform_malloc_to_box(&malloc_expr, &HirType::Int);
417
418            match box_expr {
419                HirExpression::FunctionCall { arguments, .. } => {
420                    prop_assert_eq!(arguments.len(), 1);
421                }
422                _ => prop_assert!(false, "Expected FunctionCall"),
423            }
424        }
425
426        /// Property: Transform preserves variable name
427        #[test]
428        fn property_transform_preserves_name(
429            var_name in "[a-z_][a-z0-9_]{0,10}",
430            size in 1i32..1000
431        ) {
432            let transformer = BoxTransformer::new();
433            let candidate = BoxCandidate {
434                variable: var_name.clone(),
435                malloc_index: 0,
436                free_index: None,
437            };
438
439            let stmt = HirStatement::VariableDeclaration {
440                name: var_name.clone(),
441                var_type: HirType::Pointer(Box::new(HirType::Int)),
442                initializer: Some(HirExpression::FunctionCall {
443                    function: "malloc".to_string(),
444                    arguments: vec![HirExpression::IntLiteral(size)],
445                }),
446            };
447
448            let transformed = transformer.transform_statement(&stmt, &candidate);
449
450            match transformed {
451                HirStatement::VariableDeclaration { name, .. } => {
452                    prop_assert_eq!(&name, &var_name);
453                }
454                _ => prop_assert!(false, "Expected VariableDeclaration"),
455            }
456        }
457    }
458}