Skip to main content

depyler_analysis/
borrowing_context.rs

1//! Enhanced borrowing context for proper ownership pattern inference
2//!
3//! This module provides comprehensive analysis of parameter usage patterns
4//! to determine optimal borrowing strategies for function parameters.
5
6#[cfg(feature = "decision-tracing")]
7use depyler_hir::decision_trace::DecisionCategory;
8use depyler_hir::hir::{AssignTarget, HirExpr, HirFunction, HirStmt, Type as PythonType};
9use depyler_hir::trace_decision;
10use crate::type_mapper::{RustType, TypeMapper};
11use indexmap::IndexMap;
12use std::collections::{HashMap, HashSet};
13
14/// Comprehensive borrowing context for analyzing parameter usage
15#[derive(Debug)]
16pub struct BorrowingContext {
17    /// Usage patterns for each parameter
18    param_usage: HashMap<String, ParameterUsagePattern>,
19    /// Variables that are moved or consumed
20    moved_vars: HashSet<String>,
21    /// Variables that are borrowed mutably
22    mut_borrowed_vars: HashSet<String>,
23    /// Variables that are borrowed immutably
24    immut_borrowed_vars: HashSet<String>,
25    /// Control flow context stack
26    context_stack: Vec<AnalysisContext>,
27    /// Function return type for escape analysis
28    return_type: Option<PythonType>,
29}
30
31/// Detailed parameter usage pattern
32#[derive(Debug, Clone, Default)]
33pub struct ParameterUsagePattern {
34    /// Parameter is read without modification
35    pub is_read: bool,
36    /// Parameter is modified (assigned to)
37    pub is_mutated: bool,
38    /// Parameter is moved/consumed (passed to function that takes ownership)
39    pub is_moved: bool,
40    /// Parameter escapes through return
41    pub escapes_through_return: bool,
42    /// Parameter is stored in a struct/container
43    pub is_stored: bool,
44    /// Parameter is used in a closure
45    pub used_in_closure: bool,
46    /// Parameter is used in loops (affects borrowing strategy)
47    pub used_in_loop: bool,
48    /// Nested field access patterns
49    pub field_accesses: HashSet<String>,
50    /// Method calls on the parameter
51    pub method_calls: HashSet<String>,
52    /// Specific expressions where parameter is used
53    pub usage_sites: Vec<UsageSite>,
54}
55
56/// Site where a parameter is used
57#[derive(Debug, Clone)]
58pub struct UsageSite {
59    /// Type of usage
60    pub usage_type: UsageType,
61    /// Whether in a loop context
62    pub in_loop: bool,
63    /// Whether in a conditional context
64    pub in_conditional: bool,
65    /// Depth of borrowing (for nested borrows)
66    pub borrow_depth: usize,
67}
68
69/// Type of parameter usage
70#[derive(Debug, Clone, PartialEq, Eq)]
71pub enum UsageType {
72    /// Simple read access
73    Read,
74    /// Mutable access
75    Write,
76    /// Method call that might mutate
77    MethodCall(String),
78    /// Passed to another function
79    FunctionArg { takes_ownership: bool },
80    /// Returned from function
81    Return,
82    /// Stored in a data structure
83    Store,
84    /// Used in a closure
85    Closure { captures_by_value: bool },
86    /// Field access
87    FieldAccess(String),
88    /// Index access
89    IndexAccess,
90}
91
92/// Analysis context for control flow
93#[derive(Debug, Clone)]
94#[allow(dead_code)]
95enum AnalysisContext {
96    Loop,
97    Conditional,
98    Closure { captures: HashSet<String> },
99    Function,
100}
101
102/// Result of borrowing analysis
103#[derive(Debug, Clone)]
104pub struct BorrowingAnalysisResult {
105    /// Recommended borrowing strategy for each parameter
106    pub param_strategies: IndexMap<String, BorrowingStrategy>,
107    /// Additional insights
108    pub insights: Vec<BorrowingInsight>,
109}
110
111/// Recommended borrowing strategy
112#[derive(Debug, Clone, PartialEq, Eq)]
113pub enum BorrowingStrategy {
114    /// Take ownership (move)
115    TakeOwnership,
116    /// Borrow immutably
117    BorrowImmutable { lifetime: Option<String> },
118    /// Borrow mutably
119    BorrowMutable { lifetime: Option<String> },
120    /// Use Cow for flexibility
121    UseCow { lifetime: String },
122    /// Use Arc/Rc for shared ownership
123    UseSharedOwnership { is_thread_safe: bool },
124}
125
126/// Insights from borrowing analysis
127#[derive(Debug, Clone)]
128pub enum BorrowingInsight {
129    /// Parameter could be borrowed but is currently moved
130    UnnecessaryMove(String),
131    /// Parameter could use a more specific lifetime
132    LifetimeOptimization { param: String, suggestion: String },
133    /// Parameter access pattern suggests Copy trait
134    SuggestCopyDerive(String),
135    /// Multiple mutable borrows detected
136    PotentialBorrowConflict {
137        param: String,
138        locations: Vec<String>,
139    },
140}
141
142impl BorrowingContext {
143    pub fn new(return_type: Option<PythonType>) -> Self {
144        Self {
145            param_usage: HashMap::new(),
146            moved_vars: HashSet::new(),
147            mut_borrowed_vars: HashSet::new(),
148            immut_borrowed_vars: HashSet::new(),
149            context_stack: vec![AnalysisContext::Function],
150            return_type,
151        }
152    }
153
154    /// Analyze a function to determine optimal borrowing strategies
155    pub fn analyze_function(
156        &mut self,
157        func: &HirFunction,
158        type_mapper: &TypeMapper,
159    ) -> BorrowingAnalysisResult {
160        // Initialize parameter tracking
161        for param in &func.params {
162            self.param_usage
163                .insert(param.name.clone(), ParameterUsagePattern::default());
164        }
165
166        // Analyze function body
167        for stmt in &func.body {
168            self.analyze_statement(stmt);
169        }
170
171        // Determine borrowing strategies based on usage patterns
172        self.determine_strategies(func, type_mapper)
173    }
174
175    /// Analyze a statement for parameter usage
176    fn analyze_statement(&mut self, stmt: &HirStmt) {
177        match stmt {
178            HirStmt::Assign { target, value, .. } => {
179                // Check if assigning to a parameter (mutation)
180                // DEPYLER-1217: Handle all assignment targets that mutate parameters
181                let in_loop = self.is_in_loop();
182                let in_conditional = self.is_in_conditional();
183                self.check_target_mutation(target, in_loop, in_conditional);
184                self.analyze_expression(value, 0);
185            }
186            HirStmt::Return(expr) => {
187                if let Some(e) = expr {
188                    self.analyze_expression_for_return(e);
189                }
190            }
191            HirStmt::If {
192                condition,
193                then_body,
194                else_body,
195            } => {
196                self.analyze_expression(condition, 0);
197                self.context_stack.push(AnalysisContext::Conditional);
198                for stmt in then_body {
199                    self.analyze_statement(stmt);
200                }
201                if let Some(else_stmts) = else_body {
202                    for stmt in else_stmts {
203                        self.analyze_statement(stmt);
204                    }
205                }
206                self.context_stack.pop();
207            }
208            HirStmt::While { condition, body } => {
209                self.context_stack.push(AnalysisContext::Loop);
210                self.analyze_expression(condition, 0);
211                for stmt in body {
212                    self.analyze_statement(stmt);
213                }
214                self.context_stack.pop();
215            }
216            HirStmt::For {
217                target: _,
218                iter,
219                body,
220            } => {
221                self.context_stack.push(AnalysisContext::Loop);
222                self.analyze_expression(iter, 0);
223                for stmt in body {
224                    self.analyze_statement(stmt);
225                }
226                self.context_stack.pop();
227            }
228            HirStmt::Expr(expr) => {
229                self.analyze_expression(expr, 0);
230            }
231            HirStmt::Raise { exception, cause } => {
232                if let Some(exc) = exception {
233                    self.analyze_expression(exc, 0);
234                }
235                if let Some(c) = cause {
236                    self.analyze_expression(c, 0);
237                }
238            }
239            HirStmt::Break { .. } | HirStmt::Continue { .. } | HirStmt::Pass => {
240                // Break, continue, and pass don't analyze any expressions
241            }
242            // DEPYLER-0614: Recursively analyze Block statements
243            HirStmt::Block(stmts) => {
244                for s in stmts {
245                    self.analyze_statement(s);
246                }
247            }
248            HirStmt::Assert { test, msg } => {
249                // Analyze the test expression and optional message
250                self.analyze_expression(test, 0);
251                if let Some(message) = msg {
252                    self.analyze_expression(message, 0);
253                }
254            }
255            HirStmt::With {
256                context,
257                target: _,
258                body,
259                ..
260            } => {
261                // Analyze context expression
262                self.analyze_expression(context, 0);
263
264                // Track the target variable if present
265                // Note: We don't track local variables here, only parameters
266
267                // Analyze body statements
268                for stmt in body {
269                    self.analyze_statement(stmt);
270                }
271            }
272            HirStmt::Try {
273                body,
274                handlers,
275                orelse,
276                finalbody,
277            } => {
278                // Analyze try body
279                for stmt in body {
280                    self.analyze_statement(stmt);
281                }
282
283                // Analyze except handlers
284                for handler in handlers {
285                    for stmt in &handler.body {
286                        self.analyze_statement(stmt);
287                    }
288                }
289
290                // Analyze else clause
291                if let Some(else_stmts) = orelse {
292                    for stmt in else_stmts {
293                        self.analyze_statement(stmt);
294                    }
295                }
296
297                // Analyze finally clause
298                if let Some(finally_stmts) = finalbody {
299                    for stmt in finally_stmts {
300                        self.analyze_statement(stmt);
301                    }
302                }
303            }
304            // DEPYLER-0427: Nested function support
305            // Analyze nested function body for parameter usage
306            HirStmt::FunctionDef { body, .. } => {
307                for stmt in body {
308                    self.analyze_statement(stmt);
309                }
310            }
311        }
312    }
313
314    /// DEPYLER-1217: Helper to check if an assignment target mutates a parameter
315    /// Recursively handles index, attribute, and tuple targets
316    fn check_target_mutation(
317        &mut self,
318        target: &AssignTarget,
319        in_loop: bool,
320        in_conditional: bool,
321    ) {
322        match target {
323            AssignTarget::Symbol(symbol) => {
324                if let Some(usage) = self.param_usage.get_mut(symbol) {
325                    usage.is_mutated = true;
326                    usage.usage_sites.push(UsageSite {
327                        usage_type: UsageType::Write,
328                        in_loop,
329                        in_conditional,
330                        borrow_depth: 0,
331                    });
332                }
333            }
334            // DEPYLER-1217: Index assignment (arr[i] = value) mutates the base
335            AssignTarget::Index { base, .. } => {
336                if let HirExpr::Var(var_name) = base.as_ref() {
337                    if let Some(usage) = self.param_usage.get_mut(var_name) {
338                        usage.is_mutated = true;
339                        usage.usage_sites.push(UsageSite {
340                            usage_type: UsageType::Write,
341                            in_loop,
342                            in_conditional,
343                            borrow_depth: 0,
344                        });
345                    }
346                }
347            }
348            // DEPYLER-1217: Attribute assignment (obj.field = value) mutates the base
349            AssignTarget::Attribute { value, .. } => {
350                if let HirExpr::Var(var_name) = value.as_ref() {
351                    if let Some(usage) = self.param_usage.get_mut(var_name) {
352                        usage.is_mutated = true;
353                        usage.usage_sites.push(UsageSite {
354                            usage_type: UsageType::Write,
355                            in_loop,
356                            in_conditional,
357                            borrow_depth: 0,
358                        });
359                    }
360                }
361            }
362            // DEPYLER-1217: Tuple unpacking can contain index targets
363            // e.g., arr[i], arr[j] = arr[j], arr[i] (swap pattern)
364            AssignTarget::Tuple(targets) => {
365                for t in targets {
366                    self.check_target_mutation(t, in_loop, in_conditional);
367                }
368            }
369        }
370    }
371
372    /// Analyze an expression for parameter usage
373    fn analyze_expression(&mut self, expr: &HirExpr, borrow_depth: usize) {
374        match expr {
375            HirExpr::Var(name) => {
376                let in_loop = self.is_in_loop();
377                let in_conditional = self.is_in_conditional();
378                if let Some(usage) = self.param_usage.get_mut(name) {
379                    usage.is_read = true;
380                    usage.usage_sites.push(UsageSite {
381                        usage_type: UsageType::Read,
382                        in_loop,
383                        in_conditional,
384                        borrow_depth,
385                    });
386                    if in_loop {
387                        usage.used_in_loop = true;
388                    }
389                }
390            }
391            HirExpr::Attribute { value, attr } => {
392                if let HirExpr::Var(name) = &**value {
393                    let in_loop = self.is_in_loop();
394                    let in_conditional = self.is_in_conditional();
395                    if let Some(usage) = self.param_usage.get_mut(name) {
396                        usage.field_accesses.insert(attr.clone());
397                        usage.usage_sites.push(UsageSite {
398                            usage_type: UsageType::FieldAccess(attr.clone()),
399                            in_loop,
400                            in_conditional,
401                            borrow_depth: borrow_depth + 1,
402                        });
403                    }
404                }
405                self.analyze_expression(value, borrow_depth + 1);
406            }
407            HirExpr::Call { func, args, .. } => {
408                // Analyze function calls to determine if parameters are moved
409                let in_loop = self.is_in_loop();
410                let in_conditional = self.is_in_conditional();
411                for (i, arg) in args.iter().enumerate() {
412                    if let HirExpr::Var(name) = arg {
413                        let takes_ownership = self.function_takes_ownership(func, i);
414                        if let Some(usage) = self.param_usage.get_mut(name) {
415                            // Conservative: assume ownership transfer unless we know better
416                            if takes_ownership {
417                                usage.is_moved = true;
418                                self.moved_vars.insert(name.clone());
419                            }
420                            usage.usage_sites.push(UsageSite {
421                                usage_type: UsageType::FunctionArg { takes_ownership },
422                                in_loop,
423                                in_conditional,
424                                borrow_depth,
425                            });
426                        }
427                    }
428                    self.analyze_expression(arg, borrow_depth);
429                }
430            }
431            HirExpr::Index { base, index } => {
432                if let HirExpr::Var(name) = &**base {
433                    let in_loop = self.is_in_loop();
434                    let in_conditional = self.is_in_conditional();
435                    if let Some(usage) = self.param_usage.get_mut(name) {
436                        usage.usage_sites.push(UsageSite {
437                            usage_type: UsageType::IndexAccess,
438                            in_loop,
439                            in_conditional,
440                            borrow_depth: borrow_depth + 1,
441                        });
442                    }
443                }
444                self.analyze_expression(base, borrow_depth + 1);
445                self.analyze_expression(index, borrow_depth);
446            }
447            HirExpr::Binary { left, right, .. } => {
448                self.analyze_expression(left, borrow_depth);
449                self.analyze_expression(right, borrow_depth);
450            }
451            HirExpr::Unary { operand, .. } => {
452                self.analyze_expression(operand, borrow_depth);
453            }
454            HirExpr::List(elements) | HirExpr::Tuple(elements) => {
455                for elem in elements {
456                    self.analyze_expression(elem, borrow_depth);
457                }
458            }
459            HirExpr::Dict(pairs) => {
460                for (k, v) in pairs {
461                    self.analyze_expression(k, borrow_depth);
462                    self.analyze_expression(v, borrow_depth);
463                }
464            }
465            HirExpr::Borrow { expr, mutable } => {
466                if let HirExpr::Var(name) = &**expr {
467                    if *mutable {
468                        self.mut_borrowed_vars.insert(name.clone());
469                    } else {
470                        self.immut_borrowed_vars.insert(name.clone());
471                    }
472                }
473                self.analyze_expression(expr, borrow_depth + 1);
474            }
475            HirExpr::MethodCall { object, args, .. } => {
476                self.analyze_expression(object, borrow_depth);
477                for arg in args {
478                    self.analyze_expression(arg, borrow_depth);
479                }
480            }
481            HirExpr::Slice {
482                base,
483                start,
484                stop,
485                step,
486            } => {
487                self.analyze_expression(base, borrow_depth);
488                if let Some(s) = start {
489                    self.analyze_expression(s, borrow_depth);
490                }
491                if let Some(s) = stop {
492                    self.analyze_expression(s, borrow_depth);
493                }
494                if let Some(s) = step {
495                    self.analyze_expression(s, borrow_depth);
496                }
497            }
498            HirExpr::Literal(_) => {}
499            HirExpr::ListComp {
500                element,
501                generators,
502            } => {
503                // List comprehensions create a new scope
504                self.context_stack.push(AnalysisContext::Loop);
505
506                // Analyze all generators
507                for gen in generators {
508                    // Analyze the iterator
509                    self.analyze_expression(&gen.iter, borrow_depth);
510
511                    // Analyze all conditions
512                    for cond in &gen.conditions {
513                        self.analyze_expression(cond, borrow_depth);
514                    }
515                }
516
517                // Analyze the element expression
518                self.analyze_expression(element, borrow_depth);
519
520                self.context_stack.pop();
521            }
522            HirExpr::FString { .. } => {
523                // FString support not yet implemented for borrowing analysis
524            }
525            HirExpr::Lambda { params: _, body } => {
526                // Lambda functions capture variables by reference
527                // For now, treat lambda bodies like any other expression
528                self.analyze_expression(body, borrow_depth);
529            }
530            HirExpr::Set(elements) | HirExpr::FrozenSet(elements) => {
531                for elem in elements {
532                    self.analyze_expression(elem, borrow_depth);
533                }
534            }
535            HirExpr::SetComp {
536                element,
537                generators,
538            } => {
539                // Set comprehensions create a new scope
540                self.context_stack.push(AnalysisContext::Loop);
541
542                // Analyze all generators
543                for gen in generators {
544                    // Analyze the iterator
545                    self.analyze_expression(&gen.iter, borrow_depth);
546
547                    // Analyze all conditions
548                    for cond in &gen.conditions {
549                        self.analyze_expression(cond, borrow_depth);
550                    }
551                }
552
553                // Analyze the element expression
554                self.analyze_expression(element, borrow_depth);
555
556                self.context_stack.pop();
557            }
558            HirExpr::DictComp {
559                key,
560                value,
561                generators,
562            } => {
563                // Dict comprehensions create a new scope
564                self.context_stack.push(AnalysisContext::Loop);
565
566                // Analyze all generators
567                for gen in generators {
568                    // Analyze the iterator
569                    self.analyze_expression(&gen.iter, borrow_depth);
570
571                    // Analyze all conditions
572                    for cond in &gen.conditions {
573                        self.analyze_expression(cond, borrow_depth);
574                    }
575                }
576
577                // Analyze the key and value expressions
578                self.analyze_expression(key, borrow_depth);
579                self.analyze_expression(value, borrow_depth);
580
581                self.context_stack.pop();
582            }
583            HirExpr::Await { value } => {
584                // Await expressions don't change parameter usage patterns
585                self.analyze_expression(value, borrow_depth);
586            }
587            HirExpr::Yield { value } => {
588                // Yield expressions pass values to the iterator
589                if let Some(v) = value {
590                    self.analyze_expression(v, borrow_depth);
591                }
592            }
593            HirExpr::IfExpr { test, body, orelse } => {
594                // Analyze all three parts of the ternary expression
595                self.analyze_expression(test, borrow_depth);
596                self.analyze_expression(body, borrow_depth);
597                self.analyze_expression(orelse, borrow_depth);
598            }
599            HirExpr::SortByKey {
600                iterable, key_body, ..
601            } => {
602                // Analyze the iterable and the key lambda body
603                self.analyze_expression(iterable, borrow_depth);
604                self.analyze_expression(key_body, borrow_depth);
605            }
606            HirExpr::GeneratorExp {
607                element,
608                generators,
609            } => {
610                // Analyze element expression and all generator iterables
611                self.analyze_expression(element, borrow_depth);
612                for gen in generators {
613                    self.analyze_expression(&gen.iter, borrow_depth);
614                    for cond in &gen.conditions {
615                        self.analyze_expression(cond, borrow_depth);
616                    }
617                }
618            }
619            // DEPYLER-0188: Walrus operator - analyze the value expression
620            HirExpr::NamedExpr { value, .. } => {
621                self.analyze_expression(value, borrow_depth);
622            }
623            // DEPYLER-0188: Dynamic call - analyze callee and arguments
624            HirExpr::DynamicCall { callee, args, .. } => {
625                self.analyze_expression(callee, borrow_depth);
626                for arg in args {
627                    self.analyze_expression(arg, borrow_depth);
628                }
629            }
630        }
631    }
632
633    /// Analyze expression in return context
634    /// DEPYLER-0303: Only mark parameters as escaping if they are DIRECTLY returned,
635    /// not when used in operations that produce different types (e.g., `key in dict` → bool)
636    fn analyze_expression_for_return(&mut self, expr: &HirExpr) {
637        match expr {
638            HirExpr::Var(name) => {
639                // Parameter is directly returned - it escapes
640                if let Some(usage) = self.param_usage.get_mut(name) {
641                    usage.escapes_through_return = true;
642                    usage.usage_sites.push(UsageSite {
643                        usage_type: UsageType::Return,
644                        in_loop: false,
645                        in_conditional: false,
646                        borrow_depth: 0,
647                    });
648                }
649            }
650            HirExpr::Binary { left, right, .. } => {
651                // Binary operations (comparisons, arithmetic, etc.) produce NEW values
652                // Parameters are used but don't escape - just analyze normally
653                self.analyze_expression(left, 0);
654                self.analyze_expression(right, 0);
655            }
656            _ => self.analyze_expression(expr, 0),
657        }
658    }
659
660    /// Determine if a function takes ownership of its argument
661    fn function_takes_ownership(&self, func_name: &str, _arg_index: usize) -> bool {
662        // Known functions that borrow
663        let borrowing_functions = [
664            "len",
665            "str",
666            "repr",
667            "format",
668            "print",
669            "isinstance",
670            "hasattr",
671            "getattr",
672            "contains",
673            "startswith",
674            "endswith",
675            "find",
676            "index",
677            "count",
678            // DEPYLER-0436: Type conversion functions borrow their arguments
679            "int",
680            "float",
681            "bool",
682            // DEPYLER-0732: Aggregate functions that iterate without taking ownership
683            // In Rust, these use .iter() which borrows the collection
684            "sum",
685            "min",
686            "max",
687            "any",
688            "all",
689            "sorted",
690            "reversed",
691            "enumerate",
692            "zip",
693            "map",
694            "filter",
695        ];
696
697        // Known functions that take ownership
698        let ownership_functions = ["append", "extend", "insert", "remove", "pop", "sort"];
699
700        if borrowing_functions.contains(&func_name) {
701            false
702        } else if ownership_functions.contains(&func_name) {
703            true
704        } else {
705            // DEPYLER-0732: Python semantics - function calls pass references, not ownership
706            // In Python, passing a value to a function doesn't transfer ownership.
707            // The caller can still use the value after the call.
708            // Default to borrowing to match Python semantics and prevent E0382 errors.
709            false
710        }
711    }
712
713    /// Check if currently in a loop context
714    fn is_in_loop(&self) -> bool {
715        self.context_stack
716            .iter()
717            .any(|ctx| matches!(ctx, AnalysisContext::Loop))
718    }
719
720    /// Check if currently in a conditional context
721    fn is_in_conditional(&self) -> bool {
722        self.context_stack
723            .iter()
724            .any(|ctx| matches!(ctx, AnalysisContext::Conditional))
725    }
726
727    /// Determine optimal borrowing strategies based on usage patterns
728    fn determine_strategies(
729        &self,
730        func: &HirFunction,
731        type_mapper: &TypeMapper,
732    ) -> BorrowingAnalysisResult {
733        let mut strategies = IndexMap::new();
734        let mut insights = Vec::new();
735
736        for param in &func.params {
737            let usage = self
738                .param_usage
739                .get(&param.name)
740                .cloned()
741                .unwrap_or_default();
742            let rust_type = type_mapper.map_type(&param.ty);
743
744            let strategy = self.determine_parameter_strategy(
745                &param.name,
746                &usage,
747                &rust_type,
748                &param.ty,
749                &mut insights,
750            );
751
752            strategies.insert(param.name.clone(), strategy);
753        }
754
755        BorrowingAnalysisResult {
756            param_strategies: strategies,
757            insights,
758        }
759    }
760
761    /// Determine strategy for a single parameter
762    fn determine_parameter_strategy(
763        &self,
764        param_name: &str,
765        usage: &ParameterUsagePattern,
766        rust_type: &RustType,
767        python_type: &PythonType,
768        insights: &mut Vec<BorrowingInsight>,
769    ) -> BorrowingStrategy {
770        // CITL: Trace borrow strategy determination
771        trace_decision!(
772            category = DecisionCategory::BorrowStrategy,
773            name = "param_strategy",
774            chosen = param_name,
775            alternatives = ["owned", "borrowed", "mutable_ref", "copy", "arc"],
776            confidence = 0.85
777        );
778
779        // Always check if type is Copy for insights
780        if self.is_copy_type(rust_type) {
781            insights.push(BorrowingInsight::SuggestCopyDerive(param_name.to_string()));
782        }
783
784        // If parameter is moved, we must take ownership
785        if usage.is_moved {
786            // Check if move is necessary
787            if !usage.escapes_through_return && !usage.is_stored {
788                insights.push(BorrowingInsight::UnnecessaryMove(param_name.to_string()));
789            }
790            return BorrowingStrategy::TakeOwnership;
791        }
792
793        // If parameter escapes through return and matches return type, take ownership
794        // (except for strings which have special handling)
795        if usage.escapes_through_return && !matches!(python_type, PythonType::String) {
796            if let Some(ref ret_type) = self.return_type {
797                if python_type == ret_type {
798                    return BorrowingStrategy::TakeOwnership;
799                }
800            }
801        }
802
803        // If parameter is stored in a structure, consider shared ownership
804        if usage.is_stored {
805            return BorrowingStrategy::UseSharedOwnership {
806                is_thread_safe: false,
807            };
808        }
809
810        // If used in closure, determine capture strategy
811        if usage.used_in_closure {
812            // Complex analysis needed - for now, be conservative
813            return BorrowingStrategy::TakeOwnership;
814        }
815
816        // Check if type is Copy - take ownership (cheap)
817        if self.is_copy_type(rust_type) {
818            return BorrowingStrategy::TakeOwnership; // Cheap to copy
819        }
820
821        // String-specific optimizations
822        if matches!(python_type, PythonType::String) {
823            return self.determine_string_strategy(param_name, usage);
824        }
825
826        // Determine mutability needs
827        if usage.is_mutated {
828            BorrowingStrategy::BorrowMutable { lifetime: None }
829        } else if usage.is_read {
830            BorrowingStrategy::BorrowImmutable { lifetime: None }
831        } else {
832            // Parameter unused - take ownership (simplest)
833            BorrowingStrategy::TakeOwnership
834        }
835    }
836
837    /// Determine optimal string handling strategy
838    fn determine_string_strategy(
839        &self,
840        _param_name: &str,
841        usage: &ParameterUsagePattern,
842    ) -> BorrowingStrategy {
843        // CITL: Trace string strategy determination
844        trace_decision!(
845            category = DecisionCategory::BorrowStrategy,
846            name = "string_strategy",
847            chosen = "string_analysis",
848            alternatives = ["String", "&str", "Cow", "AsRef", "Into"],
849            confidence = 0.82
850        );
851
852        // For strings that are reassigned (not string mutation itself),
853        // we can actually take ownership since we're replacing the entire string
854        // This is a Python-specific pattern where `s = s + "!"` creates a new string
855
856        // If string is moved to another function, we need ownership
857        if usage.is_moved {
858            return BorrowingStrategy::TakeOwnership;
859        }
860
861        // If string is reassigned (Python pattern), take ownership
862        // This must come before escape check to handle mutation correctly
863        if usage.is_mutated {
864            return BorrowingStrategy::TakeOwnership;
865        }
866
867        // If string escapes, we need to take ownership to avoid lifetime issues
868        // DEPYLER-0357: Cow lifetime mismatch resolution
869        // Previous behavior: Generated Cow<'static, str> which creates impossible
870        // lifetime constraints when returning borrowed parameters
871        // New behavior: Use owned String for simplicity and correctness
872        if usage.escapes_through_return {
873            return BorrowingStrategy::TakeOwnership;
874        }
875
876        // For read-only strings, prefer borrowing
877        if usage.is_read && !usage.is_moved && !usage.is_mutated {
878            return BorrowingStrategy::BorrowImmutable { lifetime: None };
879        }
880
881        // Default to ownership for simplicity
882        BorrowingStrategy::TakeOwnership
883    }
884
885    /// Check if a type implements Copy
886    #[allow(clippy::only_used_in_recursion)]
887    fn is_copy_type(&self, rust_type: &RustType) -> bool {
888        match rust_type {
889            RustType::Primitive(_) => true,
890            RustType::Unit => true,
891            RustType::Tuple(types) => types.iter().all(|t| self.is_copy_type(t)),
892            _ => false,
893        }
894    }
895
896    /// DEPYLER-PHASE2: Run enhanced ownership analysis with escape analysis
897    ///
898    /// This method combines the traditional borrowing analysis with the new
899    /// escape analysis to detect use-after-move violations and aliasing patterns.
900    ///
901    /// Returns enhanced insights including:
902    /// - Use-after-move errors with suggested fixes
903    /// - Aliasing patterns requiring cloning
904    /// - Sites where borrows should be used instead of moves
905    pub fn analyze_with_escape_analysis(
906        &mut self,
907        func: &depyler_hir::hir::HirFunction,
908        type_mapper: &TypeMapper,
909    ) -> EnhancedBorrowingResult {
910        use crate::escape_analysis::{analyze_ownership, OwnershipFix};
911
912        // Run traditional borrowing analysis
913        let basic_result = self.analyze_function(func, type_mapper);
914
915        // Run escape analysis
916        let ownership_result = analyze_ownership(func);
917
918        // Combine results
919        let mut ownership_fixes = Vec::new();
920
921        for error in &ownership_result.use_after_move_errors {
922            ownership_fixes.push(OwnershipFixSite {
923                variable: error.var.clone(),
924                site_index: error.move_site.stmt_index,
925                fix: match &error.fix {
926                    OwnershipFix::Borrow => OwnershipFixType::UseBorrow,
927                    OwnershipFix::MutableBorrow => OwnershipFixType::UseMutableBorrow,
928                    OwnershipFix::Clone => OwnershipFixType::InsertClone,
929                    OwnershipFix::CloneAtAssignment { var } => {
930                        OwnershipFixType::CloneAtAssignment(var.clone())
931                    }
932                    OwnershipFix::Reject { reason } => {
933                        OwnershipFixType::RejectPattern(reason.clone())
934                    }
935                },
936            });
937        }
938
939        for pattern in &ownership_result.aliasing_patterns {
940            if pattern.source_used_after && pattern.alias_used_after {
941                ownership_fixes.push(OwnershipFixSite {
942                    variable: pattern.alias.clone(),
943                    site_index: 0, // Will be filled with actual assignment index
944                    fix: OwnershipFixType::CloneAtAssignment(pattern.source.clone()),
945                });
946            }
947        }
948
949        EnhancedBorrowingResult {
950            basic_result,
951            use_after_move_count: ownership_result.use_after_move_errors.len(),
952            aliasing_pattern_count: ownership_result.aliasing_patterns.len(),
953            ownership_fixes,
954        }
955    }
956}
957
958/// Result of enhanced borrowing analysis with escape analysis
959#[derive(Debug, Clone)]
960pub struct EnhancedBorrowingResult {
961    /// Traditional borrowing analysis result
962    pub basic_result: BorrowingAnalysisResult,
963    /// Number of use-after-move errors detected
964    pub use_after_move_count: usize,
965    /// Number of aliasing patterns requiring cloning
966    pub aliasing_pattern_count: usize,
967    /// Suggested ownership fixes
968    pub ownership_fixes: Vec<OwnershipFixSite>,
969}
970
971/// A site where an ownership fix is needed
972#[derive(Debug, Clone)]
973pub struct OwnershipFixSite {
974    /// Variable name
975    pub variable: String,
976    /// Statement index where fix is needed
977    pub site_index: usize,
978    /// Type of fix to apply
979    pub fix: OwnershipFixType,
980}
981
982/// Type of ownership fix to apply
983#[derive(Debug, Clone, PartialEq, Eq)]
984pub enum OwnershipFixType {
985    /// Change `f(x)` to `f(&x)`
986    UseBorrow,
987    /// Change `f(x)` to `f(&mut x)`
988    UseMutableBorrow,
989    /// Change `f(x)` to `f(x.clone())`
990    InsertClone,
991    /// Change `let b = a` to `let b = a.clone()`
992    CloneAtAssignment(String),
993    /// Pattern cannot be fixed - requires Poka-Yoke rejection
994    RejectPattern(String),
995}
996
997#[cfg(test)]
998mod tests {
999    use super::*;
1000    use depyler_hir::hir::{FunctionProperties, HirParam, Literal};
1001    use depyler_annotations::TranspilationAnnotations;
1002    use smallvec::smallvec;
1003
1004    #[test]
1005    fn test_basic_borrowing_analysis() {
1006        let mut ctx = BorrowingContext::new(Some(PythonType::Int));
1007        let type_mapper = TypeMapper::new();
1008
1009        let func = HirFunction {
1010            name: "add_one".to_string(),
1011            params: smallvec![HirParam::new("x".to_string(), PythonType::Int)],
1012            ret_type: PythonType::Int,
1013            body: vec![HirStmt::Return(Some(HirExpr::Binary {
1014                op: depyler_hir::hir::BinOp::Add,
1015                left: Box::new(HirExpr::Var("x".to_string())),
1016                right: Box::new(HirExpr::Literal(Literal::Int(1))),
1017            }))],
1018            properties: FunctionProperties::default(),
1019            annotations: TranspilationAnnotations::default(),
1020            docstring: None,
1021        };
1022
1023        let result = ctx.analyze_function(&func, &type_mapper);
1024
1025        // Integer parameter should be taken by value (Copy type)
1026        let x_strategy = result.param_strategies.get("x").unwrap();
1027        assert_eq!(*x_strategy, BorrowingStrategy::TakeOwnership);
1028    }
1029
1030    #[test]
1031    fn test_string_borrowing() {
1032        let mut ctx = BorrowingContext::new(Some(PythonType::Int));
1033        let type_mapper = TypeMapper::new();
1034
1035        let func = HirFunction {
1036            name: "string_len".to_string(),
1037            params: smallvec![HirParam::new("s".to_string(), PythonType::String)],
1038            ret_type: PythonType::Int,
1039            body: vec![HirStmt::Return(Some(HirExpr::Call {
1040                func: "len".to_string(),
1041                args: vec![HirExpr::Var("s".to_string())],
1042                kwargs: vec![],
1043            }))],
1044            properties: FunctionProperties::default(),
1045            annotations: TranspilationAnnotations::default(),
1046            docstring: None,
1047        };
1048
1049        let result = ctx.analyze_function(&func, &type_mapper);
1050
1051        // String parameter should be borrowed (not moved)
1052        let s_strategy = result.param_strategies.get("s").unwrap();
1053        assert!(matches!(
1054            s_strategy,
1055            BorrowingStrategy::BorrowImmutable { .. }
1056        ));
1057    }
1058
1059    #[test]
1060    fn test_mutation_detection() {
1061        let mut ctx = BorrowingContext::new(None);
1062        let type_mapper = TypeMapper::new();
1063
1064        let func = HirFunction {
1065            name: "mutate_list".to_string(),
1066            params: smallvec![HirParam::new(
1067                "lst".to_string(),
1068                PythonType::List(Box::new(PythonType::Int))
1069            )],
1070            ret_type: PythonType::None,
1071            body: vec![HirStmt::Expr(HirExpr::Call {
1072                func: "append".to_string(),
1073                args: vec![
1074                    HirExpr::Var("lst".to_string()),
1075                    HirExpr::Literal(Literal::Int(42)),
1076                ],
1077                kwargs: vec![],
1078            })],
1079            properties: FunctionProperties::default(),
1080            annotations: TranspilationAnnotations::default(),
1081            docstring: None,
1082        };
1083
1084        let result = ctx.analyze_function(&func, &type_mapper);
1085
1086        // List parameter should be moved (append takes ownership in our analysis)
1087        let lst_strategy = result.param_strategies.get("lst").unwrap();
1088        assert_eq!(*lst_strategy, BorrowingStrategy::TakeOwnership);
1089    }
1090
1091    // BorrowingContext tests
1092    #[test]
1093    fn test_borrowing_context_new_with_none() {
1094        let ctx = BorrowingContext::new(None);
1095        assert!(ctx.param_usage.is_empty());
1096        assert!(ctx.moved_vars.is_empty());
1097        assert!(ctx.mut_borrowed_vars.is_empty());
1098        assert!(ctx.immut_borrowed_vars.is_empty());
1099    }
1100
1101    #[test]
1102    fn test_borrowing_context_new_with_return_type() {
1103        let ctx = BorrowingContext::new(Some(PythonType::String));
1104        assert!(ctx.return_type.is_some());
1105    }
1106
1107    // ParameterUsagePattern tests
1108    #[test]
1109    fn test_parameter_usage_pattern_default() {
1110        let pattern = ParameterUsagePattern::default();
1111        assert!(!pattern.is_read);
1112        assert!(!pattern.is_mutated);
1113        assert!(!pattern.is_moved);
1114        assert!(!pattern.escapes_through_return);
1115        assert!(!pattern.is_stored);
1116        assert!(!pattern.used_in_closure);
1117        assert!(!pattern.used_in_loop);
1118        assert!(pattern.field_accesses.is_empty());
1119        assert!(pattern.method_calls.is_empty());
1120        assert!(pattern.usage_sites.is_empty());
1121    }
1122
1123    #[test]
1124    fn test_parameter_usage_pattern_clone() {
1125        let pattern = ParameterUsagePattern {
1126            is_read: true,
1127            is_mutated: true,
1128            ..Default::default()
1129        };
1130        let cloned = pattern.clone();
1131        assert_eq!(pattern.is_read, cloned.is_read);
1132        assert_eq!(pattern.is_mutated, cloned.is_mutated);
1133    }
1134
1135    // UsageSite tests
1136    #[test]
1137    fn test_usage_site_creation() {
1138        let site = UsageSite {
1139            usage_type: UsageType::Read,
1140            in_loop: true,
1141            in_conditional: false,
1142            borrow_depth: 1,
1143        };
1144        assert!(site.in_loop);
1145        assert!(!site.in_conditional);
1146        assert_eq!(site.borrow_depth, 1);
1147    }
1148
1149    #[test]
1150    fn test_usage_site_clone() {
1151        let site = UsageSite {
1152            usage_type: UsageType::Write,
1153            in_loop: false,
1154            in_conditional: true,
1155            borrow_depth: 2,
1156        };
1157        let cloned = site.clone();
1158        assert_eq!(site.in_loop, cloned.in_loop);
1159        assert_eq!(site.borrow_depth, cloned.borrow_depth);
1160    }
1161
1162    // UsageType tests
1163    #[test]
1164    fn test_usage_type_read() {
1165        let usage = UsageType::Read;
1166        assert_eq!(usage, UsageType::Read);
1167    }
1168
1169    #[test]
1170    fn test_usage_type_write() {
1171        let usage = UsageType::Write;
1172        assert_eq!(usage, UsageType::Write);
1173    }
1174
1175    #[test]
1176    fn test_usage_type_method_call() {
1177        let usage = UsageType::MethodCall("append".to_string());
1178        assert!(matches!(usage, UsageType::MethodCall(_)));
1179    }
1180
1181    #[test]
1182    fn test_usage_type_function_arg_owned() {
1183        let usage = UsageType::FunctionArg {
1184            takes_ownership: true,
1185        };
1186        assert!(matches!(
1187            usage,
1188            UsageType::FunctionArg {
1189                takes_ownership: true
1190            }
1191        ));
1192    }
1193
1194    #[test]
1195    fn test_usage_type_function_arg_borrowed() {
1196        let usage = UsageType::FunctionArg {
1197            takes_ownership: false,
1198        };
1199        assert!(matches!(
1200            usage,
1201            UsageType::FunctionArg {
1202                takes_ownership: false
1203            }
1204        ));
1205    }
1206
1207    #[test]
1208    fn test_usage_type_return() {
1209        let usage = UsageType::Return;
1210        assert_eq!(usage, UsageType::Return);
1211    }
1212
1213    #[test]
1214    fn test_usage_type_store() {
1215        let usage = UsageType::Store;
1216        assert_eq!(usage, UsageType::Store);
1217    }
1218
1219    #[test]
1220    fn test_usage_type_closure() {
1221        let usage = UsageType::Closure {
1222            captures_by_value: true,
1223        };
1224        assert!(matches!(
1225            usage,
1226            UsageType::Closure {
1227                captures_by_value: true
1228            }
1229        ));
1230    }
1231
1232    #[test]
1233    fn test_usage_type_field_access() {
1234        let usage = UsageType::FieldAccess("name".to_string());
1235        assert!(matches!(usage, UsageType::FieldAccess(_)));
1236    }
1237
1238    #[test]
1239    fn test_usage_type_index_access() {
1240        let usage = UsageType::IndexAccess;
1241        assert_eq!(usage, UsageType::IndexAccess);
1242    }
1243
1244    #[test]
1245    fn test_usage_type_clone() {
1246        let usage = UsageType::MethodCall("push".to_string());
1247        let cloned = usage.clone();
1248        assert_eq!(usage, cloned);
1249    }
1250
1251    // BorrowingStrategy tests
1252    #[test]
1253    fn test_borrowing_strategy_take_ownership() {
1254        let strategy = BorrowingStrategy::TakeOwnership;
1255        assert_eq!(strategy, BorrowingStrategy::TakeOwnership);
1256    }
1257
1258    #[test]
1259    fn test_borrowing_strategy_borrow_immutable() {
1260        let strategy = BorrowingStrategy::BorrowImmutable { lifetime: None };
1261        assert!(matches!(
1262            strategy,
1263            BorrowingStrategy::BorrowImmutable { .. }
1264        ));
1265    }
1266
1267    #[test]
1268    fn test_borrowing_strategy_borrow_immutable_with_lifetime() {
1269        let strategy = BorrowingStrategy::BorrowImmutable {
1270            lifetime: Some("'a".to_string()),
1271        };
1272        if let BorrowingStrategy::BorrowImmutable { lifetime } = &strategy {
1273            assert_eq!(lifetime, &Some("'a".to_string()));
1274        }
1275    }
1276
1277    #[test]
1278    fn test_borrowing_strategy_borrow_mutable() {
1279        let strategy = BorrowingStrategy::BorrowMutable { lifetime: None };
1280        assert!(matches!(strategy, BorrowingStrategy::BorrowMutable { .. }));
1281    }
1282
1283    #[test]
1284    fn test_borrowing_strategy_use_cow() {
1285        let strategy = BorrowingStrategy::UseCow {
1286            lifetime: "'a".to_string(),
1287        };
1288        if let BorrowingStrategy::UseCow { lifetime } = &strategy {
1289            assert_eq!(lifetime, "'a");
1290        }
1291    }
1292
1293    #[test]
1294    fn test_borrowing_strategy_use_shared_ownership_arc() {
1295        let strategy = BorrowingStrategy::UseSharedOwnership {
1296            is_thread_safe: true,
1297        };
1298        if let BorrowingStrategy::UseSharedOwnership { is_thread_safe } = strategy {
1299            assert!(is_thread_safe);
1300        }
1301    }
1302
1303    #[test]
1304    fn test_borrowing_strategy_use_shared_ownership_rc() {
1305        let strategy = BorrowingStrategy::UseSharedOwnership {
1306            is_thread_safe: false,
1307        };
1308        if let BorrowingStrategy::UseSharedOwnership { is_thread_safe } = strategy {
1309            assert!(!is_thread_safe);
1310        }
1311    }
1312
1313    #[test]
1314    fn test_borrowing_strategy_clone() {
1315        let strategy = BorrowingStrategy::TakeOwnership;
1316        let cloned = strategy.clone();
1317        assert_eq!(strategy, cloned);
1318    }
1319
1320    // BorrowingInsight tests
1321    #[test]
1322    fn test_borrowing_insight_unnecessary_move() {
1323        let insight = BorrowingInsight::UnnecessaryMove("x".to_string());
1324        assert!(matches!(insight, BorrowingInsight::UnnecessaryMove(_)));
1325    }
1326
1327    #[test]
1328    fn test_borrowing_insight_lifetime_optimization() {
1329        let insight = BorrowingInsight::LifetimeOptimization {
1330            param: "s".to_string(),
1331            suggestion: "Use 'a".to_string(),
1332        };
1333        assert!(matches!(
1334            insight,
1335            BorrowingInsight::LifetimeOptimization { .. }
1336        ));
1337    }
1338
1339    #[test]
1340    fn test_borrowing_insight_suggest_copy() {
1341        let insight = BorrowingInsight::SuggestCopyDerive("Point".to_string());
1342        assert!(matches!(insight, BorrowingInsight::SuggestCopyDerive(_)));
1343    }
1344
1345    #[test]
1346    fn test_borrowing_insight_borrow_conflict() {
1347        let insight = BorrowingInsight::PotentialBorrowConflict {
1348            param: "data".to_string(),
1349            locations: vec!["line 10".to_string(), "line 20".to_string()],
1350        };
1351        assert!(matches!(
1352            insight,
1353            BorrowingInsight::PotentialBorrowConflict { .. }
1354        ));
1355    }
1356
1357    #[test]
1358    fn test_borrowing_insight_clone() {
1359        let insight = BorrowingInsight::UnnecessaryMove("y".to_string());
1360        let cloned = insight.clone();
1361        assert!(matches!(cloned, BorrowingInsight::UnnecessaryMove(_)));
1362    }
1363
1364    // BorrowingAnalysisResult tests
1365    #[test]
1366    fn test_borrowing_analysis_result_creation() {
1367        let result = BorrowingAnalysisResult {
1368            param_strategies: IndexMap::new(),
1369            insights: Vec::new(),
1370        };
1371        assert!(result.param_strategies.is_empty());
1372        assert!(result.insights.is_empty());
1373    }
1374
1375    #[test]
1376    fn test_borrowing_analysis_result_with_strategies() {
1377        let mut strategies = IndexMap::new();
1378        strategies.insert("x".to_string(), BorrowingStrategy::TakeOwnership);
1379        let result = BorrowingAnalysisResult {
1380            param_strategies: strategies,
1381            insights: Vec::new(),
1382        };
1383        assert_eq!(result.param_strategies.len(), 1);
1384        assert!(result.param_strategies.contains_key("x"));
1385    }
1386
1387    #[test]
1388    fn test_borrowing_analysis_result_clone() {
1389        let result = BorrowingAnalysisResult {
1390            param_strategies: IndexMap::new(),
1391            insights: vec![BorrowingInsight::SuggestCopyDerive("T".to_string())],
1392        };
1393        let cloned = result.clone();
1394        assert_eq!(result.insights.len(), cloned.insights.len());
1395    }
1396
1397    // Integration tests for analyze_function
1398    #[test]
1399    fn test_analyze_function_with_float_param() {
1400        let mut ctx = BorrowingContext::new(Some(PythonType::Float));
1401        let type_mapper = TypeMapper::new();
1402
1403        let func = HirFunction {
1404            name: "double".to_string(),
1405            params: smallvec![HirParam::new("x".to_string(), PythonType::Float)],
1406            ret_type: PythonType::Float,
1407            body: vec![HirStmt::Return(Some(HirExpr::Binary {
1408                op: depyler_hir::hir::BinOp::Mul,
1409                left: Box::new(HirExpr::Var("x".to_string())),
1410                right: Box::new(HirExpr::Literal(Literal::Float(2.0))),
1411            }))],
1412            properties: FunctionProperties::default(),
1413            annotations: TranspilationAnnotations::default(),
1414            docstring: None,
1415        };
1416
1417        let result = ctx.analyze_function(&func, &type_mapper);
1418        // Float is Copy, so should be taken by value
1419        let x_strategy = result.param_strategies.get("x").unwrap();
1420        assert_eq!(*x_strategy, BorrowingStrategy::TakeOwnership);
1421    }
1422
1423    #[test]
1424    fn test_analyze_function_with_bool_param() {
1425        let mut ctx = BorrowingContext::new(Some(PythonType::Bool));
1426        let type_mapper = TypeMapper::new();
1427
1428        let func = HirFunction {
1429            name: "negate".to_string(),
1430            params: smallvec![HirParam::new("b".to_string(), PythonType::Bool)],
1431            ret_type: PythonType::Bool,
1432            body: vec![HirStmt::Return(Some(HirExpr::Unary {
1433                op: depyler_hir::hir::UnaryOp::Not,
1434                operand: Box::new(HirExpr::Var("b".to_string())),
1435            }))],
1436            properties: FunctionProperties::default(),
1437            annotations: TranspilationAnnotations::default(),
1438            docstring: None,
1439        };
1440
1441        let result = ctx.analyze_function(&func, &type_mapper);
1442        // Bool is Copy, so should be taken by value
1443        let b_strategy = result.param_strategies.get("b").unwrap();
1444        assert_eq!(*b_strategy, BorrowingStrategy::TakeOwnership);
1445    }
1446
1447    #[test]
1448    fn test_analyze_function_empty_body() {
1449        let mut ctx = BorrowingContext::new(None);
1450        let type_mapper = TypeMapper::new();
1451
1452        let func = HirFunction {
1453            name: "noop".to_string(),
1454            params: smallvec![HirParam::new("x".to_string(), PythonType::Int)],
1455            ret_type: PythonType::None,
1456            body: vec![],
1457            properties: FunctionProperties::default(),
1458            annotations: TranspilationAnnotations::default(),
1459            docstring: None,
1460        };
1461
1462        let result = ctx.analyze_function(&func, &type_mapper);
1463        // Even without usage, integer should be taken by value
1464        assert!(result.param_strategies.contains_key("x"));
1465    }
1466
1467    #[test]
1468    fn test_analyze_function_no_params() {
1469        let mut ctx = BorrowingContext::new(Some(PythonType::Int));
1470        let type_mapper = TypeMapper::new();
1471
1472        let func = HirFunction {
1473            name: "get_zero".to_string(),
1474            params: smallvec![],
1475            ret_type: PythonType::Int,
1476            body: vec![HirStmt::Return(Some(HirExpr::Literal(Literal::Int(0))))],
1477            properties: FunctionProperties::default(),
1478            annotations: TranspilationAnnotations::default(),
1479            docstring: None,
1480        };
1481
1482        let result = ctx.analyze_function(&func, &type_mapper);
1483        assert!(result.param_strategies.is_empty());
1484    }
1485
1486    #[test]
1487    fn test_analyze_function_multiple_params() {
1488        let mut ctx = BorrowingContext::new(Some(PythonType::Int));
1489        let type_mapper = TypeMapper::new();
1490
1491        let func = HirFunction {
1492            name: "add".to_string(),
1493            params: smallvec![
1494                HirParam::new("a".to_string(), PythonType::Int),
1495                HirParam::new("b".to_string(), PythonType::Int)
1496            ],
1497            ret_type: PythonType::Int,
1498            body: vec![HirStmt::Return(Some(HirExpr::Binary {
1499                op: depyler_hir::hir::BinOp::Add,
1500                left: Box::new(HirExpr::Var("a".to_string())),
1501                right: Box::new(HirExpr::Var("b".to_string())),
1502            }))],
1503            properties: FunctionProperties::default(),
1504            annotations: TranspilationAnnotations::default(),
1505            docstring: None,
1506        };
1507
1508        let result = ctx.analyze_function(&func, &type_mapper);
1509        assert_eq!(result.param_strategies.len(), 2);
1510        assert!(result.param_strategies.contains_key("a"));
1511        assert!(result.param_strategies.contains_key("b"));
1512    }
1513
1514    // === DEPYLER-COVERAGE-95: Additional tests for untested components ===
1515
1516    #[test]
1517    fn test_analysis_context_loop_debug() {
1518        let ctx = AnalysisContext::Loop;
1519        let debug = format!("{:?}", ctx);
1520        assert!(debug.contains("Loop"));
1521    }
1522
1523    #[test]
1524    fn test_analysis_context_conditional_debug() {
1525        let ctx = AnalysisContext::Conditional;
1526        let debug = format!("{:?}", ctx);
1527        assert!(debug.contains("Conditional"));
1528    }
1529
1530    #[test]
1531    fn test_analysis_context_closure_debug() {
1532        let ctx = AnalysisContext::Closure {
1533            captures: HashSet::from(["x".to_string(), "y".to_string()]),
1534        };
1535        let debug = format!("{:?}", ctx);
1536        assert!(debug.contains("Closure"));
1537        assert!(debug.contains("captures"));
1538    }
1539
1540    #[test]
1541    fn test_analysis_context_function_debug() {
1542        let ctx = AnalysisContext::Function;
1543        let debug = format!("{:?}", ctx);
1544        assert!(debug.contains("Function"));
1545    }
1546
1547    #[test]
1548    fn test_analysis_context_clone() {
1549        let ctx = AnalysisContext::Closure {
1550            captures: HashSet::from(["var".to_string()]),
1551        };
1552        let cloned = ctx.clone();
1553        if let AnalysisContext::Closure { captures } = cloned {
1554            assert!(captures.contains("var"));
1555        } else {
1556            panic!("Expected Closure variant");
1557        }
1558    }
1559
1560    #[test]
1561    fn test_borrowing_context_debug() {
1562        let ctx = BorrowingContext::new(None);
1563        let debug = format!("{:?}", ctx);
1564        assert!(debug.contains("BorrowingContext"));
1565    }
1566
1567    #[test]
1568    fn test_borrowing_context_initial_state() {
1569        let ctx = BorrowingContext::new(Some(PythonType::String));
1570        assert!(ctx.param_usage.is_empty());
1571        assert!(ctx.moved_vars.is_empty());
1572        assert!(ctx.mut_borrowed_vars.is_empty());
1573        assert!(ctx.immut_borrowed_vars.is_empty());
1574        assert!(!ctx.context_stack.is_empty()); // Should have Function context
1575    }
1576
1577    #[test]
1578    fn test_parameter_usage_pattern_all_fields() {
1579        let mut pattern = ParameterUsagePattern {
1580            is_read: true,
1581            is_mutated: true,
1582            is_moved: false,
1583            escapes_through_return: true,
1584            is_stored: false,
1585            used_in_closure: true,
1586            used_in_loop: true,
1587            ..Default::default()
1588        };
1589        pattern.field_accesses.insert("name".to_string());
1590        pattern.method_calls.insert("len".to_string());
1591
1592        assert!(pattern.is_read);
1593        assert!(pattern.is_mutated);
1594        assert!(!pattern.is_moved);
1595        assert!(pattern.escapes_through_return);
1596        assert!(!pattern.is_stored);
1597        assert!(pattern.used_in_closure);
1598        assert!(pattern.used_in_loop);
1599        assert!(pattern.field_accesses.contains("name"));
1600        assert!(pattern.method_calls.contains("len"));
1601    }
1602
1603    #[test]
1604    fn test_parameter_usage_pattern_debug() {
1605        let pattern = ParameterUsagePattern::default();
1606        let debug = format!("{:?}", pattern);
1607        assert!(debug.contains("ParameterUsagePattern"));
1608        assert!(debug.contains("is_read"));
1609    }
1610
1611    #[test]
1612    fn test_usage_site_all_fields() {
1613        let site = UsageSite {
1614            usage_type: UsageType::Write,
1615            in_loop: true,
1616            in_conditional: false,
1617            borrow_depth: 2,
1618        };
1619        assert!(matches!(site.usage_type, UsageType::Write));
1620        assert!(site.in_loop);
1621        assert!(!site.in_conditional);
1622        assert_eq!(site.borrow_depth, 2);
1623    }
1624
1625    #[test]
1626    fn test_usage_site_debug() {
1627        let site = UsageSite {
1628            usage_type: UsageType::Read,
1629            in_loop: false,
1630            in_conditional: true,
1631            borrow_depth: 0,
1632        };
1633        let debug = format!("{:?}", site);
1634        assert!(debug.contains("UsageSite"));
1635        assert!(debug.contains("Read"));
1636    }
1637
1638    #[test]
1639    fn test_usage_type_read_eq() {
1640        assert_eq!(UsageType::Read, UsageType::Read);
1641        assert_ne!(UsageType::Read, UsageType::Write);
1642    }
1643
1644    #[test]
1645    fn test_usage_type_closure_variants() {
1646        let by_value = UsageType::Closure {
1647            captures_by_value: true,
1648        };
1649        let by_ref = UsageType::Closure {
1650            captures_by_value: false,
1651        };
1652        assert_ne!(by_value, by_ref);
1653    }
1654
1655    #[test]
1656    fn test_borrowing_strategy_eq() {
1657        assert_eq!(
1658            BorrowingStrategy::TakeOwnership,
1659            BorrowingStrategy::TakeOwnership
1660        );
1661        assert_ne!(
1662            BorrowingStrategy::TakeOwnership,
1663            BorrowingStrategy::BorrowMutable { lifetime: None }
1664        );
1665    }
1666
1667    #[test]
1668    fn test_borrowing_strategy_debug_all() {
1669        let strategies = [
1670            BorrowingStrategy::TakeOwnership,
1671            BorrowingStrategy::BorrowImmutable { lifetime: None },
1672            BorrowingStrategy::BorrowImmutable {
1673                lifetime: Some("a".to_string()),
1674            },
1675            BorrowingStrategy::BorrowMutable { lifetime: None },
1676            BorrowingStrategy::UseCow {
1677                lifetime: "b".to_string(),
1678            },
1679            BorrowingStrategy::UseSharedOwnership {
1680                is_thread_safe: true,
1681            },
1682        ];
1683        for strategy in strategies {
1684            let debug = format!("{:?}", strategy);
1685            assert!(!debug.is_empty());
1686        }
1687    }
1688
1689    #[test]
1690    fn test_borrowing_insight_debug_all() {
1691        let insights = [
1692            BorrowingInsight::UnnecessaryMove("x".to_string()),
1693            BorrowingInsight::LifetimeOptimization {
1694                param: "p".to_string(),
1695                suggestion: "Consider borrow".to_string(),
1696            },
1697            BorrowingInsight::SuggestCopyDerive("y".to_string()),
1698            BorrowingInsight::PotentialBorrowConflict {
1699                param: "z".to_string(),
1700                locations: vec!["line 1".to_string(), "line 2".to_string()],
1701            },
1702        ];
1703        for insight in insights {
1704            let debug = format!("{:?}", insight);
1705            assert!(!debug.is_empty());
1706        }
1707    }
1708
1709    #[test]
1710    fn test_borrowing_analysis_result_debug() {
1711        let result = BorrowingAnalysisResult {
1712            param_strategies: IndexMap::new(),
1713            insights: vec![],
1714        };
1715        let debug = format!("{:?}", result);
1716        assert!(debug.contains("BorrowingAnalysisResult"));
1717    }
1718
1719    #[test]
1720    fn test_borrowing_analysis_result_with_insights() {
1721        let result = BorrowingAnalysisResult {
1722            param_strategies: IndexMap::new(),
1723            insights: vec![
1724                BorrowingInsight::SuggestCopyDerive("val".to_string()),
1725                BorrowingInsight::LifetimeOptimization {
1726                    param: "p".to_string(),
1727                    suggestion: "Borrow instead".to_string(),
1728                },
1729            ],
1730        };
1731        assert_eq!(result.insights.len(), 2);
1732    }
1733}