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(¶ms.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}