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};
38const E_MUT_RECEIVER_NEEDED: DiagnosticCode = DiagnosticCode {
47 prefix: 'E',
48 number: 5004,
49};
50
51const MUT_LIST_RECEIVER_METHODS: &[&str] = &[
56 "push",
57 "append",
58 "pop",
59 "remove_at",
60 "insert",
61 "reverse",
62 "set",
63];
64
65pub type AIRModule = AIRNode;
69
70#[derive(Debug, Clone, PartialEq)]
72pub enum OwnershipState {
73 Owned,
75 Borrowed,
77 MutBorrowed,
79 Moved,
81 Managed,
83}
84
85#[derive(Debug, Clone, PartialEq)]
87pub struct OwnershipInfo {
88 pub state: OwnershipState,
90 pub mutable: bool,
92 pub origin: NodeId,
94}
95
96#[derive(Clone)]
99struct VarOwnership {
100 state: OwnershipState,
101 is_mut: bool,
102 move_site: Option<Span>,
104}
105
106struct OwnershipAnalyzer {
109 diags: DiagnosticBag,
110 env: HashMap<String, VarOwnership>,
111 in_loop: bool,
112 loop_entry_keys: HashSet<String>,
114 in_managed: bool,
116}
117
118impl OwnershipAnalyzer {
119 fn new() -> Self {
120 Self {
121 diags: DiagnosticBag::new(),
122 env: HashMap::new(),
123 in_loop: false,
124 loop_entry_keys: HashSet::new(),
125 in_managed: false,
126 }
127 }
128
129 fn snapshot(&self) -> HashMap<String, VarOwnership> {
130 self.env.clone()
131 }
132
133 fn check_use(&mut self, name: &str, use_span: Span) {
136 if let Some(var) = self.env.get(name) {
137 if matches!(var.state, OwnershipState::Moved) {
138 let move_site = var.move_site;
139 let diag = self.diags.error(
140 E_USE_AFTER_MOVE,
141 format!("use of moved variable `{name}`"),
142 use_span,
143 );
144 if let Some(ms) = move_site {
145 diag.label(ms, "value moved here");
146 }
147 }
148 }
149 }
150
151 fn do_move(&mut self, name: &str, move_span: Span) {
154 if let Some(var) = self.env.get(name) {
155 if matches!(var.state, OwnershipState::Managed) {
156 return; }
158 }
159
160 if self.in_loop && self.loop_entry_keys.contains(name) {
162 self.diags.error(
163 E_LOOP_MOVE,
164 format!(
165 "cannot move `{name}` inside a loop \
166 (would be moved on every iteration)"
167 ),
168 move_span,
169 );
170 }
171
172 if let Some(var) = self.env.get_mut(name) {
173 if matches!(var.state, OwnershipState::Moved) {
174 let move_site = var.move_site;
176 let diag = self.diags.error(
177 E_USE_AFTER_MOVE,
178 format!("use of moved variable `{name}`"),
179 move_span,
180 );
181 if let Some(ms) = move_site {
182 diag.label(ms, "value moved here");
183 }
184 } else {
185 var.state = OwnershipState::Moved;
186 var.move_site = Some(move_span);
187 }
188 }
189 }
190
191 fn analyze_move(&mut self, node: &AIRNode) -> bool {
197 if let NodeKind::Identifier { name } = &node.kind {
198 if let Some(var) = self.env.get(&name.name) {
199 if matches!(var.state, OwnershipState::Managed) {
200 return false; }
202 }
203 if node.metadata.get("copy_type") == Some(&Value::Bool(true)) {
206 return false;
207 }
208 self.do_move(&name.name, node.span);
209 false
210 } else {
211 self.analyze_node(node)
212 }
213 }
214
215 fn is_mutable_lvalue(&self, node: &AIRNode) -> bool {
232 match &node.kind {
233 NodeKind::Identifier { name } => match self.env.get(&name.name) {
234 Some(var) => var.is_mut || matches!(var.state, OwnershipState::Managed),
235 None => false,
237 },
238 NodeKind::FieldAccess { object, .. } => self.is_mutable_lvalue(object),
239 NodeKind::Index { object, .. } => self.is_mutable_lvalue(object),
240 _ => false,
241 }
242 }
243
244 fn check_list_mut_receiver(&mut self, call: &AIRNode, callee: &AIRNode) {
254 if self.recv_kind(call) != Some("List") {
258 return;
259 }
260 let NodeKind::FieldAccess { object, field } = &callee.kind else {
261 return;
262 };
263 if !MUT_LIST_RECEIVER_METHODS.contains(&field.name.as_str()) {
264 return;
265 }
266 if self.is_mutable_lvalue(object) {
267 return;
268 }
269 let recv_desc = match &object.kind {
270 NodeKind::Identifier { name } => format!("`{}`", name.name),
271 _ => "the receiver".to_string(),
272 };
273 self.diags
274 .error(
275 E_MUT_RECEIVER_NEEDED,
276 format!(
277 "cannot call `{}` on {recv_desc}: \
278 it mutates the list in place and requires a `mut` receiver",
279 field.name
280 ),
281 object.span,
282 )
283 .note(
284 "declare the list with `let mut`, or use `+` / `concat` to build a \
285 new list without mutation",
286 );
287 }
288
289 fn recv_kind<'a>(&self, node: &'a AIRNode) -> Option<&'a str> {
291 match node.metadata.get(crate::checker::RECV_KIND_META_KEY) {
292 Some(Value::String(s)) => Some(s.as_str()),
293 _ => None,
294 }
295 }
296
297 fn merge_states(
303 &self,
304 pre: &HashMap<String, VarOwnership>,
305 branches: &[(bool, HashMap<String, VarOwnership>)],
306 ) -> HashMap<String, VarOwnership> {
307 let non_div: Vec<&HashMap<String, VarOwnership>> = branches
308 .iter()
309 .filter(|(div, _)| !*div)
310 .map(|(_, s)| s)
311 .collect();
312
313 if non_div.is_empty() {
314 return pre.clone();
316 }
317
318 let mut result = pre.clone();
319 for name in pre.keys() {
320 let any_moved = non_div.iter().any(|state| {
321 state
322 .get(name)
323 .is_some_and(|v| matches!(v.state, OwnershipState::Moved))
324 });
325 if any_moved {
326 if let Some(var) = result.get_mut(name) {
327 let move_site = non_div
328 .iter()
329 .filter_map(|state| state.get(name))
330 .find(|v| matches!(v.state, OwnershipState::Moved))
331 .and_then(|v| v.move_site);
332 var.state = OwnershipState::Moved;
333 var.move_site = move_site;
334 }
335 }
336 }
337 result
338 }
339
340 fn bind_param(&mut self, param: &AIRNode) {
342 if let NodeKind::Param { pattern, .. } = ¶m.kind {
343 if let NodeKind::BindPat { name, is_mut } = &pattern.kind {
344 let state = if self.in_managed {
345 OwnershipState::Managed
346 } else {
347 OwnershipState::Owned
348 };
349 self.env.insert(
350 name.name.clone(),
351 VarOwnership {
352 state,
353 is_mut: *is_mut,
354 move_site: None,
355 },
356 );
357 }
358 }
359 }
360
361 fn bind_pattern(&mut self, pat: &AIRNode) {
363 let base_state = if self.in_managed {
364 OwnershipState::Managed
365 } else {
366 OwnershipState::Owned
367 };
368 match &pat.kind {
369 NodeKind::BindPat { name, is_mut } => {
370 self.env.insert(
371 name.name.clone(),
372 VarOwnership {
373 state: base_state,
374 is_mut: *is_mut,
375 move_site: None,
376 },
377 );
378 }
379 NodeKind::TuplePat { elems } => {
380 for e in elems {
381 self.bind_pattern(e);
382 }
383 }
384 NodeKind::ConstructorPat { fields, .. } => {
385 for f in fields {
386 self.bind_pattern(f);
387 }
388 }
389 NodeKind::RecordPat { fields, .. } => {
390 for f in fields {
391 if let Some(p) = &f.pattern {
392 self.bind_pattern(p);
393 } else {
394 self.env.insert(
395 f.name.name.clone(),
396 VarOwnership {
397 state: base_state.clone(),
398 is_mut: false,
399 move_site: None,
400 },
401 );
402 }
403 }
404 }
405 NodeKind::ListPat { elems, rest } => {
406 for e in elems {
407 self.bind_pattern(e);
408 }
409 if let Some(r) = rest {
410 self.bind_pattern(r);
411 }
412 }
413 NodeKind::OrPat { alternatives } => {
414 if let Some(first) = alternatives.first() {
415 self.bind_pattern(first);
416 }
417 }
418 _ => {}
419 }
420 }
421
422 #[allow(clippy::too_many_lines)]
424 fn analyze_node(&mut self, node: &AIRNode) -> bool {
425 match &node.kind {
426 NodeKind::Module { imports, items, .. } => {
428 for n in imports {
429 self.analyze_node(n);
430 }
431 for n in items {
432 self.analyze_node(n);
433 }
434 false
435 }
436
437 NodeKind::FnDecl {
439 annotations,
440 params,
441 body,
442 ..
443 } => {
444 let outer = self.snapshot();
445 let outer_managed = self.in_managed;
446 if annotations.iter().any(|a| a.name.name == "managed") {
447 self.in_managed = true;
448 }
449 for p in params {
450 self.bind_param(p);
451 }
452 self.analyze_node(body);
453 self.env = outer;
454 self.in_managed = outer_managed;
455 false
456 }
457 NodeKind::ImplBlock { methods, .. } => {
458 for m in methods {
459 self.analyze_node(m);
460 }
461 false
462 }
463 NodeKind::ClassDecl { methods, .. } => {
464 for m in methods {
465 self.analyze_node(m);
466 }
467 false
468 }
469 NodeKind::TraitDecl { methods, .. } => {
470 for m in methods {
471 self.analyze_node(m);
472 }
473 false
474 }
475 NodeKind::RecordDecl { .. }
477 | NodeKind::EnumDecl { .. }
478 | NodeKind::TypeAlias { .. }
479 | NodeKind::EffectDecl { .. }
480 | NodeKind::ConstDecl { .. }
481 | NodeKind::ImportDecl { .. }
482 | NodeKind::ModuleHandle { .. }
483 | NodeKind::PropertyTest { .. } => false,
484
485 NodeKind::Block { stmts, tail } => {
487 let pre_keys: HashSet<String> = self.env.keys().cloned().collect();
488 let mut diverges = false;
489 for stmt in stmts {
490 if diverges {
491 break;
492 }
493 diverges = self.analyze_node(stmt);
494 }
495 if !diverges {
496 if let Some(t) = tail {
497 diverges = self.analyze_node(t);
498 }
499 }
500 self.env.retain(|k, _| pre_keys.contains(k));
502 diverges
503 }
504
505 NodeKind::LetBinding {
507 is_mut,
508 pattern,
509 value,
510 ..
511 } => {
512 let is_managed =
513 self.in_managed || node.metadata.get("managed") == Some(&Value::Bool(true));
514
515 self.analyze_move(value);
516
517 let state = if is_managed {
518 OwnershipState::Managed
519 } else {
520 OwnershipState::Owned
521 };
522
523 let own = VarOwnership {
527 state,
528 is_mut: *is_mut,
529 move_site: None,
530 };
531 if is_managed {
532 if let NodeKind::BindPat { name, .. } = &pattern.kind {
534 self.env.insert(name.name.clone(), own);
535 } else {
536 self.bind_pattern(pattern);
537 }
540 } else {
541 self.bind_pattern(pattern);
542 }
543 false
544 }
545
546 NodeKind::Assign { op, target, value } => {
548 match op {
549 AssignOp::Assign => {
550 self.analyze_move(value);
552 self.analyze_node(target);
553 }
554 _ => {
555 self.analyze_node(target);
557 self.analyze_node(value);
558 }
559 }
560 false
561 }
562
563 NodeKind::Identifier { name } => {
565 self.check_use(&name.name, node.span);
566 false
567 }
568
569 NodeKind::Move { expr } => {
571 self.analyze_move(expr);
572 false
573 }
574 NodeKind::Borrow { expr } => {
575 self.analyze_node(expr);
577 false
578 }
579 NodeKind::MutableBorrow { expr } => {
580 if let NodeKind::Identifier { name } = &expr.kind {
582 if let Some(var) = self.env.get(&name.name) {
583 if !var.is_mut && !matches!(var.state, OwnershipState::Managed) {
584 self.diags.error(
585 E_MUT_BORROW_NEEDS_MUT,
586 format!(
587 "cannot mutably borrow `{}`: \
588 variable not declared `mut`",
589 name.name
590 ),
591 expr.span,
592 );
593 }
594 }
595 }
596 self.analyze_node(expr);
597 false
598 }
599
600 NodeKind::Return { value } => {
602 if let Some(v) = value {
603 let old_in_loop = self.in_loop;
606 self.in_loop = false;
607 self.analyze_move(v);
608 self.in_loop = old_in_loop;
609 }
610 true
611 }
612 NodeKind::Break { value } => {
613 if let Some(v) = value {
614 let old_in_loop = self.in_loop;
617 self.in_loop = false;
618 self.analyze_move(v);
619 self.in_loop = old_in_loop;
620 }
621 true
622 }
623 NodeKind::Continue => true,
624 NodeKind::Unreachable => true,
625
626 NodeKind::If {
628 condition,
629 then_block,
630 else_block,
631 ..
632 } => {
633 self.analyze_node(condition);
634
635 let pre = self.snapshot();
636
637 let then_div = self.analyze_node(then_block);
638 let then_state = self.snapshot();
639
640 self.env = pre.clone();
641 let (else_div, else_state) = match else_block {
642 Some(eb) => {
643 let d = self.analyze_node(eb);
644 (d, self.snapshot())
645 }
646 None => {
647 (false, pre.clone())
649 }
650 };
651
652 self.env =
653 self.merge_states(&pre, &[(then_div, then_state), (else_div, else_state)]);
654 then_div && else_div
655 }
656
657 NodeKind::Guard {
659 let_pattern,
660 condition,
661 else_block,
662 } => {
663 if let Some(pat) = let_pattern {
664 self.analyze_node(pat);
665 }
666 self.analyze_node(condition);
667 let pre = self.snapshot();
670 self.analyze_node(else_block);
671 self.env = pre;
673 false
674 }
675
676 NodeKind::Match { scrutinee, arms } => {
678 self.analyze_node(scrutinee);
679
680 let pre = self.snapshot();
681 let mut arm_results: Vec<(bool, HashMap<String, VarOwnership>)> =
682 Vec::with_capacity(arms.len());
683
684 for arm in arms {
685 self.env = pre.clone();
686 let div = self.analyze_node(arm);
687 arm_results.push((div, self.snapshot()));
688 }
689
690 self.env = self.merge_states(&pre, &arm_results);
691 arm_results.iter().all(|(d, _)| *d)
692 }
693
694 NodeKind::MatchArm {
695 pattern,
696 guard,
697 body,
698 } => {
699 let pre_keys: HashSet<String> = self.env.keys().cloned().collect();
700 self.bind_pattern(pattern);
701 if let Some(g) = guard {
702 self.analyze_node(g);
703 }
704 let div = self.analyze_node(body);
705 self.env.retain(|k, _| pre_keys.contains(k));
707 div
708 }
709
710 NodeKind::For {
712 pattern,
713 iterable,
714 body,
715 } => {
716 self.analyze_node(iterable);
717 let pre = self.snapshot();
718 let old_in_loop = self.in_loop;
719 let old_loop_keys = std::mem::take(&mut self.loop_entry_keys);
720 self.in_loop = true;
721 self.loop_entry_keys = pre.keys().cloned().collect();
722 self.bind_pattern(pattern);
723 self.analyze_node(body);
724 self.in_loop = old_in_loop;
725 self.loop_entry_keys = old_loop_keys;
726 self.env = pre;
728 false
729 }
730 NodeKind::While { condition, body } => {
731 self.analyze_node(condition);
732 let pre = self.snapshot();
733 let old_in_loop = self.in_loop;
734 let old_loop_keys = std::mem::take(&mut self.loop_entry_keys);
735 self.in_loop = true;
736 self.loop_entry_keys = pre.keys().cloned().collect();
737 self.analyze_node(body);
738 self.in_loop = old_in_loop;
739 self.loop_entry_keys = old_loop_keys;
740 self.env = pre;
741 false
742 }
743 NodeKind::Loop { body } => {
744 let pre = self.snapshot();
745 let old_in_loop = self.in_loop;
746 let old_loop_keys = std::mem::take(&mut self.loop_entry_keys);
747 self.in_loop = true;
748 self.loop_entry_keys = pre.keys().cloned().collect();
749 self.analyze_node(body);
750 self.in_loop = old_in_loop;
751 self.loop_entry_keys = old_loop_keys;
752 self.env = pre;
753 false
754 }
755
756 NodeKind::Call { callee, args, .. } => {
758 self.check_list_mut_receiver(node, callee);
764 self.analyze_node(callee);
765 for arg in args {
766 self.analyze_node(&arg.value);
769 }
770 false
771 }
772 NodeKind::MethodCall { receiver, args, .. } => {
773 self.analyze_node(receiver);
774 for arg in args {
775 self.analyze_node(&arg.value);
776 }
777 false
778 }
779
780 NodeKind::Lambda { params, body } => {
782 let outer = self.snapshot();
783 for p in params {
784 self.bind_param(p);
785 }
786 self.analyze_node(body);
787 self.env = outer;
788 false
789 }
790
791 NodeKind::BinaryOp { left, right, .. } => {
793 self.analyze_node(left);
794 self.analyze_node(right);
795 false
796 }
797 NodeKind::UnaryOp { operand, .. } => {
798 self.analyze_node(operand);
799 false
800 }
801 NodeKind::FieldAccess { object, .. } => {
802 self.analyze_node(object);
803 false
804 }
805 NodeKind::Index { object, index } => {
806 self.analyze_node(object);
807 self.analyze_node(index);
808 false
809 }
810 NodeKind::Propagate { expr } => {
811 self.analyze_node(expr);
812 false
813 }
814 NodeKind::Pipe { left, right } | NodeKind::Compose { left, right } => {
815 self.analyze_node(left);
816 self.analyze_node(right);
817 false
818 }
819 NodeKind::Await { expr } => {
820 self.analyze_node(expr);
821 false
822 }
823 NodeKind::Range { lo, hi, .. } => {
824 self.analyze_node(lo);
825 self.analyze_node(hi);
826 false
827 }
828 NodeKind::RecordConstruct { fields, spread, .. } => {
829 for f in fields {
830 if let Some(v) = &f.value {
831 self.analyze_move(v);
832 }
833 }
834 if let Some(s) = spread {
835 self.analyze_node(s);
836 }
837 false
838 }
839 NodeKind::ListLiteral { elems }
840 | NodeKind::SetLiteral { elems }
841 | NodeKind::TupleLiteral { elems } => {
842 for e in elems {
843 self.analyze_move(e);
844 }
845 false
846 }
847 NodeKind::MapLiteral { entries } => {
848 for entry in entries {
849 self.analyze_move(&entry.key);
850 self.analyze_move(&entry.value);
851 }
852 false
853 }
854 NodeKind::Interpolation { parts } => {
855 for part in parts {
856 if let AirInterpolationPart::Expr(e) = part {
857 self.analyze_node(e);
858 }
859 }
860 false
861 }
862 NodeKind::ResultConstruct { value, .. } => {
863 if let Some(v) = value {
864 self.analyze_move(v);
865 }
866 false
867 }
868 NodeKind::HandlingBlock { handlers, body } => {
869 for h in handlers {
870 self.analyze_node(&h.handler);
871 }
872 self.analyze_node(body);
873 false
874 }
875
876 NodeKind::Literal { .. }
878 | NodeKind::Placeholder
879 | NodeKind::TypeSelf
880 | NodeKind::WildcardPat
881 | NodeKind::LiteralPat { .. }
882 | NodeKind::RestPat
883 | NodeKind::TypeNamed { .. }
884 | NodeKind::TypeTuple { .. }
885 | NodeKind::TypeFunction { .. }
886 | NodeKind::TypeOptional { .. }
887 | NodeKind::EffectOp { .. }
888 | NodeKind::EffectRef { .. }
889 | NodeKind::Error => false,
890
891 NodeKind::BindPat { .. }
893 | NodeKind::ConstructorPat { .. }
894 | NodeKind::RecordPat { .. }
895 | NodeKind::TuplePat { .. }
896 | NodeKind::ListPat { .. }
897 | NodeKind::OrPat { .. }
898 | NodeKind::GuardPat { .. }
899 | NodeKind::RangePat { .. } => false,
900
901 _ => false,
903 }
904 }
905}
906
907#[must_use]
924pub fn analyze_ownership(module: &AIRModule) -> DiagnosticBag {
925 let mut analyzer = OwnershipAnalyzer::new();
926 analyzer.analyze_node(module);
927 analyzer.diags
928}
929
930#[cfg(test)]
933mod tests {
934 use super::*;
935 use bock_air::stubs::Value;
936 use bock_air::{AIRNode, AirArg, NodeIdGen, NodeKind};
937 use bock_ast::{Ident, Literal};
938 use bock_errors::{FileId, Span};
939
940 fn span() -> Span {
943 Span {
944 file: FileId(0),
945 start: 0,
946 end: 0,
947 }
948 }
949
950 fn span_at(start: usize, end: usize) -> Span {
951 Span {
952 file: FileId(0),
953 start,
954 end,
955 }
956 }
957
958 fn ident(name: &str) -> Ident {
959 Ident {
960 name: name.to_string(),
961 span: span(),
962 }
963 }
964
965 fn node(gen: &NodeIdGen, kind: NodeKind) -> AIRNode {
966 AIRNode::new(gen.next(), span(), kind)
967 }
968
969 fn node_at(gen: &NodeIdGen, kind: NodeKind, s: usize, e: usize) -> AIRNode {
970 AIRNode::new(gen.next(), span_at(s, e), kind)
971 }
972
973 fn id_node(gen: &NodeIdGen, name: &str) -> AIRNode {
974 node(gen, NodeKind::Identifier { name: ident(name) })
975 }
976
977 fn id_node_at(gen: &NodeIdGen, name: &str, s: usize, e: usize) -> AIRNode {
978 node_at(gen, NodeKind::Identifier { name: ident(name) }, s, e)
979 }
980
981 fn lit_node(gen: &NodeIdGen) -> AIRNode {
982 node(
983 gen,
984 NodeKind::Literal {
985 lit: Literal::Int("42".into()),
986 },
987 )
988 }
989
990 fn bind_pat(gen: &NodeIdGen, name: &str, is_mut: bool) -> AIRNode {
991 node(
992 gen,
993 NodeKind::BindPat {
994 name: ident(name),
995 is_mut,
996 },
997 )
998 }
999
1000 fn let_binding(gen: &NodeIdGen, name: &str, is_mut: bool, value: AIRNode) -> AIRNode {
1001 node(
1002 gen,
1003 NodeKind::LetBinding {
1004 is_mut,
1005 pattern: Box::new(bind_pat(gen, name, is_mut)),
1006 ty: None,
1007 value: Box::new(value),
1008 },
1009 )
1010 }
1011
1012 fn block(gen: &NodeIdGen, stmts: Vec<AIRNode>, tail: Option<AIRNode>) -> AIRNode {
1013 node(
1014 gen,
1015 NodeKind::Block {
1016 stmts,
1017 tail: tail.map(Box::new),
1018 },
1019 )
1020 }
1021
1022 fn module(gen: &NodeIdGen, items: Vec<AIRNode>) -> AIRNode {
1023 node(
1024 gen,
1025 NodeKind::Module {
1026 path: None,
1027 annotations: vec![],
1028 imports: vec![],
1029 items,
1030 },
1031 )
1032 }
1033
1034 fn fn_decl(gen: &NodeIdGen, body: AIRNode) -> AIRNode {
1035 fn_decl_with(gen, body, vec![])
1036 }
1037
1038 fn managed_fn_decl(gen: &NodeIdGen, body: AIRNode) -> AIRNode {
1039 use bock_ast::Annotation;
1040 fn_decl_with(
1041 gen,
1042 body,
1043 vec![Annotation {
1044 id: 0,
1045 span: span(),
1046 name: ident("managed"),
1047 args: vec![],
1048 }],
1049 )
1050 }
1051
1052 fn fn_decl_with(
1053 gen: &NodeIdGen,
1054 body: AIRNode,
1055 annotations: Vec<bock_ast::Annotation>,
1056 ) -> AIRNode {
1057 node(
1058 gen,
1059 NodeKind::FnDecl {
1060 annotations,
1061 visibility: bock_ast::Visibility::Public,
1062 is_async: false,
1063 name: ident("f"),
1064 generic_params: vec![],
1065 params: vec![],
1066 return_type: None,
1067 effect_clause: vec![],
1068 where_clause: vec![],
1069 body: Box::new(body),
1070 },
1071 )
1072 }
1073
1074 fn return_node(gen: &NodeIdGen, val: Option<AIRNode>) -> AIRNode {
1075 node(
1076 gen,
1077 NodeKind::Return {
1078 value: val.map(Box::new),
1079 },
1080 )
1081 }
1082
1083 fn move_node(gen: &NodeIdGen, expr: AIRNode) -> AIRNode {
1084 node(
1085 gen,
1086 NodeKind::Move {
1087 expr: Box::new(expr),
1088 },
1089 )
1090 }
1091
1092 fn mut_borrow(gen: &NodeIdGen, expr: AIRNode) -> AIRNode {
1093 node(
1094 gen,
1095 NodeKind::MutableBorrow {
1096 expr: Box::new(expr),
1097 },
1098 )
1099 }
1100
1101 fn if_node(gen: &NodeIdGen, cond: AIRNode, then: AIRNode, else_: Option<AIRNode>) -> AIRNode {
1102 node(
1103 gen,
1104 NodeKind::If {
1105 let_pattern: None,
1106 condition: Box::new(cond),
1107 then_block: Box::new(then),
1108 else_block: else_.map(Box::new),
1109 },
1110 )
1111 }
1112
1113 fn guard_node(gen: &NodeIdGen, cond: AIRNode, else_block: AIRNode) -> AIRNode {
1114 node(
1115 gen,
1116 NodeKind::Guard {
1117 let_pattern: None,
1118 condition: Box::new(cond),
1119 else_block: Box::new(else_block),
1120 },
1121 )
1122 }
1123
1124 fn loop_node(gen: &NodeIdGen, body: AIRNode) -> AIRNode {
1125 node(
1126 gen,
1127 NodeKind::Loop {
1128 body: Box::new(body),
1129 },
1130 )
1131 }
1132
1133 fn match_node(gen: &NodeIdGen, scrutinee: AIRNode, arms: Vec<AIRNode>) -> AIRNode {
1134 node(
1135 gen,
1136 NodeKind::Match {
1137 scrutinee: Box::new(scrutinee),
1138 arms,
1139 },
1140 )
1141 }
1142
1143 fn match_arm(gen: &NodeIdGen, pat: AIRNode, body: AIRNode) -> AIRNode {
1144 node(
1145 gen,
1146 NodeKind::MatchArm {
1147 pattern: Box::new(pat),
1148 guard: None,
1149 body: Box::new(body),
1150 },
1151 )
1152 }
1153
1154 #[test]
1157 fn no_error_simple_borrow() {
1158 let gen = NodeIdGen::new();
1162 let data_lit = lit_node(&gen);
1163 let let_data = let_binding(&gen, "data", false, data_lit);
1164 let use1 = id_node(&gen, "data");
1165 let use2 = id_node(&gen, "data");
1166 let call = node(
1167 &gen,
1168 NodeKind::Call {
1169 callee: Box::new(id_node(&gen, "summarize")),
1170 args: vec![AirArg {
1171 label: None,
1172 value: use1,
1173 }],
1174 type_args: vec![],
1175 },
1176 );
1177 let b = block(&gen, vec![let_data, call, use2], None);
1178 let m = module(&gen, vec![fn_decl(&gen, b)]);
1179 let diags = analyze_ownership(&m);
1180 assert!(
1181 !diags.has_errors(),
1182 "expected no errors, got: {:?}",
1183 diags.iter().collect::<Vec<_>>()
1184 );
1185 }
1186
1187 #[test]
1188 fn move_on_let_binding() {
1189 let gen = NodeIdGen::new();
1193 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1194 let id_data = id_node_at(&gen, "data", 10, 14);
1195 let let_archive = let_binding(&gen, "archive", false, id_data);
1196 let use_data = id_node_at(&gen, "data", 20, 24);
1197 let b = block(&gen, vec![let_data, let_archive, use_data], None);
1198 let m = module(&gen, vec![fn_decl(&gen, b)]);
1199 let diags = analyze_ownership(&m);
1200 assert!(diags.has_errors());
1201 assert!(diags.iter().any(|d| d.code == E_USE_AFTER_MOVE));
1202 }
1203
1204 #[test]
1205 fn explicit_move_node() {
1206 let gen = NodeIdGen::new();
1210 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1211 let mv = move_node(&gen, id_node_at(&gen, "data", 5, 9));
1212 let use_data = id_node_at(&gen, "data", 15, 19);
1213 let b = block(&gen, vec![let_data, mv, use_data], None);
1214 let m = module(&gen, vec![fn_decl(&gen, b)]);
1215 let diags = analyze_ownership(&m);
1216 assert!(diags.has_errors());
1217 assert!(diags.iter().any(|d| d.code == E_USE_AFTER_MOVE));
1218 }
1219
1220 #[test]
1221 fn use_after_move_has_move_site_label() {
1222 let gen = NodeIdGen::new();
1223 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1224 let mv = move_node(&gen, id_node_at(&gen, "data", 5, 9));
1225 let use_data = id_node_at(&gen, "data", 15, 19);
1226 let b = block(&gen, vec![let_data, mv, use_data], None);
1227 let m = module(&gen, vec![fn_decl(&gen, b)]);
1228 let diags = analyze_ownership(&m);
1229 let err = diags.iter().find(|d| d.code == E_USE_AFTER_MOVE).unwrap();
1230 assert!(
1231 !err.labels.is_empty(),
1232 "expected a label pointing to move site"
1233 );
1234 assert!(err.labels[0].message.contains("moved"));
1235 }
1236
1237 #[test]
1240 fn mut_borrow_of_non_mut_errors() {
1241 let gen = NodeIdGen::new();
1244 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1245 let mb = mut_borrow(&gen, id_node(&gen, "data"));
1246 let b = block(&gen, vec![let_data, mb], None);
1247 let m = module(&gen, vec![fn_decl(&gen, b)]);
1248 let diags = analyze_ownership(&m);
1249 assert!(diags.has_errors());
1250 assert!(diags.iter().any(|d| d.code == E_MUT_BORROW_NEEDS_MUT));
1251 }
1252
1253 #[test]
1254 fn mut_borrow_of_mut_ok() {
1255 let gen = NodeIdGen::new();
1258 let let_data = let_binding(&gen, "data", true, lit_node(&gen));
1259 let mb = mut_borrow(&gen, id_node(&gen, "data"));
1260 let b = block(&gen, vec![let_data, mb], None);
1261 let m = module(&gen, vec![fn_decl(&gen, b)]);
1262 let diags = analyze_ownership(&m);
1263 assert!(!diags.has_errors());
1264 }
1265
1266 #[test]
1269 fn managed_skips_ownership_tracking() {
1270 let gen = NodeIdGen::new();
1274 let mut let_data = let_binding(&gen, "data", false, lit_node(&gen));
1275 let_data
1276 .metadata
1277 .insert("managed".into(), Value::Bool(true));
1278 let id_data = id_node(&gen, "data");
1279 let let_archive = let_binding(&gen, "archive", false, id_data);
1280 let use_data = id_node(&gen, "data");
1281 let b = block(&gen, vec![let_data, let_archive, use_data], None);
1282 let m = module(&gen, vec![fn_decl(&gen, b)]);
1283 let diags = analyze_ownership(&m);
1284 assert!(!diags.has_errors());
1285 }
1286
1287 #[test]
1288 fn managed_fn_suppresses_move_errors() {
1289 let gen = NodeIdGen::new();
1296 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1297 let id_data1 = id_node(&gen, "data");
1298 let let_a = let_binding(&gen, "a", false, id_data1);
1299 let id_data2 = id_node(&gen, "data");
1300 let let_b = let_binding(&gen, "b", false, id_data2);
1301 let b = block(&gen, vec![let_data, let_a, let_b], None);
1302 let m = module(&gen, vec![managed_fn_decl(&gen, b)]);
1303 let diags = analyze_ownership(&m);
1304 assert!(
1305 !diags.has_errors(),
1306 "expected no errors in @managed fn, got: {:?}",
1307 diags.iter().collect::<Vec<_>>()
1308 );
1309 }
1310
1311 #[test]
1312 fn non_managed_fn_still_errors_on_reuse() {
1313 let gen = NodeIdGen::new();
1319 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1320 let id_data1 = id_node(&gen, "data");
1321 let let_a = let_binding(&gen, "a", false, id_data1);
1322 let id_data2 = id_node_at(&gen, "data", 20, 24);
1323 let let_b = let_binding(&gen, "b", false, id_data2);
1324 let b = block(&gen, vec![let_data, let_a, let_b], 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_USE_AFTER_MOVE));
1329 }
1330
1331 #[test]
1332 fn managed_fn_loop_move_suppressed() {
1333 let gen = NodeIdGen::new();
1339 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1340 let id_data = id_node(&gen, "data");
1341 let let_discard = let_binding(&gen, "_d", false, id_data);
1342 let loop_body = block(&gen, vec![let_discard], None);
1343 let lp = loop_node(&gen, loop_body);
1344 let b = block(&gen, vec![let_data, lp], None);
1345 let m = module(&gen, vec![managed_fn_decl(&gen, b)]);
1346 let diags = analyze_ownership(&m);
1347 assert!(
1348 !diags.has_errors(),
1349 "expected no errors in @managed fn loop, got: {:?}",
1350 diags.iter().collect::<Vec<_>>()
1351 );
1352 }
1353
1354 #[test]
1357 fn guard_else_diverges_data_still_owned() {
1358 let gen = NodeIdGen::new();
1361 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1362 let cond = lit_node(&gen);
1363 let else_block = block(&gen, vec![return_node(&gen, None)], None);
1364 let guard = guard_node(&gen, cond, else_block);
1365 let use_data = id_node(&gen, "data");
1366 let b = block(&gen, vec![let_data, guard, use_data], None);
1367 let m = module(&gen, vec![fn_decl(&gen, b)]);
1368 let diags = analyze_ownership(&m);
1369 assert!(!diags.has_errors());
1370 }
1371
1372 #[test]
1373 fn if_with_diverging_then_data_still_owned() {
1374 let gen = NodeIdGen::new();
1378 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1379 let cond = lit_node(&gen);
1380 let id_data = id_node(&gen, "data");
1382 let let_archive = let_binding(&gen, "archive", false, id_data);
1383 let ret = return_node(&gen, None);
1384 let then_block = block(&gen, vec![let_archive, ret], None);
1385 let if_node_ = if_node(&gen, cond, then_block, None);
1386 let use_data = id_node(&gen, "data");
1387 let b = block(&gen, vec![let_data, if_node_, use_data], None);
1388 let m = module(&gen, vec![fn_decl(&gen, b)]);
1389 let diags = analyze_ownership(&m);
1390 assert!(!diags.has_errors());
1391 }
1392
1393 #[test]
1394 fn if_non_diverging_branch_moves_makes_moved_at_join() {
1395 let gen = NodeIdGen::new();
1399 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1400 let cond = lit_node(&gen);
1401 let id_data = id_node(&gen, "data");
1402 let let_archive = let_binding(&gen, "archive", false, id_data);
1403 let then_block = block(&gen, vec![let_archive], None);
1404 let if_node_ = if_node(&gen, cond, then_block, None);
1405 let use_data = id_node_at(&gen, "data", 30, 34);
1406 let b = block(&gen, vec![let_data, if_node_, use_data], None);
1407 let m = module(&gen, vec![fn_decl(&gen, b)]);
1408 let diags = analyze_ownership(&m);
1409 assert!(diags.has_errors());
1410 assert!(diags.iter().any(|d| d.code == E_USE_AFTER_MOVE));
1411 }
1412
1413 #[test]
1414 fn if_both_branches_diverge_join_uses_pre_state() {
1415 let gen = NodeIdGen::new();
1419 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1420 let cond = lit_node(&gen);
1421 let then_block = block(&gen, vec![return_node(&gen, None)], None);
1422 let else_block = block(&gen, vec![return_node(&gen, None)], None);
1423 let if_node_ = if_node(&gen, cond, then_block, Some(else_block));
1424 let b = block(&gen, vec![let_data, if_node_], None);
1425 let m = module(&gen, vec![fn_decl(&gen, b)]);
1426 let diags = analyze_ownership(&m);
1427 assert!(!diags.has_errors());
1428 }
1429
1430 #[test]
1433 fn move_inside_loop_is_error() {
1434 let gen = NodeIdGen::new();
1437 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1438 let id_data = id_node_at(&gen, "data", 10, 14);
1439 let let_archive = let_binding(&gen, "archive", false, id_data);
1440 let loop_body = block(&gen, vec![let_archive], None);
1441 let lp = loop_node(&gen, loop_body);
1442 let b = block(&gen, vec![let_data, lp], None);
1443 let m = module(&gen, vec![fn_decl(&gen, b)]);
1444 let diags = analyze_ownership(&m);
1445 assert!(diags.has_errors());
1446 assert!(diags.iter().any(|d| d.code == E_LOOP_MOVE));
1447 }
1448
1449 #[test]
1450 fn variable_defined_inside_loop_can_be_moved() {
1451 let gen = NodeIdGen::new();
1453 let let_tmp = let_binding(&gen, "tmp", false, lit_node(&gen));
1454 let id_tmp = id_node(&gen, "tmp");
1455 let let_discard = let_binding(&gen, "_unused", false, id_tmp);
1456 let loop_body = block(&gen, vec![let_tmp, let_discard], None);
1457 let lp = loop_node(&gen, loop_body);
1458 let b = block(&gen, vec![lp], None);
1459 let m = module(&gen, vec![fn_decl(&gen, b)]);
1460 let diags = analyze_ownership(&m);
1461 assert!(!diags.has_errors());
1462 }
1463
1464 #[test]
1467 fn match_all_arms_diverge_no_use_after_move() {
1468 let gen = NodeIdGen::new();
1475 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1476 let scrutinee = lit_node(&gen);
1477 let id_data = id_node(&gen, "data");
1478 let let_discard = let_binding(&gen, "_d", false, id_data);
1479 let ret1 = return_node(&gen, None);
1480 let arm1_body = block(&gen, vec![let_discard, ret1], None);
1481 let arm1 = match_arm(&gen, node(&gen, NodeKind::WildcardPat), arm1_body);
1482 let ret2 = return_node(&gen, None);
1483 let arm2_body = block(&gen, vec![ret2], None);
1484 let arm2 = match_arm(&gen, node(&gen, NodeKind::WildcardPat), arm2_body);
1485 let m_node = match_node(&gen, scrutinee, vec![arm1, arm2]);
1486 let b = block(&gen, vec![let_data, m_node], None);
1487 let m = module(&gen, vec![fn_decl(&gen, b)]);
1488 let diags = analyze_ownership(&m);
1489 assert!(!diags.has_errors());
1490 }
1491
1492 #[test]
1493 fn match_non_diverging_arm_moves_is_error_after() {
1494 let gen = NodeIdGen::new();
1501 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1502 let scrutinee = lit_node(&gen);
1503 let id_data = id_node(&gen, "data");
1504 let let_discard = let_binding(&gen, "_d", false, id_data);
1505 let arm1_body = block(&gen, vec![let_discard], None);
1506 let arm1 = match_arm(&gen, node(&gen, NodeKind::WildcardPat), arm1_body);
1507 let ret = return_node(&gen, None);
1508 let arm2_body = block(&gen, vec![ret], None);
1509 let arm2 = match_arm(&gen, node(&gen, NodeKind::WildcardPat), arm2_body);
1510 let m_node = match_node(&gen, scrutinee, vec![arm1, arm2]);
1511 let use_data = id_node_at(&gen, "data", 50, 54);
1512 let b = block(&gen, vec![let_data, m_node, use_data], None);
1513 let m = module(&gen, vec![fn_decl(&gen, b)]);
1514 let diags = analyze_ownership(&m);
1515 assert!(diags.has_errors());
1516 assert!(diags.iter().any(|d| d.code == E_USE_AFTER_MOVE));
1517 }
1518
1519 #[test]
1522 fn if_inside_match_arm_nested() {
1523 let gen = NodeIdGen::new();
1531 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1532 let scrutinee = lit_node(&gen);
1533 let cond = lit_node(&gen);
1534 let id_data = id_node(&gen, "data");
1535 let let_discard = let_binding(&gen, "_d", false, id_data);
1536 let then_block = block(&gen, vec![let_discard], None);
1537 let else_block = block(&gen, vec![return_node(&gen, None)], None);
1538 let if_expr = if_node(&gen, cond, then_block, Some(else_block));
1539 let arm_body = block(&gen, vec![if_expr], None);
1540 let arm = match_arm(&gen, node(&gen, NodeKind::WildcardPat), arm_body);
1541 let m_node = match_node(&gen, scrutinee, vec![arm]);
1542 let use_data = id_node_at(&gen, "data", 50, 54);
1543 let b = block(&gen, vec![let_data, m_node, use_data], None);
1544 let m = module(&gen, vec![fn_decl(&gen, b)]);
1545 let diags = analyze_ownership(&m);
1546 assert!(diags.has_errors());
1547 assert!(diags.iter().any(|d| d.code == E_USE_AFTER_MOVE));
1548 }
1549
1550 #[test]
1551 fn no_false_positive_if_else_both_leave_owned() {
1552 let gen = NodeIdGen::new();
1556 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1557 let cond = lit_node(&gen);
1558 let use1 = id_node(&gen, "data");
1559 let use2 = id_node(&gen, "data");
1560 let then_block = block(&gen, vec![use1], None);
1561 let else_block = block(&gen, vec![use2], None);
1562 let if_expr = if_node(&gen, cond, then_block, Some(else_block));
1563 let use_after = id_node(&gen, "data");
1564 let b = block(&gen, vec![let_data, if_expr, use_after], None);
1565 let m = module(&gen, vec![fn_decl(&gen, b)]);
1566 let diags = analyze_ownership(&m);
1567 assert!(!diags.has_errors());
1568 }
1569
1570 #[test]
1571 fn double_move_error() {
1572 let gen = NodeIdGen::new();
1576 let let_data = let_binding(&gen, "data", false, lit_node(&gen));
1577 let let_a = let_binding(&gen, "a", false, id_node(&gen, "data"));
1578 let let_b = let_binding(&gen, "b", false, id_node_at(&gen, "data", 20, 24));
1579 let b = block(&gen, vec![let_data, let_a, let_b], None);
1580 let m = module(&gen, vec![fn_decl(&gen, b)]);
1581 let diags = analyze_ownership(&m);
1582 assert!(diags.has_errors());
1583 assert!(diags.iter().any(|d| d.code == E_USE_AFTER_MOVE));
1584 }
1585
1586 fn desugared_list_call(
1594 gen: &NodeIdGen,
1595 receiver: AIRNode,
1596 method: &str,
1597 arg: AIRNode,
1598 ) -> AIRNode {
1599 let recv_self = receiver.clone();
1602 let callee = node(
1603 gen,
1604 NodeKind::FieldAccess {
1605 object: Box::new(receiver),
1606 field: ident(method),
1607 },
1608 );
1609 let mut call = node(
1610 gen,
1611 NodeKind::Call {
1612 callee: Box::new(callee),
1613 args: vec![
1614 AirArg {
1615 label: None,
1616 value: recv_self,
1617 },
1618 AirArg {
1619 label: None,
1620 value: arg,
1621 },
1622 ],
1623 type_args: vec![],
1624 },
1625 );
1626 call.metadata.insert(
1627 crate::checker::RECV_KIND_META_KEY.to_string(),
1628 Value::String("List".to_string()),
1629 );
1630 call
1631 }
1632
1633 #[test]
1634 fn push_on_mut_let_binding_ok() {
1635 let gen = NodeIdGen::new();
1638 let let_acc = let_binding(&gen, "acc", true, lit_node(&gen));
1639 let push = desugared_list_call(&gen, id_node(&gen, "acc"), "push", lit_node(&gen));
1640 let b = block(&gen, vec![let_acc, push], None);
1641 let m = module(&gen, vec![fn_decl(&gen, b)]);
1642 let diags = analyze_ownership(&m);
1643 assert!(
1644 !diags.iter().any(|d| d.code == E_MUT_RECEIVER_NEEDED),
1645 "push on a `mut` binding must not error: {:?}",
1646 diags.iter().collect::<Vec<_>>()
1647 );
1648 }
1649
1650 #[test]
1651 fn push_on_non_mut_let_binding_errors() {
1652 let gen = NodeIdGen::new();
1655 let let_acc = let_binding(&gen, "acc", false, lit_node(&gen));
1656 let push = desugared_list_call(
1657 &gen,
1658 id_node_at(&gen, "acc", 10, 13),
1659 "push",
1660 lit_node(&gen),
1661 );
1662 let b = block(&gen, vec![let_acc, push], None);
1663 let m = module(&gen, vec![fn_decl(&gen, b)]);
1664 let diags = analyze_ownership(&m);
1665 assert!(diags.iter().any(|d| d.code == E_MUT_RECEIVER_NEEDED));
1666 let err = diags
1667 .iter()
1668 .find(|d| d.code == E_MUT_RECEIVER_NEEDED)
1669 .unwrap();
1670 assert!(err.message.contains("push"));
1671 assert!(err.message.contains("acc"));
1672 assert!(
1673 !err.notes.is_empty(),
1674 "expected a `let mut` suggestion note"
1675 );
1676 }
1677
1678 #[test]
1679 fn append_on_non_mut_let_binding_errors() {
1680 let gen = NodeIdGen::new();
1682 let let_acc = let_binding(&gen, "acc", false, lit_node(&gen));
1683 let push = desugared_list_call(&gen, id_node(&gen, "acc"), "append", lit_node(&gen));
1684 let b = block(&gen, vec![let_acc, push], None);
1685 let m = module(&gen, vec![fn_decl(&gen, b)]);
1686 let diags = analyze_ownership(&m);
1687 assert!(diags.iter().any(|d| d.code == E_MUT_RECEIVER_NEEDED));
1688 }
1689
1690 #[test]
1691 fn push_on_mut_parameter_ok() {
1692 let gen = NodeIdGen::new();
1694 let push = desugared_list_call(&gen, id_node(&gen, "xs"), "push", lit_node(&gen));
1695 let b = block(&gen, vec![push], None);
1696 let param = node(
1698 &gen,
1699 NodeKind::Param {
1700 pattern: Box::new(bind_pat(&gen, "xs", true)),
1701 ty: None,
1702 default: None,
1703 },
1704 );
1705 let f = node(
1706 &gen,
1707 NodeKind::FnDecl {
1708 annotations: vec![],
1709 visibility: bock_ast::Visibility::Public,
1710 is_async: false,
1711 name: ident("f"),
1712 generic_params: vec![],
1713 params: vec![param],
1714 return_type: None,
1715 effect_clause: vec![],
1716 where_clause: vec![],
1717 body: Box::new(b),
1718 },
1719 );
1720 let m = module(&gen, vec![f]);
1721 let diags = analyze_ownership(&m);
1722 assert!(
1723 !diags.iter().any(|d| d.code == E_MUT_RECEIVER_NEEDED),
1724 "push on a `mut` parameter must not error"
1725 );
1726 }
1727
1728 #[test]
1729 fn dq30_mutators_on_non_mut_let_binding_error() {
1730 for method in ["pop", "remove_at", "insert", "reverse", "set"] {
1733 let gen = NodeIdGen::new();
1734 let let_acc = let_binding(&gen, "acc", false, lit_node(&gen));
1735 let call = desugared_list_call(&gen, id_node(&gen, "acc"), method, lit_node(&gen));
1736 let b = block(&gen, vec![let_acc, call], None);
1737 let m = module(&gen, vec![fn_decl(&gen, b)]);
1738 let diags = analyze_ownership(&m);
1739 assert!(
1740 diags.iter().any(|d| d.code == E_MUT_RECEIVER_NEEDED),
1741 "`{method}` on a non-`mut` binding must emit E5004"
1742 );
1743 }
1744 }
1745
1746 #[test]
1747 fn dq30_mutators_on_mut_let_binding_ok() {
1748 for method in ["pop", "remove_at", "insert", "reverse", "set"] {
1749 let gen = NodeIdGen::new();
1750 let let_acc = let_binding(&gen, "acc", true, lit_node(&gen));
1751 let call = desugared_list_call(&gen, id_node(&gen, "acc"), method, lit_node(&gen));
1752 let b = block(&gen, vec![let_acc, call], None);
1753 let m = module(&gen, vec![fn_decl(&gen, b)]);
1754 let diags = analyze_ownership(&m);
1755 assert!(
1756 !diags.iter().any(|d| d.code == E_MUT_RECEIVER_NEEDED),
1757 "`{method}` on a `mut` binding must not emit E5004: {:?}",
1758 diags.iter().collect::<Vec<_>>()
1759 );
1760 }
1761 }
1762
1763 #[test]
1764 fn push_in_loop_on_mut_binding_ok() {
1765 let gen = NodeIdGen::new();
1769 let let_acc = let_binding(&gen, "acc", true, lit_node(&gen));
1770 let push = desugared_list_call(&gen, id_node(&gen, "acc"), "push", lit_node(&gen));
1771 let loop_body = block(&gen, vec![push], None);
1772 let lp = loop_node(&gen, loop_body);
1773 let b = block(&gen, vec![let_acc, lp], None);
1774 let m = module(&gen, vec![fn_decl(&gen, b)]);
1775 let diags = analyze_ownership(&m);
1776 assert!(
1777 !diags.iter().any(|d| d.code == E_MUT_RECEIVER_NEEDED),
1778 "push in a loop on a `mut` binding must not error: {:?}",
1779 diags.iter().collect::<Vec<_>>()
1780 );
1781 }
1782}