1use std::collections::{HashMap, HashSet};
18
19use bock_air::stubs::Value;
20use bock_air::{AIRNode, AirInterpolationPart, NodeId, NodeKind};
21use bock_ast::AssignOp;
22use bock_errors::{DiagnosticBag, DiagnosticCode, Span};
23
24const E_USE_AFTER_MOVE: DiagnosticCode = DiagnosticCode {
27 prefix: 'E',
28 number: 5001,
29};
30const E_MUT_BORROW_NEEDS_MUT: DiagnosticCode = DiagnosticCode {
31 prefix: 'E',
32 number: 5002,
33};
34const E_LOOP_MOVE: DiagnosticCode = DiagnosticCode {
35 prefix: 'E',
36 number: 5003,
37};
38
39pub type AIRModule = AIRNode;
43
44#[derive(Debug, Clone, PartialEq)]
46pub enum OwnershipState {
47 Owned,
49 Borrowed,
51 MutBorrowed,
53 Moved,
55 Managed,
57}
58
59#[derive(Debug, Clone, PartialEq)]
61pub struct OwnershipInfo {
62 pub state: OwnershipState,
64 pub mutable: bool,
66 pub origin: NodeId,
68}
69
70#[derive(Clone)]
73struct VarOwnership {
74 state: OwnershipState,
75 is_mut: bool,
76 move_site: Option<Span>,
78}
79
80struct OwnershipAnalyzer {
83 diags: DiagnosticBag,
84 env: HashMap<String, VarOwnership>,
85 in_loop: bool,
86 loop_entry_keys: HashSet<String>,
88 in_managed: bool,
90}
91
92impl OwnershipAnalyzer {
93 fn new() -> Self {
94 Self {
95 diags: DiagnosticBag::new(),
96 env: HashMap::new(),
97 in_loop: false,
98 loop_entry_keys: HashSet::new(),
99 in_managed: false,
100 }
101 }
102
103 fn snapshot(&self) -> HashMap<String, VarOwnership> {
104 self.env.clone()
105 }
106
107 fn check_use(&mut self, name: &str, use_span: Span) {
110 if let Some(var) = self.env.get(name) {
111 if matches!(var.state, OwnershipState::Moved) {
112 let move_site = var.move_site;
113 let diag = self.diags.error(
114 E_USE_AFTER_MOVE,
115 format!("use of moved variable `{name}`"),
116 use_span,
117 );
118 if let Some(ms) = move_site {
119 diag.label(ms, "value moved here");
120 }
121 }
122 }
123 }
124
125 fn do_move(&mut self, name: &str, move_span: Span) {
128 if let Some(var) = self.env.get(name) {
129 if matches!(var.state, OwnershipState::Managed) {
130 return; }
132 }
133
134 if self.in_loop && self.loop_entry_keys.contains(name) {
136 self.diags.error(
137 E_LOOP_MOVE,
138 format!(
139 "cannot move `{name}` inside a loop \
140 (would be moved on every iteration)"
141 ),
142 move_span,
143 );
144 }
145
146 if let Some(var) = self.env.get_mut(name) {
147 if matches!(var.state, OwnershipState::Moved) {
148 let move_site = var.move_site;
150 let diag = self.diags.error(
151 E_USE_AFTER_MOVE,
152 format!("use of moved variable `{name}`"),
153 move_span,
154 );
155 if let Some(ms) = move_site {
156 diag.label(ms, "value moved here");
157 }
158 } else {
159 var.state = OwnershipState::Moved;
160 var.move_site = Some(move_span);
161 }
162 }
163 }
164
165 fn analyze_move(&mut self, node: &AIRNode) -> bool {
171 if let NodeKind::Identifier { name } = &node.kind {
172 if let Some(var) = self.env.get(&name.name) {
173 if matches!(var.state, OwnershipState::Managed) {
174 return false; }
176 }
177 if node.metadata.get("copy_type") == Some(&Value::Bool(true)) {
180 return false;
181 }
182 self.do_move(&name.name, node.span);
183 false
184 } else {
185 self.analyze_node(node)
186 }
187 }
188
189 fn merge_states(
195 &self,
196 pre: &HashMap<String, VarOwnership>,
197 branches: &[(bool, HashMap<String, VarOwnership>)],
198 ) -> HashMap<String, VarOwnership> {
199 let non_div: Vec<&HashMap<String, VarOwnership>> = branches
200 .iter()
201 .filter(|(div, _)| !*div)
202 .map(|(_, s)| s)
203 .collect();
204
205 if non_div.is_empty() {
206 return pre.clone();
208 }
209
210 let mut result = pre.clone();
211 for name in pre.keys() {
212 let any_moved = non_div.iter().any(|state| {
213 state
214 .get(name)
215 .is_some_and(|v| matches!(v.state, OwnershipState::Moved))
216 });
217 if any_moved {
218 if let Some(var) = result.get_mut(name) {
219 let move_site = non_div
220 .iter()
221 .filter_map(|state| state.get(name))
222 .find(|v| matches!(v.state, OwnershipState::Moved))
223 .and_then(|v| v.move_site);
224 var.state = OwnershipState::Moved;
225 var.move_site = move_site;
226 }
227 }
228 }
229 result
230 }
231
232 fn bind_param(&mut self, param: &AIRNode) {
234 if let NodeKind::Param { pattern, .. } = ¶m.kind {
235 if let NodeKind::BindPat { name, is_mut } = &pattern.kind {
236 let state = if self.in_managed {
237 OwnershipState::Managed
238 } else {
239 OwnershipState::Owned
240 };
241 self.env.insert(
242 name.name.clone(),
243 VarOwnership {
244 state,
245 is_mut: *is_mut,
246 move_site: None,
247 },
248 );
249 }
250 }
251 }
252
253 fn bind_pattern(&mut self, pat: &AIRNode) {
255 let base_state = if self.in_managed {
256 OwnershipState::Managed
257 } else {
258 OwnershipState::Owned
259 };
260 match &pat.kind {
261 NodeKind::BindPat { name, is_mut } => {
262 self.env.insert(
263 name.name.clone(),
264 VarOwnership {
265 state: base_state,
266 is_mut: *is_mut,
267 move_site: None,
268 },
269 );
270 }
271 NodeKind::TuplePat { elems } => {
272 for e in elems {
273 self.bind_pattern(e);
274 }
275 }
276 NodeKind::ConstructorPat { fields, .. } => {
277 for f in fields {
278 self.bind_pattern(f);
279 }
280 }
281 NodeKind::RecordPat { fields, .. } => {
282 for f in fields {
283 if let Some(p) = &f.pattern {
284 self.bind_pattern(p);
285 } else {
286 self.env.insert(
287 f.name.name.clone(),
288 VarOwnership {
289 state: base_state.clone(),
290 is_mut: false,
291 move_site: None,
292 },
293 );
294 }
295 }
296 }
297 NodeKind::ListPat { elems, rest } => {
298 for e in elems {
299 self.bind_pattern(e);
300 }
301 if let Some(r) = rest {
302 self.bind_pattern(r);
303 }
304 }
305 NodeKind::OrPat { alternatives } => {
306 if let Some(first) = alternatives.first() {
307 self.bind_pattern(first);
308 }
309 }
310 _ => {}
311 }
312 }
313
314 #[allow(clippy::too_many_lines)]
316 fn analyze_node(&mut self, node: &AIRNode) -> bool {
317 match &node.kind {
318 NodeKind::Module { imports, items, .. } => {
320 for n in imports {
321 self.analyze_node(n);
322 }
323 for n in items {
324 self.analyze_node(n);
325 }
326 false
327 }
328
329 NodeKind::FnDecl {
331 annotations,
332 params,
333 body,
334 ..
335 } => {
336 let outer = self.snapshot();
337 let outer_managed = self.in_managed;
338 if annotations.iter().any(|a| a.name.name == "managed") {
339 self.in_managed = true;
340 }
341 for p in params {
342 self.bind_param(p);
343 }
344 self.analyze_node(body);
345 self.env = outer;
346 self.in_managed = outer_managed;
347 false
348 }
349 NodeKind::ImplBlock { methods, .. } => {
350 for m in methods {
351 self.analyze_node(m);
352 }
353 false
354 }
355 NodeKind::ClassDecl { methods, .. } => {
356 for m in methods {
357 self.analyze_node(m);
358 }
359 false
360 }
361 NodeKind::TraitDecl { methods, .. } => {
362 for m in methods {
363 self.analyze_node(m);
364 }
365 false
366 }
367 NodeKind::RecordDecl { .. }
369 | NodeKind::EnumDecl { .. }
370 | NodeKind::TypeAlias { .. }
371 | NodeKind::EffectDecl { .. }
372 | NodeKind::ConstDecl { .. }
373 | NodeKind::ImportDecl { .. }
374 | NodeKind::ModuleHandle { .. }
375 | NodeKind::PropertyTest { .. } => false,
376
377 NodeKind::Block { stmts, tail } => {
379 let pre_keys: HashSet<String> = self.env.keys().cloned().collect();
380 let mut diverges = false;
381 for stmt in stmts {
382 if diverges {
383 break;
384 }
385 diverges = self.analyze_node(stmt);
386 }
387 if !diverges {
388 if let Some(t) = tail {
389 diverges = self.analyze_node(t);
390 }
391 }
392 self.env.retain(|k, _| pre_keys.contains(k));
394 diverges
395 }
396
397 NodeKind::LetBinding {
399 is_mut,
400 pattern,
401 value,
402 ..
403 } => {
404 let is_managed = self.in_managed
405 || node.metadata.get("managed") == Some(&Value::Bool(true));
406
407 self.analyze_move(value);
408
409 let state = if is_managed {
410 OwnershipState::Managed
411 } else {
412 OwnershipState::Owned
413 };
414
415 let own = VarOwnership {
419 state,
420 is_mut: *is_mut,
421 move_site: None,
422 };
423 if is_managed {
424 if let NodeKind::BindPat { name, .. } = &pattern.kind {
426 self.env.insert(name.name.clone(), own);
427 } else {
428 self.bind_pattern(pattern);
429 }
432 } else {
433 self.bind_pattern(pattern);
434 }
435 false
436 }
437
438 NodeKind::Assign { op, target, value } => {
440 match op {
441 AssignOp::Assign => {
442 self.analyze_move(value);
444 self.analyze_node(target);
445 }
446 _ => {
447 self.analyze_node(target);
449 self.analyze_node(value);
450 }
451 }
452 false
453 }
454
455 NodeKind::Identifier { name } => {
457 self.check_use(&name.name, node.span);
458 false
459 }
460
461 NodeKind::Move { expr } => {
463 self.analyze_move(expr);
464 false
465 }
466 NodeKind::Borrow { expr } => {
467 self.analyze_node(expr);
469 false
470 }
471 NodeKind::MutableBorrow { expr } => {
472 if let NodeKind::Identifier { name } = &expr.kind {
474 if let Some(var) = self.env.get(&name.name) {
475 if !var.is_mut && !matches!(var.state, OwnershipState::Managed) {
476 self.diags.error(
477 E_MUT_BORROW_NEEDS_MUT,
478 format!(
479 "cannot mutably borrow `{}`: \
480 variable not declared `mut`",
481 name.name
482 ),
483 expr.span,
484 );
485 }
486 }
487 }
488 self.analyze_node(expr);
489 false
490 }
491
492 NodeKind::Return { value } => {
494 if let Some(v) = value {
495 let old_in_loop = self.in_loop;
498 self.in_loop = false;
499 self.analyze_move(v);
500 self.in_loop = old_in_loop;
501 }
502 true
503 }
504 NodeKind::Break { value } => {
505 if let Some(v) = value {
506 let old_in_loop = self.in_loop;
509 self.in_loop = false;
510 self.analyze_move(v);
511 self.in_loop = old_in_loop;
512 }
513 true
514 }
515 NodeKind::Continue => true,
516 NodeKind::Unreachable => true,
517
518 NodeKind::If {
520 condition,
521 then_block,
522 else_block,
523 ..
524 } => {
525 self.analyze_node(condition);
526
527 let pre = self.snapshot();
528
529 let then_div = self.analyze_node(then_block);
530 let then_state = self.snapshot();
531
532 self.env = pre.clone();
533 let (else_div, else_state) = match else_block {
534 Some(eb) => {
535 let d = self.analyze_node(eb);
536 (d, self.snapshot())
537 }
538 None => {
539 (false, pre.clone())
541 }
542 };
543
544 self.env =
545 self.merge_states(&pre, &[(then_div, then_state), (else_div, else_state)]);
546 then_div && else_div
547 }
548
549 NodeKind::Guard {
551 let_pattern,
552 condition,
553 else_block,
554 } => {
555 if let Some(pat) = let_pattern {
556 self.analyze_node(pat);
557 }
558 self.analyze_node(condition);
559 let pre = self.snapshot();
562 self.analyze_node(else_block);
563 self.env = pre;
565 false
566 }
567
568 NodeKind::Match { scrutinee, arms } => {
570 self.analyze_node(scrutinee);
571
572 let pre = self.snapshot();
573 let mut arm_results: Vec<(bool, HashMap<String, VarOwnership>)> =
574 Vec::with_capacity(arms.len());
575
576 for arm in arms {
577 self.env = pre.clone();
578 let div = self.analyze_node(arm);
579 arm_results.push((div, self.snapshot()));
580 }
581
582 self.env = self.merge_states(&pre, &arm_results);
583 arm_results.iter().all(|(d, _)| *d)
584 }
585
586 NodeKind::MatchArm {
587 pattern,
588 guard,
589 body,
590 } => {
591 let pre_keys: HashSet<String> = self.env.keys().cloned().collect();
592 self.bind_pattern(pattern);
593 if let Some(g) = guard {
594 self.analyze_node(g);
595 }
596 let div = self.analyze_node(body);
597 self.env.retain(|k, _| pre_keys.contains(k));
599 div
600 }
601
602 NodeKind::For {
604 pattern,
605 iterable,
606 body,
607 } => {
608 self.analyze_node(iterable);
609 let pre = self.snapshot();
610 let old_in_loop = self.in_loop;
611 let old_loop_keys = std::mem::take(&mut self.loop_entry_keys);
612 self.in_loop = true;
613 self.loop_entry_keys = pre.keys().cloned().collect();
614 self.bind_pattern(pattern);
615 self.analyze_node(body);
616 self.in_loop = old_in_loop;
617 self.loop_entry_keys = old_loop_keys;
618 self.env = pre;
620 false
621 }
622 NodeKind::While { condition, body } => {
623 self.analyze_node(condition);
624 let pre = self.snapshot();
625 let old_in_loop = self.in_loop;
626 let old_loop_keys = std::mem::take(&mut self.loop_entry_keys);
627 self.in_loop = true;
628 self.loop_entry_keys = pre.keys().cloned().collect();
629 self.analyze_node(body);
630 self.in_loop = old_in_loop;
631 self.loop_entry_keys = old_loop_keys;
632 self.env = pre;
633 false
634 }
635 NodeKind::Loop { body } => {
636 let pre = self.snapshot();
637 let old_in_loop = self.in_loop;
638 let old_loop_keys = std::mem::take(&mut self.loop_entry_keys);
639 self.in_loop = true;
640 self.loop_entry_keys = pre.keys().cloned().collect();
641 self.analyze_node(body);
642 self.in_loop = old_in_loop;
643 self.loop_entry_keys = old_loop_keys;
644 self.env = pre;
645 false
646 }
647
648 NodeKind::Call { callee, args, .. } => {
650 self.analyze_node(callee);
651 for arg in args {
652 self.analyze_node(&arg.value);
655 }
656 false
657 }
658 NodeKind::MethodCall { receiver, args, .. } => {
659 self.analyze_node(receiver);
660 for arg in args {
661 self.analyze_node(&arg.value);
662 }
663 false
664 }
665
666 NodeKind::Lambda { params, body } => {
668 let outer = self.snapshot();
669 for p in params {
670 self.bind_param(p);
671 }
672 self.analyze_node(body);
673 self.env = outer;
674 false
675 }
676
677 NodeKind::BinaryOp { left, right, .. } => {
679 self.analyze_node(left);
680 self.analyze_node(right);
681 false
682 }
683 NodeKind::UnaryOp { operand, .. } => {
684 self.analyze_node(operand);
685 false
686 }
687 NodeKind::FieldAccess { object, .. } => {
688 self.analyze_node(object);
689 false
690 }
691 NodeKind::Index { object, index } => {
692 self.analyze_node(object);
693 self.analyze_node(index);
694 false
695 }
696 NodeKind::Propagate { expr } => {
697 self.analyze_node(expr);
698 false
699 }
700 NodeKind::Pipe { left, right } | NodeKind::Compose { left, right } => {
701 self.analyze_node(left);
702 self.analyze_node(right);
703 false
704 }
705 NodeKind::Await { expr } => {
706 self.analyze_node(expr);
707 false
708 }
709 NodeKind::Range { lo, hi, .. } => {
710 self.analyze_node(lo);
711 self.analyze_node(hi);
712 false
713 }
714 NodeKind::RecordConstruct { fields, spread, .. } => {
715 for f in fields {
716 if let Some(v) = &f.value {
717 self.analyze_move(v);
718 }
719 }
720 if let Some(s) = spread {
721 self.analyze_node(s);
722 }
723 false
724 }
725 NodeKind::ListLiteral { elems }
726 | NodeKind::SetLiteral { elems }
727 | NodeKind::TupleLiteral { elems } => {
728 for e in elems {
729 self.analyze_move(e);
730 }
731 false
732 }
733 NodeKind::MapLiteral { entries } => {
734 for entry in entries {
735 self.analyze_move(&entry.key);
736 self.analyze_move(&entry.value);
737 }
738 false
739 }
740 NodeKind::Interpolation { parts } => {
741 for part in parts {
742 if let AirInterpolationPart::Expr(e) = part {
743 self.analyze_node(e);
744 }
745 }
746 false
747 }
748 NodeKind::ResultConstruct { value, .. } => {
749 if let Some(v) = value {
750 self.analyze_move(v);
751 }
752 false
753 }
754 NodeKind::HandlingBlock { handlers, body } => {
755 for h in handlers {
756 self.analyze_node(&h.handler);
757 }
758 self.analyze_node(body);
759 false
760 }
761
762 NodeKind::Literal { .. }
764 | NodeKind::Placeholder
765 | NodeKind::TypeSelf
766 | NodeKind::WildcardPat
767 | NodeKind::LiteralPat { .. }
768 | NodeKind::RestPat
769 | NodeKind::TypeNamed { .. }
770 | NodeKind::TypeTuple { .. }
771 | NodeKind::TypeFunction { .. }
772 | NodeKind::TypeOptional { .. }
773 | NodeKind::EffectOp { .. }
774 | NodeKind::EffectRef { .. }
775 | NodeKind::Error => false,
776
777 NodeKind::BindPat { .. }
779 | NodeKind::ConstructorPat { .. }
780 | NodeKind::RecordPat { .. }
781 | NodeKind::TuplePat { .. }
782 | NodeKind::ListPat { .. }
783 | NodeKind::OrPat { .. }
784 | NodeKind::GuardPat { .. }
785 | NodeKind::RangePat { .. } => false,
786
787 _ => false,
789 }
790 }
791}
792
793#[must_use]
810pub fn analyze_ownership(module: &AIRModule) -> DiagnosticBag {
811 let mut analyzer = OwnershipAnalyzer::new();
812 analyzer.analyze_node(module);
813 analyzer.diags
814}
815
816#[cfg(test)]
819mod tests {
820 use super::*;
821 use bock_air::stubs::Value;
822 use bock_air::{AIRNode, AirArg, NodeIdGen, NodeKind};
823 use bock_ast::{Ident, Literal};
824 use bock_errors::{FileId, Span};
825
826 fn span() -> Span {
829 Span {
830 file: FileId(0),
831 start: 0,
832 end: 0,
833 }
834 }
835
836 fn span_at(start: usize, end: usize) -> Span {
837 Span {
838 file: FileId(0),
839 start,
840 end,
841 }
842 }
843
844 fn ident(name: &str) -> Ident {
845 Ident {
846 name: name.to_string(),
847 span: span(),
848 }
849 }
850
851 fn node(gen: &NodeIdGen, kind: NodeKind) -> AIRNode {
852 AIRNode::new(gen.next(), span(), kind)
853 }
854
855 fn node_at(gen: &NodeIdGen, kind: NodeKind, s: usize, e: usize) -> AIRNode {
856 AIRNode::new(gen.next(), span_at(s, e), kind)
857 }
858
859 fn id_node(gen: &NodeIdGen, name: &str) -> AIRNode {
860 node(gen, NodeKind::Identifier { name: ident(name) })
861 }
862
863 fn id_node_at(gen: &NodeIdGen, name: &str, s: usize, e: usize) -> AIRNode {
864 node_at(gen, NodeKind::Identifier { name: ident(name) }, s, e)
865 }
866
867 fn lit_node(gen: &NodeIdGen) -> AIRNode {
868 node(
869 gen,
870 NodeKind::Literal {
871 lit: Literal::Int("42".into()),
872 },
873 )
874 }
875
876 fn bind_pat(gen: &NodeIdGen, name: &str, is_mut: bool) -> AIRNode {
877 node(
878 gen,
879 NodeKind::BindPat {
880 name: ident(name),
881 is_mut,
882 },
883 )
884 }
885
886 fn let_binding(gen: &NodeIdGen, name: &str, is_mut: bool, value: AIRNode) -> AIRNode {
887 node(
888 gen,
889 NodeKind::LetBinding {
890 is_mut,
891 pattern: Box::new(bind_pat(gen, name, is_mut)),
892 ty: None,
893 value: Box::new(value),
894 },
895 )
896 }
897
898 fn block(gen: &NodeIdGen, stmts: Vec<AIRNode>, tail: Option<AIRNode>) -> AIRNode {
899 node(
900 gen,
901 NodeKind::Block {
902 stmts,
903 tail: tail.map(Box::new),
904 },
905 )
906 }
907
908 fn module(gen: &NodeIdGen, items: Vec<AIRNode>) -> AIRNode {
909 node(
910 gen,
911 NodeKind::Module {
912 path: None,
913 annotations: vec![],
914 imports: vec![],
915 items,
916 },
917 )
918 }
919
920 fn fn_decl(gen: &NodeIdGen, body: AIRNode) -> AIRNode {
921 fn_decl_with(gen, body, vec![])
922 }
923
924 fn managed_fn_decl(gen: &NodeIdGen, body: AIRNode) -> AIRNode {
925 use bock_ast::Annotation;
926 fn_decl_with(
927 gen,
928 body,
929 vec![Annotation {
930 id: 0,
931 span: span(),
932 name: ident("managed"),
933 args: vec![],
934 }],
935 )
936 }
937
938 fn fn_decl_with(gen: &NodeIdGen, body: AIRNode, annotations: Vec<bock_ast::Annotation>) -> AIRNode {
939 node(
940 gen,
941 NodeKind::FnDecl {
942 annotations,
943 visibility: bock_ast::Visibility::Public,
944 is_async: false,
945 name: ident("f"),
946 generic_params: vec![],
947 params: vec![],
948 return_type: None,
949 effect_clause: vec![],
950 where_clause: vec![],
951 body: Box::new(body),
952 },
953 )
954 }
955
956 fn return_node(gen: &NodeIdGen, val: Option<AIRNode>) -> AIRNode {
957 node(
958 gen,
959 NodeKind::Return {
960 value: val.map(Box::new),
961 },
962 )
963 }
964
965 fn move_node(gen: &NodeIdGen, expr: AIRNode) -> AIRNode {
966 node(
967 gen,
968 NodeKind::Move {
969 expr: Box::new(expr),
970 },
971 )
972 }
973
974 fn mut_borrow(gen: &NodeIdGen, expr: AIRNode) -> AIRNode {
975 node(
976 gen,
977 NodeKind::MutableBorrow {
978 expr: Box::new(expr),
979 },
980 )
981 }
982
983 fn if_node(gen: &NodeIdGen, cond: AIRNode, then: AIRNode, else_: Option<AIRNode>) -> AIRNode {
984 node(
985 gen,
986 NodeKind::If {
987 let_pattern: None,
988 condition: Box::new(cond),
989 then_block: Box::new(then),
990 else_block: else_.map(Box::new),
991 },
992 )
993 }
994
995 fn guard_node(gen: &NodeIdGen, cond: AIRNode, else_block: AIRNode) -> AIRNode {
996 node(
997 gen,
998 NodeKind::Guard {
999 let_pattern: None,
1000 condition: Box::new(cond),
1001 else_block: Box::new(else_block),
1002 },
1003 )
1004 }
1005
1006 fn loop_node(gen: &NodeIdGen, body: AIRNode) -> AIRNode {
1007 node(
1008 gen,
1009 NodeKind::Loop {
1010 body: Box::new(body),
1011 },
1012 )
1013 }
1014
1015 fn match_node(gen: &NodeIdGen, scrutinee: AIRNode, arms: Vec<AIRNode>) -> AIRNode {
1016 node(
1017 gen,
1018 NodeKind::Match {
1019 scrutinee: Box::new(scrutinee),
1020 arms,
1021 },
1022 )
1023 }
1024
1025 fn match_arm(gen: &NodeIdGen, pat: AIRNode, body: AIRNode) -> AIRNode {
1026 node(
1027 gen,
1028 NodeKind::MatchArm {
1029 pattern: Box::new(pat),
1030 guard: None,
1031 body: Box::new(body),
1032 },
1033 )
1034 }
1035
1036 #[test]
1039 fn no_error_simple_borrow() {
1040 let gen = NodeIdGen::new();
1044 let data_lit = lit_node(&gen);
1045 let let_data = let_binding(&gen, "data", false, data_lit);
1046 let use1 = id_node(&gen, "data");
1047 let use2 = id_node(&gen, "data");
1048 let call = node(
1049 &gen,
1050 NodeKind::Call {
1051 callee: Box::new(id_node(&gen, "summarize")),
1052 args: vec![AirArg {
1053 label: None,
1054 value: use1,
1055 }],
1056 type_args: vec![],
1057 },
1058 );
1059 let b = block(&gen, vec![let_data, call, use2], None);
1060 let m = module(&gen, vec![fn_decl(&gen, b)]);
1061 let diags = analyze_ownership(&m);
1062 assert!(
1063 !diags.has_errors(),
1064 "expected no errors, got: {:?}",
1065 diags.iter().collect::<Vec<_>>()
1066 );
1067 }
1068
1069 #[test]
1070 fn move_on_let_binding() {
1071 let gen = NodeIdGen::new();
1075 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1076 let id_data = id_node_at(&gen, "data", 10, 14);
1077 let let_archive = let_binding(&gen, "archive", false, id_data);
1078 let use_data = id_node_at(&gen, "data", 20, 24);
1079 let b = block(&gen, vec![let_data, let_archive, use_data], None);
1080 let m = module(&gen, vec![fn_decl(&gen, b)]);
1081 let diags = analyze_ownership(&m);
1082 assert!(diags.has_errors());
1083 assert!(diags.iter().any(|d| d.code == E_USE_AFTER_MOVE));
1084 }
1085
1086 #[test]
1087 fn explicit_move_node() {
1088 let gen = NodeIdGen::new();
1092 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1093 let mv = move_node(&gen, id_node_at(&gen, "data", 5, 9));
1094 let use_data = id_node_at(&gen, "data", 15, 19);
1095 let b = block(&gen, vec![let_data, mv, use_data], None);
1096 let m = module(&gen, vec![fn_decl(&gen, b)]);
1097 let diags = analyze_ownership(&m);
1098 assert!(diags.has_errors());
1099 assert!(diags.iter().any(|d| d.code == E_USE_AFTER_MOVE));
1100 }
1101
1102 #[test]
1103 fn use_after_move_has_move_site_label() {
1104 let gen = NodeIdGen::new();
1105 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1106 let mv = move_node(&gen, id_node_at(&gen, "data", 5, 9));
1107 let use_data = id_node_at(&gen, "data", 15, 19);
1108 let b = block(&gen, vec![let_data, mv, use_data], None);
1109 let m = module(&gen, vec![fn_decl(&gen, b)]);
1110 let diags = analyze_ownership(&m);
1111 let err = diags.iter().find(|d| d.code == E_USE_AFTER_MOVE).unwrap();
1112 assert!(
1113 !err.labels.is_empty(),
1114 "expected a label pointing to move site"
1115 );
1116 assert!(err.labels[0].message.contains("moved"));
1117 }
1118
1119 #[test]
1122 fn mut_borrow_of_non_mut_errors() {
1123 let gen = NodeIdGen::new();
1126 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1127 let mb = mut_borrow(&gen, id_node(&gen, "data"));
1128 let b = block(&gen, vec![let_data, mb], None);
1129 let m = module(&gen, vec![fn_decl(&gen, b)]);
1130 let diags = analyze_ownership(&m);
1131 assert!(diags.has_errors());
1132 assert!(diags.iter().any(|d| d.code == E_MUT_BORROW_NEEDS_MUT));
1133 }
1134
1135 #[test]
1136 fn mut_borrow_of_mut_ok() {
1137 let gen = NodeIdGen::new();
1140 let let_data = let_binding(&gen, "data", true, lit_node(&gen));
1141 let mb = mut_borrow(&gen, id_node(&gen, "data"));
1142 let b = block(&gen, vec![let_data, mb], None);
1143 let m = module(&gen, vec![fn_decl(&gen, b)]);
1144 let diags = analyze_ownership(&m);
1145 assert!(!diags.has_errors());
1146 }
1147
1148 #[test]
1151 fn managed_skips_ownership_tracking() {
1152 let gen = NodeIdGen::new();
1156 let mut let_data = let_binding(&gen, "data", false, lit_node(&gen));
1157 let_data
1158 .metadata
1159 .insert("managed".into(), Value::Bool(true));
1160 let id_data = id_node(&gen, "data");
1161 let let_archive = let_binding(&gen, "archive", false, id_data);
1162 let use_data = id_node(&gen, "data");
1163 let b = block(&gen, vec![let_data, let_archive, use_data], None);
1164 let m = module(&gen, vec![fn_decl(&gen, b)]);
1165 let diags = analyze_ownership(&m);
1166 assert!(!diags.has_errors());
1167 }
1168
1169 #[test]
1170 fn managed_fn_suppresses_move_errors() {
1171 let gen = NodeIdGen::new();
1178 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1179 let id_data1 = id_node(&gen, "data");
1180 let let_a = let_binding(&gen, "a", false, id_data1);
1181 let id_data2 = id_node(&gen, "data");
1182 let let_b = let_binding(&gen, "b", false, id_data2);
1183 let b = block(&gen, vec![let_data, let_a, let_b], None);
1184 let m = module(&gen, vec![managed_fn_decl(&gen, b)]);
1185 let diags = analyze_ownership(&m);
1186 assert!(
1187 !diags.has_errors(),
1188 "expected no errors in @managed fn, got: {:?}",
1189 diags.iter().collect::<Vec<_>>()
1190 );
1191 }
1192
1193 #[test]
1194 fn non_managed_fn_still_errors_on_reuse() {
1195 let gen = NodeIdGen::new();
1201 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1202 let id_data1 = id_node(&gen, "data");
1203 let let_a = let_binding(&gen, "a", false, id_data1);
1204 let id_data2 = id_node_at(&gen, "data", 20, 24);
1205 let let_b = let_binding(&gen, "b", false, id_data2);
1206 let b = block(&gen, vec![let_data, let_a, let_b], None);
1207 let m = module(&gen, vec![fn_decl(&gen, b)]);
1208 let diags = analyze_ownership(&m);
1209 assert!(diags.has_errors());
1210 assert!(diags.iter().any(|d| d.code == E_USE_AFTER_MOVE));
1211 }
1212
1213 #[test]
1214 fn managed_fn_loop_move_suppressed() {
1215 let gen = NodeIdGen::new();
1221 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1222 let id_data = id_node(&gen, "data");
1223 let let_discard = let_binding(&gen, "_d", false, id_data);
1224 let loop_body = block(&gen, vec![let_discard], None);
1225 let lp = loop_node(&gen, loop_body);
1226 let b = block(&gen, vec![let_data, lp], None);
1227 let m = module(&gen, vec![managed_fn_decl(&gen, b)]);
1228 let diags = analyze_ownership(&m);
1229 assert!(
1230 !diags.has_errors(),
1231 "expected no errors in @managed fn loop, got: {:?}",
1232 diags.iter().collect::<Vec<_>>()
1233 );
1234 }
1235
1236 #[test]
1239 fn guard_else_diverges_data_still_owned() {
1240 let gen = NodeIdGen::new();
1243 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1244 let cond = lit_node(&gen);
1245 let else_block = block(&gen, vec![return_node(&gen, None)], None);
1246 let guard = guard_node(&gen, cond, else_block);
1247 let use_data = id_node(&gen, "data");
1248 let b = block(&gen, vec![let_data, guard, use_data], None);
1249 let m = module(&gen, vec![fn_decl(&gen, b)]);
1250 let diags = analyze_ownership(&m);
1251 assert!(!diags.has_errors());
1252 }
1253
1254 #[test]
1255 fn if_with_diverging_then_data_still_owned() {
1256 let gen = NodeIdGen::new();
1260 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1261 let cond = lit_node(&gen);
1262 let id_data = id_node(&gen, "data");
1264 let let_archive = let_binding(&gen, "archive", false, id_data);
1265 let ret = return_node(&gen, None);
1266 let then_block = block(&gen, vec![let_archive, ret], None);
1267 let if_node_ = if_node(&gen, cond, then_block, None);
1268 let use_data = id_node(&gen, "data");
1269 let b = block(&gen, vec![let_data, if_node_, use_data], None);
1270 let m = module(&gen, vec![fn_decl(&gen, b)]);
1271 let diags = analyze_ownership(&m);
1272 assert!(!diags.has_errors());
1273 }
1274
1275 #[test]
1276 fn if_non_diverging_branch_moves_makes_moved_at_join() {
1277 let gen = NodeIdGen::new();
1281 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1282 let cond = lit_node(&gen);
1283 let id_data = id_node(&gen, "data");
1284 let let_archive = let_binding(&gen, "archive", false, id_data);
1285 let then_block = block(&gen, vec![let_archive], None);
1286 let if_node_ = if_node(&gen, cond, then_block, None);
1287 let use_data = id_node_at(&gen, "data", 30, 34);
1288 let b = block(&gen, vec![let_data, if_node_, use_data], None);
1289 let m = module(&gen, vec![fn_decl(&gen, b)]);
1290 let diags = analyze_ownership(&m);
1291 assert!(diags.has_errors());
1292 assert!(diags.iter().any(|d| d.code == E_USE_AFTER_MOVE));
1293 }
1294
1295 #[test]
1296 fn if_both_branches_diverge_join_uses_pre_state() {
1297 let gen = NodeIdGen::new();
1301 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1302 let cond = lit_node(&gen);
1303 let then_block = block(&gen, vec![return_node(&gen, None)], None);
1304 let else_block = block(&gen, vec![return_node(&gen, None)], None);
1305 let if_node_ = if_node(&gen, cond, then_block, Some(else_block));
1306 let b = block(&gen, vec![let_data, if_node_], None);
1307 let m = module(&gen, vec![fn_decl(&gen, b)]);
1308 let diags = analyze_ownership(&m);
1309 assert!(!diags.has_errors());
1310 }
1311
1312 #[test]
1315 fn move_inside_loop_is_error() {
1316 let gen = NodeIdGen::new();
1319 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1320 let id_data = id_node_at(&gen, "data", 10, 14);
1321 let let_archive = let_binding(&gen, "archive", false, id_data);
1322 let loop_body = block(&gen, vec![let_archive], None);
1323 let lp = loop_node(&gen, loop_body);
1324 let b = block(&gen, vec![let_data, lp], None);
1325 let m = module(&gen, vec![fn_decl(&gen, b)]);
1326 let diags = analyze_ownership(&m);
1327 assert!(diags.has_errors());
1328 assert!(diags.iter().any(|d| d.code == E_LOOP_MOVE));
1329 }
1330
1331 #[test]
1332 fn variable_defined_inside_loop_can_be_moved() {
1333 let gen = NodeIdGen::new();
1335 let let_tmp = let_binding(&gen, "tmp", false, lit_node(&gen));
1336 let id_tmp = id_node(&gen, "tmp");
1337 let let_discard = let_binding(&gen, "_unused", false, id_tmp);
1338 let loop_body = block(&gen, vec![let_tmp, let_discard], None);
1339 let lp = loop_node(&gen, loop_body);
1340 let b = block(&gen, vec![lp], None);
1341 let m = module(&gen, vec![fn_decl(&gen, b)]);
1342 let diags = analyze_ownership(&m);
1343 assert!(!diags.has_errors());
1344 }
1345
1346 #[test]
1349 fn match_all_arms_diverge_no_use_after_move() {
1350 let gen = NodeIdGen::new();
1357 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1358 let scrutinee = lit_node(&gen);
1359 let id_data = id_node(&gen, "data");
1360 let let_discard = let_binding(&gen, "_d", false, id_data);
1361 let ret1 = return_node(&gen, None);
1362 let arm1_body = block(&gen, vec![let_discard, ret1], None);
1363 let arm1 = match_arm(&gen, node(&gen, NodeKind::WildcardPat), arm1_body);
1364 let ret2 = return_node(&gen, None);
1365 let arm2_body = block(&gen, vec![ret2], None);
1366 let arm2 = match_arm(&gen, node(&gen, NodeKind::WildcardPat), arm2_body);
1367 let m_node = match_node(&gen, scrutinee, vec![arm1, arm2]);
1368 let b = block(&gen, vec![let_data, m_node], None);
1369 let m = module(&gen, vec![fn_decl(&gen, b)]);
1370 let diags = analyze_ownership(&m);
1371 assert!(!diags.has_errors());
1372 }
1373
1374 #[test]
1375 fn match_non_diverging_arm_moves_is_error_after() {
1376 let gen = NodeIdGen::new();
1383 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1384 let scrutinee = lit_node(&gen);
1385 let id_data = id_node(&gen, "data");
1386 let let_discard = let_binding(&gen, "_d", false, id_data);
1387 let arm1_body = block(&gen, vec![let_discard], None);
1388 let arm1 = match_arm(&gen, node(&gen, NodeKind::WildcardPat), arm1_body);
1389 let ret = return_node(&gen, None);
1390 let arm2_body = block(&gen, vec![ret], None);
1391 let arm2 = match_arm(&gen, node(&gen, NodeKind::WildcardPat), arm2_body);
1392 let m_node = match_node(&gen, scrutinee, vec![arm1, arm2]);
1393 let use_data = id_node_at(&gen, "data", 50, 54);
1394 let b = block(&gen, vec![let_data, m_node, use_data], None);
1395 let m = module(&gen, vec![fn_decl(&gen, b)]);
1396 let diags = analyze_ownership(&m);
1397 assert!(diags.has_errors());
1398 assert!(diags.iter().any(|d| d.code == E_USE_AFTER_MOVE));
1399 }
1400
1401 #[test]
1404 fn if_inside_match_arm_nested() {
1405 let gen = NodeIdGen::new();
1413 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1414 let scrutinee = lit_node(&gen);
1415 let cond = lit_node(&gen);
1416 let id_data = id_node(&gen, "data");
1417 let let_discard = let_binding(&gen, "_d", false, id_data);
1418 let then_block = block(&gen, vec![let_discard], None);
1419 let else_block = block(&gen, vec![return_node(&gen, None)], None);
1420 let if_expr = if_node(&gen, cond, then_block, Some(else_block));
1421 let arm_body = block(&gen, vec![if_expr], None);
1422 let arm = match_arm(&gen, node(&gen, NodeKind::WildcardPat), arm_body);
1423 let m_node = match_node(&gen, scrutinee, vec![arm]);
1424 let use_data = id_node_at(&gen, "data", 50, 54);
1425 let b = block(&gen, vec![let_data, m_node, use_data], None);
1426 let m = module(&gen, vec![fn_decl(&gen, b)]);
1427 let diags = analyze_ownership(&m);
1428 assert!(diags.has_errors());
1429 assert!(diags.iter().any(|d| d.code == E_USE_AFTER_MOVE));
1430 }
1431
1432 #[test]
1433 fn no_false_positive_if_else_both_leave_owned() {
1434 let gen = NodeIdGen::new();
1438 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1439 let cond = lit_node(&gen);
1440 let use1 = id_node(&gen, "data");
1441 let use2 = id_node(&gen, "data");
1442 let then_block = block(&gen, vec![use1], None);
1443 let else_block = block(&gen, vec![use2], None);
1444 let if_expr = if_node(&gen, cond, then_block, Some(else_block));
1445 let use_after = id_node(&gen, "data");
1446 let b = block(&gen, vec![let_data, if_expr, use_after], None);
1447 let m = module(&gen, vec![fn_decl(&gen, b)]);
1448 let diags = analyze_ownership(&m);
1449 assert!(!diags.has_errors());
1450 }
1451
1452 #[test]
1453 fn double_move_error() {
1454 let gen = NodeIdGen::new();
1458 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1459 let let_a = let_binding(&gen, "a", false, id_node(&gen, "data"));
1460 let let_b = let_binding(&gen, "b", false, id_node_at(&gen, "data", 20, 24));
1461 let b = block(&gen, vec![let_data, let_a, let_b], None);
1462 let m = module(&gen, vec![fn_decl(&gen, b)]);
1463 let diags = analyze_ownership(&m);
1464 assert!(diags.has_errors());
1465 assert!(diags.iter().any(|d| d.code == E_USE_AFTER_MOVE));
1466 }
1467}