Skip to main content

depyler_analysis/
escape_analysis.rs

1//! # Escape Analysis for Ownership Inference
2//!
3//! DEPYLER-PHASE2: Ownership Inference Engine - 80% Single-Shot Compile
4//!
5//! This module implements interprocedural escape analysis to detect:
6//! - Use-after-move violations (~60% of ownership errors)
7//! - Aliasing requiring strategic cloning (~15% of ownership errors)
8//! - Mutability requirements (~5% of ownership errors)
9//!
10//! ## Architecture
11//!
12//! ```text
13//! Python Code
14//!     │
15//!     ▼
16//! ┌─────────────────┐
17//! │ UseAfterMove    │ ← Detects: print(x); x.log()
18//! │ Analysis        │
19//! └────────┬────────┘
20//!          │
21//!          ▼
22//! ┌─────────────────┐
23//! │ Strategic Clone │ ← Detects: b = a; use(a); use(b)
24//! │ Analysis        │
25//! └────────┬────────┘
26//!          │
27//!          ▼
28//! ┌─────────────────┐
29//! │ Ownership Fix   │ ← Emits: &x, x.clone(), &mut x
30//! │ Generation      │
31//! └─────────────────┘
32//! ```
33
34use std::collections::{HashMap, HashSet};
35
36use depyler_hir::hir::{AssignTarget, HirExpr, HirFunction, HirStmt, Type};
37
38/// Location information for error reporting
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct SourceSpan {
41    /// Line number (1-indexed)
42    pub line: u32,
43    /// Column number (1-indexed)
44    pub column: u32,
45    /// Optional statement index for ordering
46    pub stmt_index: usize,
47}
48
49impl Default for SourceSpan {
50    fn default() -> Self {
51        Self {
52            line: 1,
53            column: 1,
54            stmt_index: 0,
55        }
56    }
57}
58
59/// A detected use-after-move violation
60#[derive(Debug, Clone)]
61pub struct UseAfterMoveError {
62    /// Variable name that was used after move
63    pub var: String,
64    /// Where the variable was moved (consumed)
65    pub move_site: SourceSpan,
66    /// Where the variable was used after move
67    pub use_site: SourceSpan,
68    /// The function that consumed the variable
69    pub moved_by: String,
70    /// Suggested fix
71    pub fix: OwnershipFix,
72}
73
74/// Suggested fix for an ownership violation
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub enum OwnershipFix {
77    /// Change to borrow: `f(x)` → `f(&x)`
78    Borrow,
79    /// Change to mutable borrow: `f(x)` → `f(&mut x)`
80    MutableBorrow,
81    /// Add clone: `f(x)` → `f(x.clone())`
82    Clone,
83    /// Add clone at assignment: `let b = a` → `let b = a.clone()`
84    CloneAtAssignment { var: String },
85    /// No fix available (Poka-Yoke rejection needed)
86    Reject { reason: String },
87}
88
89/// Result of analyzing a variable's aliasing pattern
90#[derive(Debug, Clone)]
91pub struct AliasingPattern {
92    /// Original variable
93    pub source: String,
94    /// Alias variable (from assignment)
95    pub alias: String,
96    /// Whether source is used after aliasing
97    pub source_used_after: bool,
98    /// Whether alias is used after aliasing
99    pub alias_used_after: bool,
100    /// Type of the variable
101    pub var_type: Type,
102}
103
104/// Tracks the movement state of a variable
105#[derive(Debug, Clone, PartialEq, Eq)]
106enum MoveState {
107    /// Variable is available
108    Available,
109    /// Variable was moved at this location
110    Moved(SourceSpan),
111    /// Variable might be moved (conditional)
112    ConditionallyMoved(SourceSpan),
113}
114
115/// Use-After-Move Analysis Engine
116///
117/// Walks function bodies in statement order, tracking when variables
118/// are consumed by ownership-taking functions and flagging subsequent uses.
119#[derive(Debug)]
120pub struct UseAfterMoveAnalysis {
121    /// Current move state of each variable
122    move_states: HashMap<String, MoveState>,
123    /// Detected use-after-move violations
124    errors: Vec<UseAfterMoveError>,
125    /// Current statement index for ordering
126    current_stmt_index: usize,
127    /// Functions known to take ownership
128    ownership_functions: HashSet<String>,
129    /// Functions known to borrow
130    borrowing_functions: HashSet<String>,
131}
132
133impl Default for UseAfterMoveAnalysis {
134    fn default() -> Self {
135        Self::new()
136    }
137}
138
139impl UseAfterMoveAnalysis {
140    /// Create a new analysis instance
141    pub fn new() -> Self {
142        Self {
143            move_states: HashMap::new(),
144            errors: Vec::new(),
145            current_stmt_index: 0,
146            ownership_functions: Self::default_ownership_functions(),
147            borrowing_functions: Self::default_borrowing_functions(),
148        }
149    }
150
151    /// Functions that take ownership of their arguments
152    fn default_ownership_functions() -> HashSet<String> {
153        [
154            "push",
155            "append",
156            "extend",
157            "insert",
158            "add",
159            "put",
160            "set",
161            "store",
162            "consume",
163            "take",
164            "into_iter",
165            "drain",
166        ]
167        .iter()
168        .map(|s| s.to_string())
169        .collect()
170    }
171
172    /// Functions that borrow their arguments
173    fn default_borrowing_functions() -> HashSet<String> {
174        [
175            "len",
176            "str",
177            "repr",
178            "format",
179            "print",
180            "isinstance",
181            "hasattr",
182            "getattr",
183            "contains",
184            "startswith",
185            "endswith",
186            "find",
187            "index",
188            "count",
189            "int",
190            "float",
191            "bool",
192            "sum",
193            "min",
194            "max",
195            "any",
196            "all",
197            "reversed",
198            "enumerate",
199            "zip",
200            "map",
201            "filter",
202            "iter",
203            "get",
204            "keys",
205            "values",
206            "items",
207            "copy",
208            "deepcopy",
209            "sorted",
210        ]
211        .iter()
212        .map(|s| s.to_string())
213        .collect()
214    }
215
216    /// Analyze a function for use-after-move violations
217    pub fn analyze_function(&mut self, func: &HirFunction) -> Vec<UseAfterMoveError> {
218        // Reset state
219        self.move_states.clear();
220        self.errors.clear();
221        self.current_stmt_index = 0;
222
223        // Initialize parameters as available
224        for param in &func.params {
225            self.move_states
226                .insert(param.name.clone(), MoveState::Available);
227        }
228
229        // Analyze each statement in order
230        for stmt in &func.body {
231            self.analyze_statement(stmt);
232            self.current_stmt_index += 1;
233        }
234
235        self.errors.clone()
236    }
237
238    /// Analyze a statement for move semantics
239    fn analyze_statement(&mut self, stmt: &HirStmt) {
240        match stmt {
241            HirStmt::Assign { target, value, .. } => {
242                // First, analyze the value for uses
243                self.analyze_expression_for_use(value);
244
245                // Handle assignment as potential move source
246                if let HirExpr::Var(source_name) = value {
247                    // This is a potential aliasing pattern: `b = a`
248                    if let AssignTarget::Symbol(target_name) = target {
249                        self.record_potential_alias(target_name, source_name);
250                    }
251                }
252
253                // Make target available
254                if let AssignTarget::Symbol(target_name) = target {
255                    self.move_states
256                        .insert(target_name.clone(), MoveState::Available);
257                }
258            }
259
260            HirStmt::Return(Some(expr)) => {
261                self.analyze_expression_for_use(expr);
262            }
263
264            HirStmt::Return(None) => {}
265
266            HirStmt::If {
267                condition,
268                then_body,
269                else_body,
270            } => {
271                self.analyze_expression_for_use(condition);
272
273                // Save state before branches
274                let state_before = self.move_states.clone();
275
276                // Analyze then branch
277                for stmt in then_body {
278                    self.analyze_statement(stmt);
279                    self.current_stmt_index += 1;
280                }
281                let state_after_then = self.move_states.clone();
282
283                // Restore and analyze else branch
284                self.move_states = state_before.clone();
285                if let Some(else_stmts) = else_body {
286                    for stmt in else_stmts {
287                        self.analyze_statement(stmt);
288                        self.current_stmt_index += 1;
289                    }
290                }
291                let state_after_else = self.move_states.clone();
292
293                // Merge states
294                self.merge_branch_states(&state_before, &state_after_then, &state_after_else);
295            }
296
297            HirStmt::While { condition, body } => {
298                self.analyze_expression_for_use(condition);
299
300                let state_before = self.move_states.clone();
301                for stmt in body {
302                    self.analyze_statement(stmt);
303                    self.current_stmt_index += 1;
304                }
305
306                self.merge_loop_state(&state_before);
307            }
308
309            HirStmt::For { target, iter, body } => {
310                // Iterating consumes the iterator
311                self.analyze_expression_for_move(iter, "for");
312
313                // Target is fresh each iteration
314                if let AssignTarget::Symbol(target_name) = target {
315                    self.move_states
316                        .insert(target_name.clone(), MoveState::Available);
317                }
318
319                let state_before = self.move_states.clone();
320                for stmt in body {
321                    self.analyze_statement(stmt);
322                    self.current_stmt_index += 1;
323                }
324
325                self.merge_loop_state(&state_before);
326            }
327
328            HirStmt::Block(stmts) => {
329                for stmt in stmts {
330                    self.analyze_statement(stmt);
331                    self.current_stmt_index += 1;
332                }
333            }
334
335            HirStmt::Expr(expr) => {
336                self.analyze_expression_for_use(expr);
337            }
338
339            HirStmt::Try {
340                body,
341                handlers: _,
342                orelse,
343                finalbody,
344            } => {
345                for stmt in body {
346                    self.analyze_statement(stmt);
347                    self.current_stmt_index += 1;
348                }
349                if let Some(else_stmts) = orelse {
350                    for stmt in else_stmts {
351                        self.analyze_statement(stmt);
352                        self.current_stmt_index += 1;
353                    }
354                }
355                if let Some(finally_stmts) = finalbody {
356                    for stmt in finally_stmts {
357                        self.analyze_statement(stmt);
358                        self.current_stmt_index += 1;
359                    }
360                }
361            }
362
363            HirStmt::With { body, .. } => {
364                for stmt in body {
365                    self.analyze_statement(stmt);
366                    self.current_stmt_index += 1;
367                }
368            }
369
370            HirStmt::Assert { test, msg } => {
371                self.analyze_expression_for_use(test);
372                if let Some(m) = msg {
373                    self.analyze_expression_for_use(m);
374                }
375            }
376
377            HirStmt::FunctionDef { body, params, .. } => {
378                // Nested function captures - analyze for use
379                let outer_vars: HashSet<_> = self.move_states.keys().cloned().collect();
380                let params_set: HashSet<String> = params.iter().map(|p| p.name.clone()).collect();
381                self.analyze_nested_captures(body, &outer_vars, &params_set);
382            }
383
384            HirStmt::Raise { exception, cause } => {
385                if let Some(e) = exception {
386                    self.analyze_expression_for_use(e);
387                }
388                if let Some(c) = cause {
389                    self.analyze_expression_for_use(c);
390                }
391            }
392
393            HirStmt::Pass | HirStmt::Break { .. } | HirStmt::Continue { .. } => {}
394        }
395    }
396
397    /// Analyze an expression to check if it uses a moved variable
398    fn analyze_expression_for_use(&mut self, expr: &HirExpr) {
399        match expr {
400            HirExpr::Var(name) => {
401                self.check_use(name);
402            }
403
404            HirExpr::Call { func, args, kwargs } => {
405                // func is a Symbol (String), not an expression
406                let takes_ownership = self.function_takes_ownership(func);
407
408                for arg in args {
409                    if takes_ownership {
410                        self.analyze_expression_for_move(arg, func);
411                    } else {
412                        self.analyze_expression_for_use(arg);
413                    }
414                }
415
416                for (_, value) in kwargs {
417                    if takes_ownership {
418                        self.analyze_expression_for_move(value, func);
419                    } else {
420                        self.analyze_expression_for_use(value);
421                    }
422                }
423            }
424
425            HirExpr::MethodCall {
426                object,
427                method,
428                args,
429                ..
430            } => {
431                let takes_ownership = self.ownership_functions.contains(method);
432                self.analyze_expression_for_use(object);
433
434                for arg in args {
435                    if takes_ownership {
436                        self.analyze_expression_for_move(arg, method);
437                    } else {
438                        self.analyze_expression_for_use(arg);
439                    }
440                }
441            }
442
443            HirExpr::Binary { left, right, .. } => {
444                self.analyze_expression_for_use(left);
445                self.analyze_expression_for_use(right);
446            }
447
448            HirExpr::Unary { operand, .. } => {
449                self.analyze_expression_for_use(operand);
450            }
451
452            HirExpr::Index { base, index } => {
453                self.analyze_expression_for_use(base);
454                self.analyze_expression_for_use(index);
455            }
456
457            HirExpr::Slice {
458                base,
459                start,
460                stop,
461                step,
462            } => {
463                self.analyze_expression_for_use(base);
464                if let Some(s) = start {
465                    self.analyze_expression_for_use(s);
466                }
467                if let Some(e) = stop {
468                    self.analyze_expression_for_use(e);
469                }
470                if let Some(st) = step {
471                    self.analyze_expression_for_use(st);
472                }
473            }
474
475            HirExpr::Attribute { value, .. } => {
476                self.analyze_expression_for_use(value);
477            }
478
479            HirExpr::List(elements) | HirExpr::Tuple(elements) | HirExpr::Set(elements) => {
480                for elem in elements {
481                    self.analyze_expression_for_use(elem);
482                }
483            }
484
485            HirExpr::Dict(pairs) => {
486                for (key, value) in pairs {
487                    self.analyze_expression_for_use(key);
488                    self.analyze_expression_for_use(value);
489                }
490            }
491
492            HirExpr::ListComp {
493                element,
494                generators,
495            }
496            | HirExpr::SetComp {
497                element,
498                generators,
499            } => {
500                for gen in generators {
501                    self.analyze_expression_for_use(&gen.iter);
502                    for cond in &gen.conditions {
503                        self.analyze_expression_for_use(cond);
504                    }
505                }
506                self.analyze_expression_for_use(element);
507            }
508
509            HirExpr::DictComp {
510                key,
511                value,
512                generators,
513            } => {
514                for gen in generators {
515                    self.analyze_expression_for_use(&gen.iter);
516                    for cond in &gen.conditions {
517                        self.analyze_expression_for_use(cond);
518                    }
519                }
520                self.analyze_expression_for_use(key);
521                self.analyze_expression_for_use(value);
522            }
523
524            HirExpr::GeneratorExp {
525                element,
526                generators,
527            } => {
528                for gen in generators {
529                    self.analyze_expression_for_use(&gen.iter);
530                    for cond in &gen.conditions {
531                        self.analyze_expression_for_use(cond);
532                    }
533                }
534                self.analyze_expression_for_use(element);
535            }
536
537            HirExpr::Lambda { body, .. } => {
538                self.analyze_expression_for_use(body);
539            }
540
541            HirExpr::IfExpr { test, body, orelse } => {
542                self.analyze_expression_for_use(test);
543                self.analyze_expression_for_use(body);
544                self.analyze_expression_for_use(orelse);
545            }
546
547            HirExpr::Await { value } => {
548                self.analyze_expression_for_use(value);
549            }
550
551            HirExpr::Yield { value: Some(v), .. } => {
552                self.analyze_expression_for_use(v);
553            }
554
555            HirExpr::Yield { value: None, .. } => {}
556
557            HirExpr::NamedExpr { value, .. } => {
558                self.analyze_expression_for_use(value);
559            }
560
561            HirExpr::FString { parts } => {
562                for part in parts {
563                    if let depyler_hir::hir::FStringPart::Expr(expr) = part {
564                        self.analyze_expression_for_use(expr);
565                    }
566                }
567            }
568
569            // Literals don't use variables
570            HirExpr::Literal(_) => {}
571
572            // Handle remaining cases
573            _ => {}
574        }
575    }
576
577    /// Analyze an expression that will be moved (consumed)
578    fn analyze_expression_for_move(&mut self, expr: &HirExpr, moved_by: &str) {
579        self.analyze_expression_for_use(expr);
580
581        if let HirExpr::Var(name) = expr {
582            self.record_move(name, moved_by);
583        }
584    }
585
586    /// Check if using a variable that was moved
587    fn check_use(&mut self, name: &str) {
588        if let Some(state) = self.move_states.get(name) {
589            match state {
590                MoveState::Moved(move_site) => {
591                    self.errors.push(UseAfterMoveError {
592                        var: name.to_string(),
593                        move_site: move_site.clone(),
594                        use_site: SourceSpan {
595                            stmt_index: self.current_stmt_index,
596                            ..Default::default()
597                        },
598                        moved_by: String::new(),
599                        fix: OwnershipFix::Borrow,
600                    });
601                }
602                MoveState::ConditionallyMoved(move_site) => {
603                    self.errors.push(UseAfterMoveError {
604                        var: name.to_string(),
605                        move_site: move_site.clone(),
606                        use_site: SourceSpan {
607                            stmt_index: self.current_stmt_index,
608                            ..Default::default()
609                        },
610                        moved_by: String::new(),
611                        fix: OwnershipFix::Clone,
612                    });
613                }
614                MoveState::Available => {}
615            }
616        }
617    }
618
619    /// Record that a variable was moved
620    fn record_move(&mut self, name: &str, moved_by: &str) {
621        let move_site = SourceSpan {
622            stmt_index: self.current_stmt_index,
623            ..Default::default()
624        };
625
626        for error in &mut self.errors {
627            if error.var == name && error.moved_by.is_empty() {
628                error.moved_by = moved_by.to_string();
629            }
630        }
631
632        self.move_states
633            .insert(name.to_string(), MoveState::Moved(move_site));
634    }
635
636    /// Record potential alias pattern
637    fn record_potential_alias(&mut self, _target: &str, _source: &str) {
638        // Track aliasing for later analysis
639    }
640
641    /// Determine if a function takes ownership of arguments
642    fn function_takes_ownership(&self, func_name: &str) -> bool {
643        if self.borrowing_functions.contains(func_name) {
644            return false;
645        }
646        // Conservative: unknown functions are assumed to borrow (Python semantics)
647        false
648    }
649
650    /// Merge states after if/else branches
651    fn merge_branch_states(
652        &mut self,
653        before: &HashMap<String, MoveState>,
654        after_then: &HashMap<String, MoveState>,
655        after_else: &HashMap<String, MoveState>,
656    ) {
657        let all_vars: HashSet<_> = before
658            .keys()
659            .chain(after_then.keys())
660            .chain(after_else.keys())
661            .cloned()
662            .collect();
663
664        for var in all_vars {
665            let then_state = after_then.get(&var);
666            let else_state = after_else.get(&var);
667
668            let new_state = match (then_state, else_state) {
669                (Some(MoveState::Moved(s)), _) | (_, Some(MoveState::Moved(s))) => {
670                    MoveState::ConditionallyMoved(s.clone())
671                }
672                (Some(MoveState::ConditionallyMoved(s)), _)
673                | (_, Some(MoveState::ConditionallyMoved(s))) => {
674                    MoveState::ConditionallyMoved(s.clone())
675                }
676                _ => before.get(&var).cloned().unwrap_or(MoveState::Available),
677            };
678
679            self.move_states.insert(var, new_state);
680        }
681    }
682
683    /// Merge state after loop body
684    fn merge_loop_state(&mut self, before: &HashMap<String, MoveState>) {
685        let current = self.move_states.clone();
686
687        for (var, state) in &current {
688            if let MoveState::Moved(span) = state {
689                if before
690                    .get(var)
691                    .map(|s| s == &MoveState::Available)
692                    .unwrap_or(false)
693                {
694                    self.move_states
695                        .insert(var.clone(), MoveState::ConditionallyMoved(span.clone()));
696                }
697            }
698        }
699    }
700
701    /// Analyze captures in nested function
702    fn analyze_nested_captures(
703        &mut self,
704        body: &[HirStmt],
705        outer_vars: &HashSet<String>,
706        params: &HashSet<String>,
707    ) {
708        for stmt in body {
709            self.find_captured_vars_in_stmt(stmt, outer_vars, params);
710        }
711    }
712
713    /// Find captured variables in a statement
714    fn find_captured_vars_in_stmt(
715        &mut self,
716        stmt: &HirStmt,
717        outer_vars: &HashSet<String>,
718        params: &HashSet<String>,
719    ) {
720        match stmt {
721            HirStmt::Assign { value, .. } => {
722                self.find_captured_vars_in_expr(value, outer_vars, params);
723            }
724            HirStmt::Return(Some(expr)) => {
725                self.find_captured_vars_in_expr(expr, outer_vars, params);
726            }
727            HirStmt::Expr(expr) => {
728                self.find_captured_vars_in_expr(expr, outer_vars, params);
729            }
730            _ => {}
731        }
732    }
733
734    /// Find captured variables in an expression
735    fn find_captured_vars_in_expr(
736        &mut self,
737        expr: &HirExpr,
738        outer_vars: &HashSet<String>,
739        params: &HashSet<String>,
740    ) {
741        if let HirExpr::Var(name) = expr {
742            if outer_vars.contains(name) && !params.contains(name) {
743                self.check_use(name);
744            }
745        }
746
747        match expr {
748            HirExpr::Call { args, kwargs, .. } => {
749                // func is a Symbol, not an expression - no need to check it
750                for arg in args {
751                    self.find_captured_vars_in_expr(arg, outer_vars, params);
752                }
753                for (_, v) in kwargs {
754                    self.find_captured_vars_in_expr(v, outer_vars, params);
755                }
756            }
757            HirExpr::Binary { left, right, .. } => {
758                self.find_captured_vars_in_expr(left, outer_vars, params);
759                self.find_captured_vars_in_expr(right, outer_vars, params);
760            }
761            HirExpr::Unary { operand, .. } => {
762                self.find_captured_vars_in_expr(operand, outer_vars, params);
763            }
764            _ => {}
765        }
766    }
767
768    /// Get the detected errors
769    pub fn errors(&self) -> &[UseAfterMoveError] {
770        &self.errors
771    }
772}
773
774/// Strategic Clone Analysis
775#[derive(Debug)]
776pub struct StrategicCloneAnalysis {
777    aliases: Vec<AliasingPattern>,
778    clone_assignments: Vec<String>,
779}
780
781impl Default for StrategicCloneAnalysis {
782    fn default() -> Self {
783        Self::new()
784    }
785}
786
787impl StrategicCloneAnalysis {
788    pub fn new() -> Self {
789        Self {
790            aliases: Vec::new(),
791            clone_assignments: Vec::new(),
792        }
793    }
794
795    pub fn analyze_function(&mut self, func: &HirFunction) -> Vec<AliasingPattern> {
796        self.aliases.clear();
797        self.clone_assignments.clear();
798
799        let mut var_assignments: HashMap<String, Vec<usize>> = HashMap::new();
800        let mut var_uses: HashMap<String, Vec<usize>> = HashMap::new();
801        let mut aliases: Vec<(String, String, usize, Type)> = Vec::new();
802
803        for (stmt_idx, stmt) in func.body.iter().enumerate() {
804            self.collect_var_info(
805                stmt,
806                stmt_idx,
807                &mut var_assignments,
808                &mut var_uses,
809                &mut aliases,
810            );
811        }
812
813        for (target, source, alias_idx, var_type) in aliases {
814            let source_used_after = var_uses
815                .get(&source)
816                .map(|uses| uses.iter().any(|&u| u > alias_idx))
817                .unwrap_or(false);
818
819            let alias_used_after = var_uses
820                .get(&target)
821                .map(|uses| uses.iter().any(|&u| u > alias_idx))
822                .unwrap_or(false);
823
824            if source_used_after && alias_used_after {
825                self.aliases.push(AliasingPattern {
826                    source: source.clone(),
827                    alias: target.clone(),
828                    source_used_after,
829                    alias_used_after,
830                    var_type,
831                });
832                self.clone_assignments.push(target);
833            }
834        }
835
836        self.aliases.clone()
837    }
838
839    fn collect_var_info(
840        &self,
841        stmt: &HirStmt,
842        stmt_idx: usize,
843        assignments: &mut HashMap<String, Vec<usize>>,
844        uses: &mut HashMap<String, Vec<usize>>,
845        aliases: &mut Vec<(String, String, usize, Type)>,
846    ) {
847        match stmt {
848            HirStmt::Assign {
849                target,
850                value,
851                type_annotation,
852            } => {
853                if let AssignTarget::Symbol(target_name) = target {
854                    assignments
855                        .entry(target_name.clone())
856                        .or_default()
857                        .push(stmt_idx);
858
859                    if let HirExpr::Var(source_name) = value {
860                        let ty = type_annotation.clone().unwrap_or(Type::Unknown);
861                        aliases.push((target_name.clone(), source_name.clone(), stmt_idx, ty));
862                    }
863                }
864                self.collect_uses_in_expr(value, stmt_idx, uses);
865            }
866
867            HirStmt::Return(Some(expr)) => {
868                self.collect_uses_in_expr(expr, stmt_idx, uses);
869            }
870
871            HirStmt::Expr(expr) => {
872                self.collect_uses_in_expr(expr, stmt_idx, uses);
873            }
874
875            HirStmt::If {
876                condition,
877                then_body,
878                else_body,
879            } => {
880                self.collect_uses_in_expr(condition, stmt_idx, uses);
881                for s in then_body {
882                    self.collect_var_info(s, stmt_idx, assignments, uses, aliases);
883                }
884                if let Some(else_stmts) = else_body {
885                    for s in else_stmts {
886                        self.collect_var_info(s, stmt_idx, assignments, uses, aliases);
887                    }
888                }
889            }
890
891            HirStmt::While { condition, body } => {
892                self.collect_uses_in_expr(condition, stmt_idx, uses);
893                for s in body {
894                    self.collect_var_info(s, stmt_idx, assignments, uses, aliases);
895                }
896            }
897
898            HirStmt::For { iter, body, .. } => {
899                self.collect_uses_in_expr(iter, stmt_idx, uses);
900                for s in body {
901                    self.collect_var_info(s, stmt_idx, assignments, uses, aliases);
902                }
903            }
904
905            HirStmt::Block(stmts) => {
906                for s in stmts {
907                    self.collect_var_info(s, stmt_idx, assignments, uses, aliases);
908                }
909            }
910
911            _ => {}
912        }
913    }
914
915    fn collect_uses_in_expr(
916        &self,
917        expr: &HirExpr,
918        stmt_idx: usize,
919        uses: &mut HashMap<String, Vec<usize>>,
920    ) {
921        match expr {
922            HirExpr::Var(name) => {
923                uses.entry(name.clone()).or_default().push(stmt_idx);
924            }
925            HirExpr::Call { args, kwargs, .. } => {
926                // func is a Symbol, not an expression - no need to check it
927                for arg in args {
928                    self.collect_uses_in_expr(arg, stmt_idx, uses);
929                }
930                for (_, v) in kwargs {
931                    self.collect_uses_in_expr(v, stmt_idx, uses);
932                }
933            }
934            HirExpr::MethodCall { object, args, .. } => {
935                self.collect_uses_in_expr(object, stmt_idx, uses);
936                for arg in args {
937                    self.collect_uses_in_expr(arg, stmt_idx, uses);
938                }
939            }
940            HirExpr::Binary { left, right, .. } => {
941                self.collect_uses_in_expr(left, stmt_idx, uses);
942                self.collect_uses_in_expr(right, stmt_idx, uses);
943            }
944            HirExpr::Unary { operand, .. } => {
945                self.collect_uses_in_expr(operand, stmt_idx, uses);
946            }
947            HirExpr::List(elements) | HirExpr::Tuple(elements) | HirExpr::Set(elements) => {
948                for e in elements {
949                    self.collect_uses_in_expr(e, stmt_idx, uses);
950                }
951            }
952            HirExpr::Dict(pairs) => {
953                for (k, v) in pairs {
954                    self.collect_uses_in_expr(k, stmt_idx, uses);
955                    self.collect_uses_in_expr(v, stmt_idx, uses);
956                }
957            }
958            HirExpr::Index { base, index } => {
959                self.collect_uses_in_expr(base, stmt_idx, uses);
960                self.collect_uses_in_expr(index, stmt_idx, uses);
961            }
962            HirExpr::Attribute { value, .. } => {
963                self.collect_uses_in_expr(value, stmt_idx, uses);
964            }
965            HirExpr::IfExpr { test, body, orelse } => {
966                self.collect_uses_in_expr(test, stmt_idx, uses);
967                self.collect_uses_in_expr(body, stmt_idx, uses);
968                self.collect_uses_in_expr(orelse, stmt_idx, uses);
969            }
970            _ => {}
971        }
972    }
973
974    pub fn needs_clone(&self) -> &[String] {
975        &self.clone_assignments
976    }
977}
978
979/// Combined ownership analysis result
980#[derive(Debug, Clone)]
981pub struct OwnershipAnalysisResult {
982    pub use_after_move_errors: Vec<UseAfterMoveError>,
983    pub aliasing_patterns: Vec<AliasingPattern>,
984    pub borrow_sites: HashMap<String, Vec<usize>>,
985    pub clone_sites: HashMap<String, Vec<usize>>,
986    pub mut_borrow_sites: HashMap<String, Vec<usize>>,
987}
988
989/// Run complete ownership analysis on a function
990pub fn analyze_ownership(func: &HirFunction) -> OwnershipAnalysisResult {
991    let mut uam_analysis = UseAfterMoveAnalysis::new();
992    let use_after_move_errors = uam_analysis.analyze_function(func);
993
994    let mut clone_analysis = StrategicCloneAnalysis::new();
995    let aliasing_patterns = clone_analysis.analyze_function(func);
996
997    let mut borrow_sites: HashMap<String, Vec<usize>> = HashMap::new();
998    let mut clone_sites: HashMap<String, Vec<usize>> = HashMap::new();
999    let mut mut_borrow_sites: HashMap<String, Vec<usize>> = HashMap::new();
1000
1001    for error in &use_after_move_errors {
1002        match &error.fix {
1003            OwnershipFix::Borrow => {
1004                borrow_sites
1005                    .entry(error.var.clone())
1006                    .or_default()
1007                    .push(error.move_site.stmt_index);
1008            }
1009            OwnershipFix::MutableBorrow => {
1010                mut_borrow_sites
1011                    .entry(error.var.clone())
1012                    .or_default()
1013                    .push(error.move_site.stmt_index);
1014            }
1015            OwnershipFix::Clone | OwnershipFix::CloneAtAssignment { .. } => {
1016                clone_sites
1017                    .entry(error.var.clone())
1018                    .or_default()
1019                    .push(error.move_site.stmt_index);
1020            }
1021            OwnershipFix::Reject { .. } => {}
1022        }
1023    }
1024
1025    OwnershipAnalysisResult {
1026        use_after_move_errors,
1027        aliasing_patterns,
1028        borrow_sites,
1029        clone_sites,
1030        mut_borrow_sites,
1031    }
1032}
1033
1034#[cfg(test)]
1035mod tests {
1036    use super::*;
1037    use depyler_hir::hir::{BinOp, Literal};
1038    use smallvec::smallvec;
1039
1040    fn make_var(name: &str) -> HirExpr {
1041        HirExpr::Var(name.to_string())
1042    }
1043
1044    fn make_literal_int(n: i64) -> HirExpr {
1045        HirExpr::Literal(Literal::Int(n))
1046    }
1047
1048    fn make_function(name: &str, body: Vec<HirStmt>) -> HirFunction {
1049        HirFunction {
1050            name: name.to_string(),
1051            params: smallvec![],
1052            ret_type: Type::Unknown,
1053            body,
1054            properties: Default::default(),
1055            annotations: Default::default(),
1056            docstring: None,
1057        }
1058    }
1059
1060    #[test]
1061    fn test_source_span_default() {
1062        let span = SourceSpan::default();
1063        assert_eq!(span.line, 1);
1064        assert_eq!(span.column, 1);
1065        assert_eq!(span.stmt_index, 0);
1066    }
1067
1068    #[test]
1069    fn test_use_after_move_analysis_new() {
1070        let analysis = UseAfterMoveAnalysis::new();
1071        assert!(analysis.errors.is_empty());
1072        assert!(analysis.move_states.is_empty());
1073    }
1074
1075    #[test]
1076    fn test_no_use_after_move_simple() {
1077        let func = HirFunction {
1078            name: "foo".to_string(),
1079            params: smallvec![depyler_hir::hir::HirParam {
1080                name: "x".to_string(),
1081                ty: Type::Int,
1082                default: None,
1083                is_vararg: false,
1084            }],
1085            ret_type: Type::Int,
1086            body: vec![HirStmt::Return(Some(make_var("x")))],
1087            properties: Default::default(),
1088            annotations: Default::default(),
1089            docstring: None,
1090        };
1091
1092        let mut analysis = UseAfterMoveAnalysis::new();
1093        let errors = analysis.analyze_function(&func);
1094        assert!(errors.is_empty(), "No use-after-move expected");
1095    }
1096
1097    #[test]
1098    fn test_strategic_clone_aliasing_pattern() {
1099        let func = make_function(
1100            "foo",
1101            vec![
1102                HirStmt::Assign {
1103                    target: AssignTarget::Symbol("a".to_string()),
1104                    value: make_literal_int(1),
1105                    type_annotation: Some(Type::Int),
1106                },
1107                HirStmt::Assign {
1108                    target: AssignTarget::Symbol("b".to_string()),
1109                    value: make_var("a"),
1110                    type_annotation: Some(Type::Int),
1111                },
1112                HirStmt::Expr(HirExpr::Call {
1113                    func: "use".to_string(),
1114                    args: vec![make_var("a")],
1115                    kwargs: vec![],
1116                }),
1117                HirStmt::Expr(HirExpr::Call {
1118                    func: "use".to_string(),
1119                    args: vec![make_var("b")],
1120                    kwargs: vec![],
1121                }),
1122            ],
1123        );
1124
1125        let mut analysis = StrategicCloneAnalysis::new();
1126        let patterns = analysis.analyze_function(&func);
1127
1128        assert!(!patterns.is_empty(), "Should detect aliasing pattern");
1129        assert_eq!(patterns[0].source, "a");
1130        assert_eq!(patterns[0].alias, "b");
1131    }
1132
1133    #[test]
1134    fn test_ownership_fix_variants() {
1135        let borrow = OwnershipFix::Borrow;
1136        let mut_borrow = OwnershipFix::MutableBorrow;
1137        let clone = OwnershipFix::Clone;
1138        let clone_assign = OwnershipFix::CloneAtAssignment {
1139            var: "x".to_string(),
1140        };
1141        let reject = OwnershipFix::Reject {
1142            reason: "test".to_string(),
1143        };
1144
1145        assert_eq!(borrow, OwnershipFix::Borrow);
1146        assert_eq!(mut_borrow, OwnershipFix::MutableBorrow);
1147        assert_ne!(clone, clone_assign);
1148        assert_ne!(borrow, reject);
1149    }
1150
1151    #[test]
1152    fn test_analyze_ownership_comprehensive() {
1153        let func = make_function(
1154            "test",
1155            vec![
1156                HirStmt::Assign {
1157                    target: AssignTarget::Symbol("x".to_string()),
1158                    value: make_literal_int(42),
1159                    type_annotation: Some(Type::Int),
1160                },
1161                HirStmt::Return(Some(make_var("x"))),
1162            ],
1163        );
1164
1165        let result = analyze_ownership(&func);
1166        assert!(result.use_after_move_errors.is_empty());
1167    }
1168
1169    #[test]
1170    fn test_default_impls() {
1171        let uam = UseAfterMoveAnalysis::default();
1172        assert!(uam.errors.is_empty());
1173
1174        let clone = StrategicCloneAnalysis::default();
1175        assert!(clone.aliases.is_empty());
1176    }
1177
1178    #[test]
1179    fn test_ownership_analysis_with_if() {
1180        let func = HirFunction {
1181            name: "foo".to_string(),
1182            params: smallvec![depyler_hir::hir::HirParam {
1183                name: "x".to_string(),
1184                ty: Type::Int,
1185                default: None,
1186                is_vararg: false,
1187            }],
1188            ret_type: Type::Int,
1189            body: vec![
1190                HirStmt::If {
1191                    condition: HirExpr::Binary {
1192                        op: BinOp::Gt,
1193                        left: Box::new(make_var("x")),
1194                        right: Box::new(make_literal_int(0)),
1195                    },
1196                    then_body: vec![HirStmt::Assign {
1197                        target: AssignTarget::Symbol("y".to_string()),
1198                        value: make_var("x"),
1199                        type_annotation: Some(Type::Int),
1200                    }],
1201                    else_body: Some(vec![HirStmt::Assign {
1202                        target: AssignTarget::Symbol("y".to_string()),
1203                        value: HirExpr::Unary {
1204                            op: depyler_hir::hir::UnaryOp::Neg,
1205                            operand: Box::new(make_var("x")),
1206                        },
1207                        type_annotation: Some(Type::Int),
1208                    }]),
1209                },
1210                HirStmt::Return(Some(make_var("y"))),
1211            ],
1212            properties: Default::default(),
1213            annotations: Default::default(),
1214            docstring: None,
1215        };
1216
1217        let mut analysis = UseAfterMoveAnalysis::new();
1218        let errors = analysis.analyze_function(&func);
1219        assert!(errors.is_empty());
1220    }
1221
1222    #[test]
1223    fn test_ownership_analysis_with_loop() {
1224        let func = HirFunction {
1225            name: "foo".to_string(),
1226            params: smallvec![depyler_hir::hir::HirParam {
1227                name: "items".to_string(),
1228                ty: Type::List(Box::new(Type::Int)),
1229                default: None,
1230                is_vararg: false,
1231            }],
1232            ret_type: Type::None,
1233            body: vec![HirStmt::For {
1234                target: AssignTarget::Symbol("item".to_string()),
1235                iter: make_var("items"),
1236                body: vec![HirStmt::Expr(HirExpr::Call {
1237                    func: "print".to_string(),
1238                    args: vec![make_var("item")],
1239                    kwargs: vec![],
1240                })],
1241            }],
1242            properties: Default::default(),
1243            annotations: Default::default(),
1244            docstring: None,
1245        };
1246
1247        let result = analyze_ownership(&func);
1248        assert!(
1249            result.use_after_move_errors.is_empty(),
1250            "print should borrow"
1251        );
1252    }
1253
1254    #[test]
1255    fn test_move_state_variants() {
1256        let available = MoveState::Available;
1257        let moved = MoveState::Moved(SourceSpan::default());
1258        let conditional = MoveState::ConditionallyMoved(SourceSpan::default());
1259
1260        assert_eq!(available, MoveState::Available);
1261        assert_ne!(available, moved);
1262        assert_ne!(moved, conditional);
1263    }
1264
1265    #[test]
1266    fn test_aliasing_pattern_struct() {
1267        let pattern = AliasingPattern {
1268            source: "a".to_string(),
1269            alias: "b".to_string(),
1270            source_used_after: true,
1271            alias_used_after: true,
1272            var_type: Type::String,
1273        };
1274
1275        assert_eq!(pattern.source, "a");
1276        assert_eq!(pattern.alias, "b");
1277        assert!(pattern.source_used_after);
1278        assert!(pattern.alias_used_after);
1279    }
1280
1281    // --- DEPYLER-99MODE: Additional escape analysis coverage tests ---
1282
1283    #[test]
1284    fn test_analyze_while_loop() {
1285        let func = make_function(
1286            "test",
1287            vec![
1288                HirStmt::Assign {
1289                    target: AssignTarget::Symbol("x".to_string()),
1290                    value: make_literal_int(0),
1291                    type_annotation: Some(Type::Int),
1292                },
1293                HirStmt::While {
1294                    condition: HirExpr::Binary {
1295                        op: BinOp::Lt,
1296                        left: Box::new(make_var("x")),
1297                        right: Box::new(make_literal_int(10)),
1298                    },
1299                    body: vec![HirStmt::Assign {
1300                        target: AssignTarget::Symbol("x".to_string()),
1301                        value: HirExpr::Binary {
1302                            op: BinOp::Add,
1303                            left: Box::new(make_var("x")),
1304                            right: Box::new(make_literal_int(1)),
1305                        },
1306                        type_annotation: Some(Type::Int),
1307                    }],
1308                },
1309                HirStmt::Return(Some(make_var("x"))),
1310            ],
1311        );
1312        let mut analysis = UseAfterMoveAnalysis::new();
1313        let errors = analysis.analyze_function(&func);
1314        assert!(errors.is_empty());
1315    }
1316
1317    #[test]
1318    fn test_analyze_for_loop() {
1319        let func = make_function(
1320            "test",
1321            vec![HirStmt::For {
1322                target: AssignTarget::Symbol("item".to_string()),
1323                iter: HirExpr::List(vec![make_literal_int(1), make_literal_int(2)]),
1324                body: vec![HirStmt::Expr(HirExpr::Call {
1325                    func: "print".to_string(),
1326                    args: vec![make_var("item")],
1327                    kwargs: vec![],
1328                })],
1329            }],
1330        );
1331        let mut analysis = UseAfterMoveAnalysis::new();
1332        let errors = analysis.analyze_function(&func);
1333        assert!(errors.is_empty());
1334    }
1335
1336    #[test]
1337    fn test_analyze_block_stmt() {
1338        let func = make_function(
1339            "test",
1340            vec![HirStmt::Block(vec![
1341                HirStmt::Assign {
1342                    target: AssignTarget::Symbol("x".to_string()),
1343                    value: make_literal_int(1),
1344                    type_annotation: Some(Type::Int),
1345                },
1346                HirStmt::Return(Some(make_var("x"))),
1347            ])],
1348        );
1349        let mut analysis = UseAfterMoveAnalysis::new();
1350        let errors = analysis.analyze_function(&func);
1351        assert!(errors.is_empty());
1352    }
1353
1354    #[test]
1355    fn test_analyze_try_stmt() {
1356        let func = make_function(
1357            "test",
1358            vec![HirStmt::Try {
1359                body: vec![HirStmt::Assign {
1360                    target: AssignTarget::Symbol("x".to_string()),
1361                    value: make_literal_int(42),
1362                    type_annotation: Some(Type::Int),
1363                }],
1364                handlers: vec![],
1365                orelse: Some(vec![HirStmt::Expr(make_var("x"))]),
1366                finalbody: Some(vec![HirStmt::Pass]),
1367            }],
1368        );
1369        let mut analysis = UseAfterMoveAnalysis::new();
1370        let errors = analysis.analyze_function(&func);
1371        assert!(errors.is_empty());
1372    }
1373
1374    #[test]
1375    fn test_analyze_with_stmt() {
1376        let func = make_function(
1377            "test",
1378            vec![HirStmt::With {
1379                context: HirExpr::Call {
1380                    func: "open".to_string(),
1381                    args: vec![HirExpr::Literal(depyler_hir::hir::Literal::String(
1382                        "file.txt".to_string(),
1383                    ))],
1384                    kwargs: vec![],
1385                },
1386                target: Some("f".to_string()),
1387                body: vec![HirStmt::Expr(make_var("f"))],
1388                is_async: false,
1389            }],
1390        );
1391        let mut analysis = UseAfterMoveAnalysis::new();
1392        let errors = analysis.analyze_function(&func);
1393        assert!(errors.is_empty());
1394    }
1395
1396    #[test]
1397    fn test_analyze_assert_stmt() {
1398        let func = make_function(
1399            "test",
1400            vec![HirStmt::Assert {
1401                test: HirExpr::Binary {
1402                    op: BinOp::Gt,
1403                    left: Box::new(make_literal_int(5)),
1404                    right: Box::new(make_literal_int(0)),
1405                },
1406                msg: Some(HirExpr::Literal(depyler_hir::hir::Literal::String(
1407                    "must be positive".to_string(),
1408                ))),
1409            }],
1410        );
1411        let mut analysis = UseAfterMoveAnalysis::new();
1412        let errors = analysis.analyze_function(&func);
1413        assert!(errors.is_empty());
1414    }
1415
1416    #[test]
1417    fn test_analyze_raise_stmt() {
1418        let func = make_function(
1419            "test",
1420            vec![HirStmt::Raise {
1421                exception: Some(HirExpr::Call {
1422                    func: "ValueError".to_string(),
1423                    args: vec![HirExpr::Literal(depyler_hir::hir::Literal::String(
1424                        "bad value".to_string(),
1425                    ))],
1426                    kwargs: vec![],
1427                }),
1428                cause: None,
1429            }],
1430        );
1431        let mut analysis = UseAfterMoveAnalysis::new();
1432        let errors = analysis.analyze_function(&func);
1433        assert!(errors.is_empty());
1434    }
1435
1436    #[test]
1437    fn test_analyze_raise_with_cause() {
1438        let func = make_function(
1439            "test",
1440            vec![HirStmt::Raise {
1441                exception: Some(HirExpr::Call {
1442                    func: "RuntimeError".to_string(),
1443                    args: vec![],
1444                    kwargs: vec![],
1445                }),
1446                cause: Some(make_var("original_error")),
1447            }],
1448        );
1449        let mut analysis = UseAfterMoveAnalysis::new();
1450        let _errors = analysis.analyze_function(&func);
1451        // Just verifies it doesn't panic
1452    }
1453
1454    #[test]
1455    fn test_analyze_pass_break_continue() {
1456        let func = make_function(
1457            "test",
1458            vec![
1459                HirStmt::Pass,
1460                HirStmt::Break { label: None },
1461                HirStmt::Continue { label: None },
1462            ],
1463        );
1464        let mut analysis = UseAfterMoveAnalysis::new();
1465        let errors = analysis.analyze_function(&func);
1466        assert!(errors.is_empty());
1467    }
1468
1469    #[test]
1470    fn test_analyze_return_none() {
1471        let func = make_function("test", vec![HirStmt::Return(None)]);
1472        let mut analysis = UseAfterMoveAnalysis::new();
1473        let errors = analysis.analyze_function(&func);
1474        assert!(errors.is_empty());
1475    }
1476
1477    #[test]
1478    fn test_analyze_nested_function_captures() {
1479        let func = HirFunction {
1480            name: "outer".to_string(),
1481            params: smallvec![depyler_hir::hir::HirParam {
1482                name: "x".to_string(),
1483                ty: Type::Int,
1484                default: None,
1485                is_vararg: false,
1486            }],
1487            ret_type: Type::Int,
1488            body: vec![HirStmt::FunctionDef {
1489                name: "inner".to_string(),
1490                params: Box::new(smallvec![depyler_hir::hir::HirParam {
1491                    name: "y".to_string(),
1492                    ty: Type::Int,
1493                    default: None,
1494                    is_vararg: false,
1495                }]),
1496                ret_type: Type::Int,
1497                body: vec![HirStmt::Return(Some(HirExpr::Binary {
1498                    op: BinOp::Add,
1499                    left: Box::new(make_var("x")),
1500                    right: Box::new(make_var("y")),
1501                }))],
1502                docstring: None,
1503            }],
1504            properties: Default::default(),
1505            annotations: Default::default(),
1506            docstring: None,
1507        };
1508        let mut analysis = UseAfterMoveAnalysis::new();
1509        let _errors = analysis.analyze_function(&func);
1510    }
1511
1512    #[test]
1513    fn test_expr_method_call() {
1514        let func = make_function(
1515            "test",
1516            vec![HirStmt::Expr(HirExpr::MethodCall {
1517                object: Box::new(make_var("items")),
1518                method: "append".to_string(),
1519                args: vec![make_literal_int(42)],
1520                kwargs: vec![],
1521            })],
1522        );
1523        let mut analysis = UseAfterMoveAnalysis::new();
1524        let _errors = analysis.analyze_function(&func);
1525    }
1526
1527    #[test]
1528    fn test_expr_index_access() {
1529        let func = make_function(
1530            "test",
1531            vec![HirStmt::Expr(HirExpr::Index {
1532                base: Box::new(make_var("items")),
1533                index: Box::new(make_literal_int(0)),
1534            })],
1535        );
1536        let mut analysis = UseAfterMoveAnalysis::new();
1537        let errors = analysis.analyze_function(&func);
1538        assert!(errors.is_empty());
1539    }
1540
1541    #[test]
1542    fn test_expr_slice() {
1543        let func = make_function(
1544            "test",
1545            vec![HirStmt::Expr(HirExpr::Slice {
1546                base: Box::new(make_var("items")),
1547                start: Some(Box::new(make_literal_int(1))),
1548                stop: Some(Box::new(make_literal_int(3))),
1549                step: Some(Box::new(make_literal_int(1))),
1550            })],
1551        );
1552        let mut analysis = UseAfterMoveAnalysis::new();
1553        let errors = analysis.analyze_function(&func);
1554        assert!(errors.is_empty());
1555    }
1556
1557    #[test]
1558    fn test_expr_attribute() {
1559        let func = make_function(
1560            "test",
1561            vec![HirStmt::Expr(HirExpr::Attribute {
1562                value: Box::new(make_var("obj")),
1563                attr: "name".to_string(),
1564            })],
1565        );
1566        let mut analysis = UseAfterMoveAnalysis::new();
1567        let errors = analysis.analyze_function(&func);
1568        assert!(errors.is_empty());
1569    }
1570
1571    #[test]
1572    fn test_expr_collections() {
1573        let func = make_function(
1574            "test",
1575            vec![
1576                HirStmt::Expr(HirExpr::List(vec![
1577                    make_literal_int(1),
1578                    make_literal_int(2),
1579                ])),
1580                HirStmt::Expr(HirExpr::Tuple(vec![
1581                    make_literal_int(3),
1582                    make_literal_int(4),
1583                ])),
1584                HirStmt::Expr(HirExpr::Set(vec![make_literal_int(5)])),
1585                HirStmt::Expr(HirExpr::Dict(vec![(
1586                    HirExpr::Literal(depyler_hir::hir::Literal::String("k".to_string())),
1587                    make_literal_int(1),
1588                )])),
1589            ],
1590        );
1591        let mut analysis = UseAfterMoveAnalysis::new();
1592        let errors = analysis.analyze_function(&func);
1593        assert!(errors.is_empty());
1594    }
1595
1596    #[test]
1597    fn test_expr_list_comprehension() {
1598        let func = make_function(
1599            "test",
1600            vec![HirStmt::Expr(HirExpr::ListComp {
1601                element: Box::new(HirExpr::Binary {
1602                    op: BinOp::Mul,
1603                    left: Box::new(make_var("x")),
1604                    right: Box::new(make_literal_int(2)),
1605                }),
1606                generators: vec![depyler_hir::hir::HirComprehension {
1607                    target: "x".to_string(),
1608                    iter: Box::new(HirExpr::Call {
1609                        func: "range".to_string(),
1610                        args: vec![make_literal_int(10)],
1611                        kwargs: vec![],
1612                    }),
1613                    conditions: vec![HirExpr::Binary {
1614                        op: BinOp::Gt,
1615                        left: Box::new(make_var("x")),
1616                        right: Box::new(make_literal_int(5)),
1617                    }],
1618                }],
1619            })],
1620        );
1621        let mut analysis = UseAfterMoveAnalysis::new();
1622        let errors = analysis.analyze_function(&func);
1623        assert!(errors.is_empty());
1624    }
1625
1626    #[test]
1627    fn test_expr_dict_comprehension() {
1628        let func = make_function(
1629            "test",
1630            vec![HirStmt::Expr(HirExpr::DictComp {
1631                key: Box::new(make_var("k")),
1632                value: Box::new(make_var("v")),
1633                generators: vec![depyler_hir::hir::HirComprehension {
1634                    target: "k".to_string(),
1635                    iter: Box::new(make_var("data")),
1636                    conditions: vec![],
1637                }],
1638            })],
1639        );
1640        let mut analysis = UseAfterMoveAnalysis::new();
1641        let _errors = analysis.analyze_function(&func);
1642    }
1643
1644    #[test]
1645    fn test_expr_generator() {
1646        let func = make_function(
1647            "test",
1648            vec![HirStmt::Expr(HirExpr::GeneratorExp {
1649                element: Box::new(make_var("x")),
1650                generators: vec![depyler_hir::hir::HirComprehension {
1651                    target: "x".to_string(),
1652                    iter: Box::new(make_var("items")),
1653                    conditions: vec![],
1654                }],
1655            })],
1656        );
1657        let mut analysis = UseAfterMoveAnalysis::new();
1658        let _errors = analysis.analyze_function(&func);
1659    }
1660
1661    #[test]
1662    fn test_expr_lambda() {
1663        let func = make_function(
1664            "test",
1665            vec![HirStmt::Expr(HirExpr::Lambda {
1666                params: vec!["x".to_string()],
1667                body: Box::new(HirExpr::Binary {
1668                    op: BinOp::Mul,
1669                    left: Box::new(make_var("x")),
1670                    right: Box::new(make_literal_int(2)),
1671                }),
1672            })],
1673        );
1674        let mut analysis = UseAfterMoveAnalysis::new();
1675        let errors = analysis.analyze_function(&func);
1676        assert!(errors.is_empty());
1677    }
1678
1679    #[test]
1680    fn test_expr_if_expression() {
1681        let func = make_function(
1682            "test",
1683            vec![HirStmt::Expr(HirExpr::IfExpr {
1684                test: Box::new(HirExpr::Binary {
1685                    op: BinOp::Gt,
1686                    left: Box::new(make_var("x")),
1687                    right: Box::new(make_literal_int(0)),
1688                }),
1689                body: Box::new(make_var("x")),
1690                orelse: Box::new(make_literal_int(0)),
1691            })],
1692        );
1693        let mut analysis = UseAfterMoveAnalysis::new();
1694        let _errors = analysis.analyze_function(&func);
1695    }
1696
1697    #[test]
1698    fn test_expr_fstring() {
1699        let func = make_function(
1700            "test",
1701            vec![HirStmt::Expr(HirExpr::FString {
1702                parts: vec![
1703                    depyler_hir::hir::FStringPart::Literal("hello ".to_string()),
1704                    depyler_hir::hir::FStringPart::Expr(Box::new(make_var("name"))),
1705                ],
1706            })],
1707        );
1708        let mut analysis = UseAfterMoveAnalysis::new();
1709        let _errors = analysis.analyze_function(&func);
1710    }
1711
1712    #[test]
1713    fn test_expr_await() {
1714        let func = make_function(
1715            "test",
1716            vec![HirStmt::Expr(HirExpr::Await {
1717                value: Box::new(HirExpr::Call {
1718                    func: "fetch".to_string(),
1719                    args: vec![],
1720                    kwargs: vec![],
1721                }),
1722            })],
1723        );
1724        let mut analysis = UseAfterMoveAnalysis::new();
1725        let errors = analysis.analyze_function(&func);
1726        assert!(errors.is_empty());
1727    }
1728
1729    #[test]
1730    fn test_expr_yield() {
1731        let func = make_function(
1732            "test",
1733            vec![
1734                HirStmt::Expr(HirExpr::Yield {
1735                    value: Some(Box::new(make_literal_int(42))),
1736                }),
1737                HirStmt::Expr(HirExpr::Yield { value: None }),
1738            ],
1739        );
1740        let mut analysis = UseAfterMoveAnalysis::new();
1741        let errors = analysis.analyze_function(&func);
1742        assert!(errors.is_empty());
1743    }
1744
1745    #[test]
1746    fn test_expr_named_expr() {
1747        let func = make_function(
1748            "test",
1749            vec![HirStmt::Expr(HirExpr::NamedExpr {
1750                target: "n".to_string(),
1751                value: Box::new(make_literal_int(10)),
1752            })],
1753        );
1754        let mut analysis = UseAfterMoveAnalysis::new();
1755        let errors = analysis.analyze_function(&func);
1756        assert!(errors.is_empty());
1757    }
1758
1759    #[test]
1760    fn test_merge_branch_states_moved_in_then() {
1761        let mut analysis = UseAfterMoveAnalysis::new();
1762        analysis
1763            .move_states
1764            .insert("x".to_string(), MoveState::Available);
1765
1766        let before = analysis.move_states.clone();
1767        let mut after_then = before.clone();
1768        after_then.insert("x".to_string(), MoveState::Moved(SourceSpan::default()));
1769        let after_else = before.clone();
1770
1771        analysis.merge_branch_states(&before, &after_then, &after_else);
1772
1773        assert!(matches!(
1774            analysis.move_states.get("x"),
1775            Some(MoveState::ConditionallyMoved(_))
1776        ));
1777    }
1778
1779    #[test]
1780    fn test_merge_loop_state() {
1781        let mut analysis = UseAfterMoveAnalysis::new();
1782        let mut before = HashMap::new();
1783        before.insert("x".to_string(), MoveState::Available);
1784
1785        analysis.move_states = before.clone();
1786        analysis
1787            .move_states
1788            .insert("x".to_string(), MoveState::Moved(SourceSpan::default()));
1789
1790        analysis.merge_loop_state(&before);
1791
1792        assert!(matches!(
1793            analysis.move_states.get("x"),
1794            Some(MoveState::ConditionallyMoved(_))
1795        ));
1796    }
1797
1798    #[test]
1799    fn test_function_takes_ownership() {
1800        let analysis = UseAfterMoveAnalysis::new();
1801        // Borrowing functions return false
1802        assert!(!analysis.function_takes_ownership("print"));
1803        assert!(!analysis.function_takes_ownership("len"));
1804        // Unknown functions also false (conservative)
1805        assert!(!analysis.function_takes_ownership("unknown_func"));
1806    }
1807
1808    #[test]
1809    fn test_errors_accessor() {
1810        let analysis = UseAfterMoveAnalysis::new();
1811        assert!(analysis.errors().is_empty());
1812    }
1813
1814    #[test]
1815    fn test_needs_clone_accessor() {
1816        let analysis = StrategicCloneAnalysis::new();
1817        assert!(analysis.needs_clone().is_empty());
1818    }
1819
1820    #[test]
1821    fn test_analyze_ownership_comprehensive_with_aliasing() {
1822        let func = make_function(
1823            "test",
1824            vec![
1825                HirStmt::Assign {
1826                    target: AssignTarget::Symbol("a".to_string()),
1827                    value: HirExpr::Literal(depyler_hir::hir::Literal::String("hello".to_string())),
1828                    type_annotation: Some(Type::String),
1829                },
1830                HirStmt::Assign {
1831                    target: AssignTarget::Symbol("b".to_string()),
1832                    value: make_var("a"),
1833                    type_annotation: Some(Type::String),
1834                },
1835                HirStmt::Expr(HirExpr::Call {
1836                    func: "print".to_string(),
1837                    args: vec![make_var("a")],
1838                    kwargs: vec![],
1839                }),
1840                HirStmt::Expr(HirExpr::Call {
1841                    func: "print".to_string(),
1842                    args: vec![make_var("b")],
1843                    kwargs: vec![],
1844                }),
1845            ],
1846        );
1847        let result = analyze_ownership(&func);
1848        assert!(!result.aliasing_patterns.is_empty());
1849    }
1850
1851    #[test]
1852    fn test_strategic_clone_with_while() {
1853        let func = make_function(
1854            "test",
1855            vec![
1856                HirStmt::Assign {
1857                    target: AssignTarget::Symbol("x".to_string()),
1858                    value: make_literal_int(0),
1859                    type_annotation: Some(Type::Int),
1860                },
1861                HirStmt::While {
1862                    condition: HirExpr::Binary {
1863                        op: BinOp::Lt,
1864                        left: Box::new(make_var("x")),
1865                        right: Box::new(make_literal_int(10)),
1866                    },
1867                    body: vec![HirStmt::Assign {
1868                        target: AssignTarget::Symbol("x".to_string()),
1869                        value: HirExpr::Binary {
1870                            op: BinOp::Add,
1871                            left: Box::new(make_var("x")),
1872                            right: Box::new(make_literal_int(1)),
1873                        },
1874                        type_annotation: Some(Type::Int),
1875                    }],
1876                },
1877            ],
1878        );
1879        let mut analysis = StrategicCloneAnalysis::new();
1880        let _patterns = analysis.analyze_function(&func);
1881    }
1882
1883    #[test]
1884    fn test_strategic_clone_with_for() {
1885        let func = make_function(
1886            "test",
1887            vec![HirStmt::For {
1888                target: AssignTarget::Symbol("item".to_string()),
1889                iter: make_var("items"),
1890                body: vec![HirStmt::Expr(make_var("item"))],
1891            }],
1892        );
1893        let mut analysis = StrategicCloneAnalysis::new();
1894        let _patterns = analysis.analyze_function(&func);
1895    }
1896
1897    #[test]
1898    fn test_strategic_clone_collect_uses_method_call() {
1899        let func = make_function(
1900            "test",
1901            vec![HirStmt::Expr(HirExpr::MethodCall {
1902                object: Box::new(make_var("obj")),
1903                method: "process".to_string(),
1904                args: vec![make_var("data")],
1905                kwargs: vec![],
1906            })],
1907        );
1908        let mut analysis = StrategicCloneAnalysis::new();
1909        let _patterns = analysis.analyze_function(&func);
1910    }
1911
1912    #[test]
1913    fn test_strategic_clone_collect_uses_expr_types() {
1914        let func = make_function(
1915            "test",
1916            vec![
1917                HirStmt::Expr(HirExpr::Unary {
1918                    op: depyler_hir::hir::UnaryOp::Neg,
1919                    operand: Box::new(make_var("x")),
1920                }),
1921                HirStmt::Expr(HirExpr::Index {
1922                    base: Box::new(make_var("arr")),
1923                    index: Box::new(make_literal_int(0)),
1924                }),
1925                HirStmt::Expr(HirExpr::Attribute {
1926                    value: Box::new(make_var("obj")),
1927                    attr: "field".to_string(),
1928                }),
1929                HirStmt::Expr(HirExpr::IfExpr {
1930                    test: Box::new(make_var("cond")),
1931                    body: Box::new(make_var("a")),
1932                    orelse: Box::new(make_var("b")),
1933                }),
1934            ],
1935        );
1936        let mut analysis = StrategicCloneAnalysis::new();
1937        let _patterns = analysis.analyze_function(&func);
1938    }
1939
1940    #[test]
1941    fn test_set_comp() {
1942        let func = make_function(
1943            "test",
1944            vec![HirStmt::Expr(HirExpr::SetComp {
1945                element: Box::new(make_var("x")),
1946                generators: vec![depyler_hir::hir::HirComprehension {
1947                    target: "x".to_string(),
1948                    iter: Box::new(make_var("items")),
1949                    conditions: vec![],
1950                }],
1951            })],
1952        );
1953        let mut analysis = UseAfterMoveAnalysis::new();
1954        let errors = analysis.analyze_function(&func);
1955        assert!(errors.is_empty());
1956    }
1957
1958    #[test]
1959    fn test_expr_call_with_kwargs() {
1960        let func = make_function(
1961            "test",
1962            vec![HirStmt::Expr(HirExpr::Call {
1963                func: "create".to_string(),
1964                args: vec![make_literal_int(1)],
1965                kwargs: vec![
1966                    (
1967                        "name".to_string(),
1968                        HirExpr::Literal(depyler_hir::hir::Literal::String("test".to_string())),
1969                    ),
1970                    ("value".to_string(), make_literal_int(42)),
1971                ],
1972            })],
1973        );
1974        let mut analysis = UseAfterMoveAnalysis::new();
1975        let errors = analysis.analyze_function(&func);
1976        assert!(errors.is_empty());
1977    }
1978}