1use std::collections::{HashMap, HashSet};
35
36use depyler_hir::hir::{AssignTarget, HirExpr, HirFunction, HirStmt, Type};
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct SourceSpan {
41 pub line: u32,
43 pub column: u32,
45 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#[derive(Debug, Clone)]
61pub struct UseAfterMoveError {
62 pub var: String,
64 pub move_site: SourceSpan,
66 pub use_site: SourceSpan,
68 pub moved_by: String,
70 pub fix: OwnershipFix,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
76pub enum OwnershipFix {
77 Borrow,
79 MutableBorrow,
81 Clone,
83 CloneAtAssignment { var: String },
85 Reject { reason: String },
87}
88
89#[derive(Debug, Clone)]
91pub struct AliasingPattern {
92 pub source: String,
94 pub alias: String,
96 pub source_used_after: bool,
98 pub alias_used_after: bool,
100 pub var_type: Type,
102}
103
104#[derive(Debug, Clone, PartialEq, Eq)]
106enum MoveState {
107 Available,
109 Moved(SourceSpan),
111 ConditionallyMoved(SourceSpan),
113}
114
115#[derive(Debug)]
120pub struct UseAfterMoveAnalysis {
121 move_states: HashMap<String, MoveState>,
123 errors: Vec<UseAfterMoveError>,
125 current_stmt_index: usize,
127 ownership_functions: HashSet<String>,
129 borrowing_functions: HashSet<String>,
131}
132
133impl Default for UseAfterMoveAnalysis {
134 fn default() -> Self {
135 Self::new()
136 }
137}
138
139impl UseAfterMoveAnalysis {
140 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 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 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 pub fn analyze_function(&mut self, func: &HirFunction) -> Vec<UseAfterMoveError> {
218 self.move_states.clear();
220 self.errors.clear();
221 self.current_stmt_index = 0;
222
223 for param in &func.params {
225 self.move_states
226 .insert(param.name.clone(), MoveState::Available);
227 }
228
229 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 fn analyze_statement(&mut self, stmt: &HirStmt) {
240 match stmt {
241 HirStmt::Assign { target, value, .. } => {
242 self.analyze_expression_for_use(value);
244
245 if let HirExpr::Var(source_name) = value {
247 if let AssignTarget::Symbol(target_name) = target {
249 self.record_potential_alias(target_name, source_name);
250 }
251 }
252
253 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 let state_before = self.move_states.clone();
275
276 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 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 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 self.analyze_expression_for_move(iter, "for");
312
313 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 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, ¶ms_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 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 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 HirExpr::Literal(_) => {}
571
572 _ => {}
574 }
575 }
576
577 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 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 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 fn record_potential_alias(&mut self, _target: &str, _source: &str) {
638 }
640
641 fn function_takes_ownership(&self, func_name: &str) -> bool {
643 if self.borrowing_functions.contains(func_name) {
644 return false;
645 }
646 false
648 }
649
650 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 fn merge_loop_state(&mut self, before: &HashMap<String, MoveState>) {
685 let current = self.move_states.clone();
686
687 for (var, state) in ¤t {
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 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 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 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 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 pub fn errors(&self) -> &[UseAfterMoveError] {
770 &self.errors
771 }
772}
773
774#[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 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#[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
989pub 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 #[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 }
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 assert!(!analysis.function_takes_ownership("print"));
1803 assert!(!analysis.function_takes_ownership("len"));
1804 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}