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