Skip to main content

decy_codegen/
func_gen.rs

1//! Function, struct, enum, typedef, constant, and global variable code generation.
2//!
3//! Contains all methods related to generating Rust code from HIR top-level
4//! declarations: function signatures, function bodies with ownership analysis,
5//! struct/enum definitions, typedefs, constants, and global variables.
6
7use super::CodeGenerator;
8use decy_hir::{BinaryOperator, HirExpression, HirFunction, HirStatement, HirType};
9use decy_ownership::lifetime_gen::{AnnotatedSignature, AnnotatedType};
10
11impl CodeGenerator {
12    /// Generate a function signature from HIR.
13    ///
14    /// # Examples
15    ///
16    /// ```
17    /// use decy_codegen::CodeGenerator;
18    /// use decy_hir::{HirFunction, HirType};
19    ///
20    /// let func = HirFunction::new("test".to_string(), HirType::Void, vec![]);
21    /// let codegen = CodeGenerator::new();
22    /// let sig = codegen.generate_signature(&func);
23    ///
24    /// assert_eq!(sig, "fn test()");
25    /// ```
26    pub fn generate_signature(&self, func: &HirFunction) -> String {
27        // DECY-076 GREEN: Generate lifetime annotations using LifetimeAnnotator
28        use decy_ownership::lifetime_gen::LifetimeAnnotator;
29        let lifetime_annotator = LifetimeAnnotator::new();
30        let annotated_sig = lifetime_annotator.annotate_function(func);
31
32        // DECY-241: Rename functions that conflict with Rust macros/keywords
33        let safe_name = match func.name() {
34            "write" => "c_write", // Conflicts with Rust's write! macro
35            "read" => "c_read",   // Conflicts with Rust's read
36            "type" => "c_type",   // Rust keyword
37            "match" => "c_match", // Rust keyword
38            "self" => "c_self",   // Rust keyword
39            "in" => "c_in",       // Rust keyword
40            name => name,
41        };
42        let mut sig = format!("fn {}", safe_name);
43
44        // Add lifetime parameters if needed
45        let lifetime_syntax = lifetime_annotator.generate_lifetime_syntax(&annotated_sig.lifetimes);
46        sig.push_str(&lifetime_syntax);
47
48        // DECY-096: Detect void* parameters for generic transformation
49        use decy_analyzer::void_ptr_analysis::{TypeConstraint, VoidPtrAnalyzer};
50        let void_analyzer = VoidPtrAnalyzer::new();
51        let void_patterns = void_analyzer.analyze(func);
52
53        // DECY-168: Only consider patterns with actual constraints/types as "real" void* usage
54        // Empty body functions (stubs) will have patterns but no constraints
55        let has_real_void_usage = void_patterns
56            .iter()
57            .any(|vp| !vp.constraints.is_empty() || !vp.inferred_types.is_empty());
58
59        // DECY-097: Collect trait bounds from all void* patterns
60        let mut trait_bounds: Vec<&str> = Vec::new();
61        for pattern in &void_patterns {
62            for constraint in &pattern.constraints {
63                let bound = match constraint {
64                    TypeConstraint::PartialOrd => "PartialOrd",
65                    TypeConstraint::PartialEq => "PartialEq",
66                    TypeConstraint::Clone => "Clone",
67                    TypeConstraint::Copy => "Copy",
68                    _ => continue,
69                };
70                if !trait_bounds.contains(&bound) {
71                    trait_bounds.push(bound);
72                }
73            }
74        }
75
76        // Add generic type parameter with trait bounds if function has void* params with real usage
77        // DECY-168: Don't add <T> for stub functions without body analysis
78        if has_real_void_usage {
79            if trait_bounds.is_empty() {
80                sig.push_str("<T>");
81            } else {
82                sig.push_str(&format!("<T: {}>", trait_bounds.join(" + ")));
83            }
84        }
85
86        // DECY-072 GREEN: Detect array parameters using ownership analysis
87        use decy_ownership::dataflow::DataflowAnalyzer;
88        let analyzer = DataflowAnalyzer::new();
89        let graph = analyzer.analyze(func);
90
91        // DECY-084 GREEN: Detect output parameters for transformation
92        use decy_analyzer::output_params::{OutputParamDetector, ParameterKind};
93        let output_detector = OutputParamDetector::new();
94        let output_params = output_detector.detect(func);
95
96        // Track which parameters are length parameters to skip them
97        let mut skip_params = std::collections::HashSet::new();
98
99        // DECY-084: Track output parameters to skip and use for return type
100        let mut output_param_type: Option<HirType> = None;
101        let mut output_is_fallible = false;
102        for op in &output_params {
103            if op.kind == ParameterKind::Output {
104                skip_params.insert(op.name.clone());
105                output_is_fallible = op.is_fallible;
106                // Get the output parameter's inner type (pointer target)
107                if let Some(param) = func.parameters().iter().find(|p| p.name() == op.name) {
108                    if let HirType::Pointer(inner) = param.param_type() {
109                        output_param_type = Some((**inner).clone());
110                    }
111                }
112            }
113        }
114
115        // First pass: identify array parameters and their associated length parameters
116        // DECY-113: Only skip params with length-like names to avoid removing non-length params
117        // DECY-162: Don't skip length param if array uses pointer arithmetic (stays as raw pointer)
118        for (idx, param) in func.parameters().iter().enumerate() {
119            if let Some(true) = graph.is_array_parameter(param.name()) {
120                // DECY-162: Don't skip length param if array uses pointer arithmetic
121                // Raw pointers don't have .len(), so we need to keep the size param
122                if self.uses_pointer_arithmetic(func, param.name()) {
123                    continue; // Skip adding length param to skip_params
124                }
125
126                // This is an array parameter - mark the next param as length param to skip
127                // but only if it has a length-like name
128                if idx + 1 < func.parameters().len() {
129                    let next_param = &func.parameters()[idx + 1];
130                    if matches!(next_param.param_type(), HirType::Int) {
131                        let param_name = next_param.name().to_lowercase();
132                        // Only skip if the name suggests it's a length/size parameter
133                        if param_name.contains("len")
134                            || param_name.contains("size")
135                            || param_name.contains("count")
136                            || param_name == "n"
137                            || param_name == "num"
138                        {
139                            skip_params.insert(next_param.name().to_string());
140                        }
141                    }
142                }
143            }
144        }
145
146        // Generate parameters with lifetime annotations
147        sig.push('(');
148        let params: Vec<String> = annotated_sig
149            .parameters
150            .iter()
151            .filter_map(|p| {
152                if skip_params.contains(&p.name) {
153                    return None;
154                }
155                self.generate_signature_param(p, func, &graph, &void_patterns)
156            })
157            .collect();
158        sig.push_str(&params.join(", "));
159        sig.push(')');
160
161        // Generate return type
162        self.append_signature_return_type(
163            &mut sig,
164            func,
165            output_param_type.as_ref(),
166            output_is_fallible,
167            &annotated_sig,
168        );
169
170        sig
171    }
172
173    /// Generate a single parameter for a function signature.
174    fn generate_signature_param(
175        &self,
176        p: &decy_ownership::lifetime_gen::AnnotatedParameter,
177        func: &HirFunction,
178        graph: &decy_ownership::dataflow::DataflowGraph,
179        void_patterns: &[decy_analyzer::void_ptr_analysis::VoidPtrInfo],
180    ) -> Option<String> {
181        // Check if this is an array parameter
182        let is_array = graph.is_array_parameter(&p.name).unwrap_or(false);
183
184        // DECY-161: Array params with pointer arithmetic must stay as raw pointers
185        // Slices don't support arr++ or arr + n, so check for pointer arithmetic first
186        let uses_ptr_arithmetic = self.uses_pointer_arithmetic(func, &p.name);
187
188        if is_array && !uses_ptr_arithmetic {
189            // Transform to slice parameter (only if no pointer arithmetic)
190            // Find the original parameter to get the HirType
191            if let Some(orig_param) =
192                func.parameters().iter().find(|fp| fp.name() == p.name)
193            {
194                let is_mutable = self.is_parameter_modified(func, &p.name);
195                let slice_type =
196                    self.pointer_to_slice_type(orig_param.param_type(), is_mutable);
197                // For slices, don't add 'mut' prefix (slices themselves aren't reassigned)
198                Some(format!("{}: {}", p.name, slice_type))
199            } else {
200                None
201            }
202        } else {
203            // DECY-086: Check if this is an array parameter that should become a slice
204            // In C, `int arr[10]` as a parameter decays to a pointer, so we use slice
205            if let Some(orig_param) =
206                func.parameters().iter().find(|fp| fp.name() == p.name)
207            {
208                if let HirType::Array { element_type, .. } = orig_param.param_type() {
209                    // Fixed-size array parameter → slice reference
210                    let is_mutable = self.is_parameter_modified(func, &p.name);
211                    let element_str = Self::map_type(element_type);
212                    if is_mutable {
213                        return Some(format!("{}: &mut [{}]", p.name, element_str));
214                    } else {
215                        return Some(format!("{}: &[{}]", p.name, element_str));
216                    }
217                }
218            }
219            // DECY-111: Check if this is a pointer parameter that should become a reference
220            // DECY-123: Skip transformation if pointer arithmetic is used
221            if let Some(orig_param) =
222                func.parameters().iter().find(|fp| fp.name() == p.name)
223            {
224                // DECY-135: const char* → &str transformation
225                // DECY-138: Add mut for string iteration patterns (param reassignment)
226                // Must check BEFORE other pointer transformations
227                if orig_param.is_const_char_pointer() {
228                    return Some(format!("mut {}: &str", p.name));
229                }
230
231                if let HirType::Pointer(inner) = orig_param.param_type() {
232                    return Some(self.generate_pointer_param(
233                        &p.name, inner, func, void_patterns,
234                    ));
235                }
236            }
237            // Regular parameter with lifetime annotation
238            let type_str = self.annotated_type_to_string(&p.param_type);
239            // In C, parameters are mutable by default (can be reassigned)
240            Some(format!("mut {}: {}", p.name, type_str))
241        }
242    }
243
244    /// Generate a pointer parameter representation (reference, raw pointer, slice, or generic).
245    fn generate_pointer_param(
246        &self,
247        name: &str,
248        inner: &HirType,
249        func: &HirFunction,
250        void_patterns: &[decy_analyzer::void_ptr_analysis::VoidPtrInfo],
251    ) -> String {
252        use decy_analyzer::void_ptr_analysis::TypeConstraint;
253
254        // DECY-096: void* param becomes generic &T or &mut T
255        // DECY-168: Only apply generic transformation if we found an actual pattern
256        // for this specific parameter WITH real constraints (from body analysis).
257        // Otherwise keep as raw pointer *mut ().
258        if matches!(inner, HirType::Void) {
259            // Look for a void pattern specifically for this parameter
260            // that has actual constraints (indicating real usage in body)
261            let void_pattern = void_patterns.iter().find(|vp| {
262                vp.param_name == name
263                    && (!vp.constraints.is_empty()
264                        || !vp.inferred_types.is_empty())
265            });
266
267            if let Some(pattern) = void_pattern {
268                // Found actual usage pattern - apply generic transformation
269                let is_mutable = pattern.constraints.contains(&TypeConstraint::Mutable);
270                if is_mutable {
271                    return format!("{}: &mut T", name);
272                } else {
273                    return format!("{}: &T", name);
274                }
275            } else {
276                // DECY-168: No pattern with real constraints found - keep as raw pointer
277                // This is important for stdlib stubs (realloc, memcpy, etc.)
278                return format!("{}: *mut ()", name);
279            }
280        }
281        // DECY-134: Check for string iteration pattern FIRST
282        // char* with pointer arithmetic → slice instead of raw pointer
283        if self.is_string_iteration_param(func, name) {
284            // Transform to slice for safe string iteration
285            let is_mutable = self.is_parameter_deref_modified(func, name);
286            if is_mutable {
287                return format!("{}: &mut [u8]", name);
288            } else {
289                return format!("{}: &[u8]", name);
290            }
291        }
292        // DECY-123: Don't transform to reference if pointer arithmetic is used
293        // (e.g., ptr = ptr + 1) - keep as raw pointer
294        if self.uses_pointer_arithmetic(func, name) {
295            // Keep as raw pointer - will need unsafe blocks
296            // DECY-124: Add mut since the pointer is reassigned
297            let inner_type = Self::map_type(inner);
298            return format!("mut {}: *mut {}", name, inner_type);
299        }
300        // Transform pointer param to mutable reference
301        // Check if the param is modified in the function body
302        let is_mutable = self.is_parameter_deref_modified(func, name);
303        let inner_type = Self::map_type(inner);
304        if is_mutable {
305            format!("{}: &mut {}", name, inner_type)
306        } else {
307            // Read-only pointer becomes immutable reference
308            format!("{}: &{}", name, inner_type)
309        }
310    }
311
312    /// Append return type to signature string.
313    fn append_signature_return_type(
314        &self,
315        sig: &mut String,
316        func: &HirFunction,
317        output_param_type: Option<&HirType>,
318        output_is_fallible: bool,
319        annotated_sig: &AnnotatedSignature,
320    ) {
321        // Special handling for main function (DECY-AUDIT-001)
322        // C's int main() must become Rust's fn main() (no return type)
323        // Rust's entry point returns () and uses std::process::exit(N) for exit codes
324        if func.name() == "main" && matches!(func.return_type(), HirType::Int) {
325            return;
326        }
327
328        // DECY-084 GREEN: Generate return type considering output parameters
329        // Priority: output param type > original return type
330        if let Some(out_type) = output_param_type {
331            let out_type_str = Self::map_type(out_type);
332            if output_is_fallible {
333                sig.push_str(&format!(" -> Result<{}, i32>", out_type_str));
334            } else {
335                sig.push_str(&format!(" -> {}", out_type_str));
336            }
337        } else {
338            // DECY-142: Check if function returns malloc'd array → use Vec<T>
339            if let Some(vec_element_type) = self.detect_vec_return(func) {
340                let element_type_str = Self::map_type(&vec_element_type);
341                sig.push_str(&format!(" -> Vec<{}>", element_type_str));
342            } else {
343                // Generate return type with lifetime annotation (skip for void)
344                if !matches!(&annotated_sig.return_type, AnnotatedType::Simple(HirType::Void)) {
345                    let return_type_str = self.annotated_type_to_string(&annotated_sig.return_type);
346                    sig.push_str(&format!(" -> {}", return_type_str));
347                }
348            }
349        }
350    }
351
352    /// DECY-142: Check if function returns a malloc-allocated array.
353    /// Returns Some(element_type) if the function allocates with malloc and returns it.
354    /// This pattern should use Vec<T> return type instead of *mut T.
355    pub(crate) fn detect_vec_return(&self, func: &HirFunction) -> Option<HirType> {
356        // Only applies to functions returning pointer types
357        let return_type = func.return_type();
358        let element_type = match return_type {
359            HirType::Pointer(inner) => inner.as_ref().clone(),
360            _ => return None,
361        };
362
363        // Look for pattern: var = malloc(...); return var;
364        // or: return malloc(...);
365        let mut malloc_vars: std::collections::HashSet<String> = std::collections::HashSet::new();
366
367        for stmt in func.body() {
368            // Track variables assigned from malloc
369            if let HirStatement::VariableDeclaration {
370                name, initializer: Some(init_expr), ..
371            } = stmt
372            {
373                if Self::is_malloc_call(init_expr) {
374                    malloc_vars.insert(name.clone());
375                }
376            }
377
378            // Check return statements
379            if let HirStatement::Return(Some(ret_expr)) = stmt {
380                // Direct return of malloc
381                if Self::is_malloc_call(ret_expr) {
382                    return Some(element_type);
383                }
384                // Return of a variable that was assigned from malloc
385                if let HirExpression::Variable(var_name) = ret_expr {
386                    if malloc_vars.contains(var_name) {
387                        return Some(element_type);
388                    }
389                }
390            }
391        }
392
393        None
394    }
395
396    /// Helper to check if an expression is ANY malloc or calloc call (including through casts).
397    /// DECY-220: This is used for type annotation transformation (*mut T → Vec<T>).
398    /// Unlike `is_malloc_call`, this returns true for ANY malloc/calloc, not just array patterns.
399    pub(crate) fn is_any_malloc_or_calloc(expr: &HirExpression) -> bool {
400        match expr {
401            HirExpression::Malloc { .. } => true,
402            HirExpression::Calloc { .. } => true,
403            HirExpression::FunctionCall { function, .. }
404                if function == "malloc" || function == "calloc" =>
405            {
406                true
407            }
408            // DECY-220: Check through cast expressions (e.g., (int*)malloc(...))
409            HirExpression::Cast { expr: inner, .. } => Self::is_any_malloc_or_calloc(inner),
410            _ => false,
411        }
412    }
413
414    /// Helper to check if an expression is a malloc call for ARRAY allocation.
415    /// DECY-142: Only returns true for array allocations (malloc(n * sizeof(T))),
416    /// not single struct allocations (malloc(sizeof(T))).
417    fn is_malloc_call(expr: &HirExpression) -> bool {
418        match expr {
419            HirExpression::FunctionCall { function, arguments, .. } if function == "malloc" => {
420                // Check if this is an array allocation: malloc(n * sizeof(T))
421                // Single struct allocation: malloc(sizeof(T)) should NOT match
422                if arguments.len() == 1 {
423                    Self::is_array_allocation_size(&arguments[0])
424                } else {
425                    false
426                }
427            }
428            HirExpression::Malloc { size } => {
429                // Check if this is an array allocation
430                Self::is_array_allocation_size(size)
431            }
432            // DECY-142: Check through cast expressions (e.g., (int*)malloc(...))
433            HirExpression::Cast { expr: inner, .. } => Self::is_malloc_call(inner),
434            _ => false,
435        }
436    }
437
438    /// Check if a malloc size expression indicates array allocation (n * sizeof(T))
439    /// vs single struct allocation (sizeof(T) or constant).
440    fn is_array_allocation_size(size_expr: &HirExpression) -> bool {
441        match size_expr {
442            // n * sizeof(T) pattern - this is array allocation
443            HirExpression::BinaryOp { op: decy_hir::BinaryOperator::Multiply, .. } => true,
444            // sizeof(T) alone - this is single struct allocation, NOT array
445            HirExpression::Sizeof { .. } => false,
446            // Constant - likely single allocation
447            HirExpression::IntLiteral(_) => false,
448            // Variable could be array size, but be conservative
449            HirExpression::Variable(_) => false,
450            // Recurse through casts
451            HirExpression::Cast { expr: inner, .. } => Self::is_array_allocation_size(inner),
452            // Other expressions - be conservative, assume not array
453            _ => false,
454        }
455    }
456
457    /// Check if a parameter is modified in the function body (DECY-072 GREEN).
458    ///
459    /// Used to determine whether to use `&[T]` or `&mut [T]` for array parameters.
460    fn is_parameter_modified(&self, func: &HirFunction, param_name: &str) -> bool {
461        // Check if the parameter is used in any assignment statements
462        for stmt in func.body() {
463            if self.statement_modifies_variable(stmt, param_name) {
464                return true;
465            }
466        }
467        false
468    }
469
470    /// Check if a pointer parameter is dereferenced and modified (DECY-111 GREEN).
471    ///
472    /// Used to determine whether to use `&T` or `&mut T` for pointer parameters.
473    /// Returns true if the parameter is used in:
474    /// - `*ptr = value;` (DerefAssignment)
475    /// - `ptr[i] = value;` (ArrayIndexAssignment with pointer)
476    pub(crate) fn is_parameter_deref_modified(&self, func: &HirFunction, param_name: &str) -> bool {
477        for stmt in func.body() {
478            if self.statement_deref_modifies_variable(stmt, param_name) {
479                return true;
480            }
481        }
482        false
483    }
484
485    /// Recursively check if a statement deref-modifies a variable (DECY-111 GREEN).
486    #[allow(clippy::only_used_in_recursion)]
487    fn statement_deref_modifies_variable(&self, stmt: &HirStatement, var_name: &str) -> bool {
488        match stmt {
489            HirStatement::DerefAssignment { target, .. } => {
490                // Check if this is *ptr = value where ptr is our variable
491                if let HirExpression::Variable(name) = target {
492                    return name == var_name;
493                }
494                false
495            }
496            HirStatement::ArrayIndexAssignment { array, .. } => {
497                // Check if this is ptr[i] = value where ptr is our variable
498                if let HirExpression::Variable(name) = &**array {
499                    return name == var_name;
500                }
501                false
502            }
503            HirStatement::Assignment { .. } => {
504                // Regular variable assignment (src = src + 1) does NOT modify *src
505                // Only DerefAssignment (*src = value) modifies the pointed-to value
506                false
507            }
508            HirStatement::If { then_block, else_block, .. } => {
509                then_block.iter().any(|s| self.statement_deref_modifies_variable(s, var_name))
510                    || else_block.as_ref().is_some_and(|blk| {
511                        blk.iter().any(|s| self.statement_deref_modifies_variable(s, var_name))
512                    })
513            }
514            HirStatement::While { body, .. } | HirStatement::For { body, .. } => {
515                body.iter().any(|s| self.statement_deref_modifies_variable(s, var_name))
516            }
517            _ => false,
518        }
519    }
520
521    /// Check if a parameter uses pointer arithmetic, is reassigned, or compared to NULL (DECY-123, DECY-137).
522    ///
523    /// Used to determine whether a pointer parameter should remain a raw pointer
524    /// instead of being transformed to a reference.
525    /// Returns true if the parameter is used in:
526    /// - `ptr = ptr + n;` (pointer arithmetic assignment)
527    /// - `ptr = ptr - n;` (pointer arithmetic assignment)
528    /// - `ptr += n;` or `ptr -= n;` (compound pointer arithmetic)
529    /// - `ptr = ptr->field;` (DECY-137: linked list traversal pattern)
530    /// - `ptr = other_ptr;` (any pointer reassignment)
531    /// - `ptr != 0` or `ptr == 0` (DECY-137: NULL comparison - Rust refs can't be null)
532    ///
533    /// References in Rust cannot be reassigned or null, so any pointer param that is
534    /// reassigned or NULL-checked must remain as a raw pointer.
535    pub(crate) fn uses_pointer_arithmetic(&self, func: &HirFunction, param_name: &str) -> bool {
536        for stmt in func.body() {
537            if self.statement_uses_pointer_arithmetic(stmt, param_name) {
538                return true;
539            }
540            // DECY-137: Also check for NULL comparisons in conditions
541            if self.statement_uses_null_comparison(stmt, param_name) {
542                return true;
543            }
544        }
545        false
546    }
547
548    /// Check if a statement contains NULL comparison for a variable (DECY-137).
549    ///
550    /// If a pointer is compared to NULL (0), it should stay as raw pointer
551    /// because Rust references can never be null.
552    #[allow(clippy::only_used_in_recursion)]
553    fn statement_uses_null_comparison(&self, stmt: &HirStatement, var_name: &str) -> bool {
554        match stmt {
555            HirStatement::If { condition, then_block, else_block, .. } => {
556                // Check condition for NULL comparison
557                if self.expression_compares_to_null(condition, var_name) {
558                    return true;
559                }
560                // Recursively check nested statements
561                then_block.iter().any(|s| self.statement_uses_null_comparison(s, var_name))
562                    || else_block.as_ref().is_some_and(|blk| {
563                        blk.iter().any(|s| self.statement_uses_null_comparison(s, var_name))
564                    })
565            }
566            HirStatement::While { condition, body, .. } => {
567                if self.expression_compares_to_null(condition, var_name) {
568                    return true;
569                }
570                body.iter().any(|s| self.statement_uses_null_comparison(s, var_name))
571            }
572            HirStatement::For { condition, body, .. } => {
573                if let Some(cond) = condition {
574                    if self.expression_compares_to_null(cond, var_name) {
575                        return true;
576                    }
577                }
578                body.iter().any(|s| self.statement_uses_null_comparison(s, var_name))
579            }
580            _ => false,
581        }
582    }
583
584    /// Check if an expression compares a variable to NULL (0).
585    fn expression_compares_to_null(&self, expr: &HirExpression, var_name: &str) -> bool {
586        match expr {
587            HirExpression::BinaryOp { op, left, right } => {
588                if matches!(op, BinaryOperator::Equal | BinaryOperator::NotEqual) {
589                    // Check: var == 0 or var != 0
590                    if let HirExpression::Variable(name) = &**left {
591                        if name == var_name
592                            && matches!(
593                                **right,
594                                HirExpression::IntLiteral(0) | HirExpression::NullLiteral
595                            )
596                        {
597                            return true;
598                        }
599                    }
600                    // Check: 0 == var or 0 != var
601                    if let HirExpression::Variable(name) = &**right {
602                        if name == var_name
603                            && matches!(
604                                **left,
605                                HirExpression::IntLiteral(0) | HirExpression::NullLiteral
606                            )
607                        {
608                            return true;
609                        }
610                    }
611                }
612                // Recursively check nested expressions (e.g., in logical AND/OR)
613                self.expression_compares_to_null(left, var_name)
614                    || self.expression_compares_to_null(right, var_name)
615            }
616            _ => false,
617        }
618    }
619
620    /// Recursively check if a statement uses pointer arithmetic or reassigns a variable (DECY-123, DECY-137).
621    #[allow(clippy::only_used_in_recursion)]
622    fn statement_uses_pointer_arithmetic(&self, stmt: &HirStatement, var_name: &str) -> bool {
623        match stmt {
624            HirStatement::Assignment { target, value } => {
625                // DECY-137: Any assignment to the pointer parameter means it must stay as raw pointer
626                // This catches:
627                // - ptr = ptr + n (pointer arithmetic)
628                // - ptr = ptr->next (linked list traversal)
629                // - ptr = other_ptr (general reassignment)
630                //
631                // References cannot be reassigned, only raw pointers can.
632                if target == var_name {
633                    // Check if this is pointer arithmetic (ptr = ptr + n or ptr = ptr - n)
634                    if let HirExpression::BinaryOp { op, left, .. } = value {
635                        if matches!(op, BinaryOperator::Add | BinaryOperator::Subtract) {
636                            if let HirExpression::Variable(name) = &**left {
637                                if name == var_name {
638                                    return true;
639                                }
640                            }
641                        }
642                    }
643
644                    // DECY-137: Check for field access reassignment (ptr = ptr->field)
645                    // This is the linked list traversal pattern: head = head->next
646                    if let HirExpression::PointerFieldAccess { pointer, .. } = value {
647                        if let HirExpression::Variable(name) = &**pointer {
648                            if name == var_name {
649                                return true;
650                            }
651                        }
652                    }
653
654                    // DECY-137: Check for any other pointer reassignment
655                    // If ptr is assigned from another variable or expression, it needs
656                    // to stay as raw pointer. However, we need to be careful not to
657                    // flag initialization (which happens at declaration, not assignment).
658                    // For now, flag field access from ANY pointer as reassignment.
659                    if matches!(value, HirExpression::PointerFieldAccess { .. }) {
660                        return true;
661                    }
662                }
663                false
664            }
665            HirStatement::If { then_block, else_block, .. } => {
666                then_block.iter().any(|s| self.statement_uses_pointer_arithmetic(s, var_name))
667                    || else_block.as_ref().is_some_and(|blk| {
668                        blk.iter().any(|s| self.statement_uses_pointer_arithmetic(s, var_name))
669                    })
670            }
671            // DECY-164: Check for post/pre increment/decrement on the variable
672            HirStatement::Expression(expr) => {
673                Self::expression_uses_pointer_arithmetic_static(expr, var_name)
674            }
675            HirStatement::While { body, .. } | HirStatement::For { body, .. } => {
676                body.iter().any(|s| self.statement_uses_pointer_arithmetic(s, var_name))
677            }
678            _ => false,
679        }
680    }
681
682    /// DECY-164: Check if an expression uses pointer arithmetic on a variable.
683    /// Catches str++, ++str, str--, --str patterns.
684    fn expression_uses_pointer_arithmetic_static(expr: &HirExpression, var_name: &str) -> bool {
685        match expr {
686            HirExpression::PostIncrement { operand }
687            | HirExpression::PreIncrement { operand }
688            | HirExpression::PostDecrement { operand }
689            | HirExpression::PreDecrement { operand } => {
690                matches!(&**operand, HirExpression::Variable(name) if name == var_name)
691            }
692            _ => false,
693        }
694    }
695
696    /// DECY-134b: Get all string iteration params for a function.
697    ///
698    /// Returns a list of (param_index, is_mutable) for each char* param that uses pointer arithmetic.
699    /// Used by decy-core to build string_iter_funcs info for call site transformation.
700    pub fn get_string_iteration_params(&self, func: &HirFunction) -> Vec<(usize, bool)> {
701        func.parameters()
702            .iter()
703            .enumerate()
704            .filter_map(|(i, param)| {
705                if self.is_string_iteration_param(func, param.name()) {
706                    let is_mutable = self.is_parameter_deref_modified(func, param.name());
707                    Some((i, is_mutable))
708                } else {
709                    None
710                }
711            })
712            .collect()
713    }
714
715    /// DECY-134: Check if a char* parameter is used in a string iteration pattern.
716    ///
717    /// String iteration pattern: char* with pointer arithmetic in a loop (while (*s) { s++; })
718    /// These should be transformed to slice + index for safe Rust.
719    /// DECY-164: Skip if function uses pointer subtraction (e.g., str - start for length calculation).
720    pub(crate) fn is_string_iteration_param(&self, func: &HirFunction, param_name: &str) -> bool {
721        // Must be a char pointer (Pointer(Char))
722        let is_char_ptr = func.parameters().iter().any(|p| {
723            p.name() == param_name
724                && matches!(p.param_type(), HirType::Pointer(inner) if matches!(&**inner, HirType::Char))
725        });
726
727        if !is_char_ptr {
728            return false;
729        }
730
731        // DECY-164: Don't apply string iteration transformation if there's pointer subtraction
732        // Pointer subtraction (str - start) requires raw pointers, can't use slices
733        if self.function_uses_pointer_subtraction(func, param_name) {
734            return false;
735        }
736
737        // Must use pointer arithmetic
738        self.uses_pointer_arithmetic(func, param_name)
739    }
740
741    /// DECY-164: Check if a function uses pointer subtraction involving a variable.
742    /// Pattern: var - other_ptr (e.g., str - start for calculating string length)
743    fn function_uses_pointer_subtraction(&self, func: &HirFunction, var_name: &str) -> bool {
744        for stmt in func.body() {
745            if self.statement_uses_pointer_subtraction(stmt, var_name) {
746                return true;
747            }
748        }
749        false
750    }
751
752    /// DECY-164: Check if a statement uses pointer subtraction involving a variable.
753    fn statement_uses_pointer_subtraction(&self, stmt: &HirStatement, var_name: &str) -> bool {
754        match stmt {
755            HirStatement::Return(Some(expr)) => {
756                self.expression_uses_pointer_subtraction(expr, var_name)
757            }
758            HirStatement::Assignment { value, .. } => {
759                self.expression_uses_pointer_subtraction(value, var_name)
760            }
761            HirStatement::VariableDeclaration { initializer, .. } => initializer
762                .as_ref()
763                .map(|e| self.expression_uses_pointer_subtraction(e, var_name))
764                .unwrap_or(false),
765            HirStatement::If { condition, then_block, else_block, .. } => {
766                self.expression_uses_pointer_subtraction(condition, var_name)
767                    || then_block
768                        .iter()
769                        .any(|s| self.statement_uses_pointer_subtraction(s, var_name))
770                    || else_block.as_ref().is_some_and(|blk| {
771                        blk.iter().any(|s| self.statement_uses_pointer_subtraction(s, var_name))
772                    })
773            }
774            HirStatement::While { condition, body } => {
775                self.expression_uses_pointer_subtraction(condition, var_name)
776                    || body.iter().any(|s| self.statement_uses_pointer_subtraction(s, var_name))
777            }
778            HirStatement::For { body, .. } => {
779                body.iter().any(|s| self.statement_uses_pointer_subtraction(s, var_name))
780            }
781            _ => false,
782        }
783    }
784
785    /// DECY-164: Check if an expression uses pointer subtraction involving a variable.
786    fn expression_uses_pointer_subtraction(&self, expr: &HirExpression, var_name: &str) -> bool {
787        match expr {
788            HirExpression::BinaryOp { op, left, right } => {
789                // Check for var - other_ptr pattern
790                if matches!(op, BinaryOperator::Subtract) {
791                    if let HirExpression::Variable(name) = &**left {
792                        if name == var_name {
793                            return true;
794                        }
795                    }
796                    if let HirExpression::Variable(name) = &**right {
797                        if name == var_name {
798                            return true;
799                        }
800                    }
801                }
802                // Recursively check subexpressions
803                self.expression_uses_pointer_subtraction(left, var_name)
804                    || self.expression_uses_pointer_subtraction(right, var_name)
805            }
806            HirExpression::Dereference(inner) => {
807                self.expression_uses_pointer_subtraction(inner, var_name)
808            }
809            HirExpression::Cast { expr, .. } => {
810                self.expression_uses_pointer_subtraction(expr, var_name)
811            }
812            _ => false,
813        }
814    }
815
816    /// Recursively check if a statement modifies a variable (DECY-072 GREEN).
817    #[allow(clippy::only_used_in_recursion)]
818    fn statement_modifies_variable(&self, stmt: &HirStatement, var_name: &str) -> bool {
819        match stmt {
820            HirStatement::ArrayIndexAssignment { array, .. } => {
821                // Check if this is arr[i] = value where arr is our variable
822                if let HirExpression::Variable(name) = &**array {
823                    return name == var_name;
824                }
825                false
826            }
827            HirStatement::DerefAssignment { target, .. } => {
828                // Check if this is *ptr = value where ptr is our variable
829                if let HirExpression::Variable(name) = target {
830                    return name == var_name;
831                }
832                false
833            }
834            HirStatement::If { then_block, else_block, .. } => {
835                then_block.iter().any(|s| self.statement_modifies_variable(s, var_name))
836                    || else_block.as_ref().is_some_and(|blk| {
837                        blk.iter().any(|s| self.statement_modifies_variable(s, var_name))
838                    })
839            }
840            HirStatement::While { body, .. } | HirStatement::For { body, .. } => {
841                body.iter().any(|s| self.statement_modifies_variable(s, var_name))
842            }
843            _ => false,
844        }
845    }
846
847    /// Convert a pointer type to a slice type (DECY-072 GREEN).
848    ///
849    /// Transforms `*mut T` or `*const T` to `&\[T]` or `&mut \[T]`.
850    fn pointer_to_slice_type(&self, ptr_type: &HirType, is_mutable: bool) -> String {
851        if let HirType::Pointer(inner) = ptr_type {
852            let element_type = Self::map_type(inner);
853            if is_mutable {
854                format!("&mut [{}]", element_type)
855            } else {
856                format!("&[{}]", element_type)
857            }
858        } else {
859            // Fallback: not a pointer, use normal mapping
860            Self::map_type(ptr_type)
861        }
862    }
863
864    /// Transform length parameter references to array.len() calls (DECY-072 GREEN).
865    ///
866    /// Replaces variable references like `len` with `arr.len()` in generated code.
867    pub(crate) fn transform_length_refs(
868        &self,
869        code: &str,
870        length_to_array: &std::collections::HashMap<String, String>,
871    ) -> String {
872        let mut result = code.to_string();
873
874        // Replace each length parameter reference with corresponding array.len() call
875        for (length_param, array_param) in length_to_array {
876            // Match the length parameter as a standalone identifier
877            // Use word boundaries to avoid partial matches
878            // Common patterns: "return len", "x + len", "len)", etc.
879            let patterns = vec![
880                (
881                    format!("return {}", length_param),
882                    format!("return {}.len() as i32", array_param),
883                ),
884                (format!("{} ", length_param), format!("{}.len() as i32 ", array_param)),
885                (format!("{})", length_param), format!("{}.len() as i32)", array_param)),
886                (format!("{},", length_param), format!("{}.len() as i32,", array_param)),
887                (format!("{}]", length_param), format!("{}.len() as i32]", array_param)),
888                (length_param.clone() + "}", array_param.clone() + ".len() as i32}"),
889                (format!("{};", length_param), format!("{}.len() as i32;", array_param)),
890            ];
891
892            for (pattern, replacement) in patterns {
893                result = result.replace(&pattern, &replacement);
894            }
895        }
896
897        result
898    }
899
900}