1use super::var_id::VarId;
9use smallvec::SmallVec;
10use std::collections::HashMap;
11use std::fmt;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
19pub enum BorrowKind {
20 Shared,
22 Mutable,
24}
25
26impl BorrowKind {
27 pub fn conflicts_with(&self, other: BorrowKind) -> bool {
32 matches!(
33 (self, other),
34 (BorrowKind::Mutable, _) | (_, BorrowKind::Mutable)
35 )
36 }
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Default)]
45pub enum BorrowStateV2 {
46 #[default]
48 Owned,
49
50 SharedRef {
52 source: VarId,
54 start_line: u32,
56 },
57
58 MutRef {
60 source: VarId,
62 start_line: u32,
64 },
65
66 Moved {
68 to: VarId,
70 at_line: u32,
72 },
73
74 Dropped {
76 at_line: u32,
78 },
79
80 Copied {
82 to: VarId,
84 at_line: u32,
86 },
87}
88
89impl BorrowStateV2 {
90 pub fn can_read(&self) -> bool {
92 matches!(
93 self,
94 BorrowStateV2::Owned
95 | BorrowStateV2::SharedRef { .. }
96 | BorrowStateV2::MutRef { .. }
97 | BorrowStateV2::Copied { .. }
98 )
99 }
100
101 pub fn can_mutate(&self) -> bool {
103 matches!(self, BorrowStateV2::Owned | BorrowStateV2::MutRef { .. })
104 }
105
106 pub fn is_invalidated(&self) -> bool {
108 matches!(
109 self,
110 BorrowStateV2::Moved { .. } | BorrowStateV2::Dropped { .. }
111 )
112 }
113
114 pub fn is_reference(&self) -> bool {
116 matches!(
117 self,
118 BorrowStateV2::SharedRef { .. } | BorrowStateV2::MutRef { .. }
119 )
120 }
121
122 pub fn borrow_source(&self) -> Option<VarId> {
124 match self {
125 BorrowStateV2::SharedRef { source, .. } | BorrowStateV2::MutRef { source, .. } => {
126 Some(*source)
127 }
128 _ => None,
129 }
130 }
131}
132
133#[derive(Debug, Clone, PartialEq, Eq)]
139pub struct ActiveBorrowV2 {
140 pub borrower: VarId,
142 pub kind: BorrowKind,
144 pub start_line: u32,
146 pub end_line: Option<u32>,
148}
149
150impl ActiveBorrowV2 {
151 pub fn new(borrower: VarId, kind: BorrowKind, start_line: u32) -> Self {
153 Self {
154 borrower,
155 kind,
156 start_line,
157 end_line: None,
158 }
159 }
160
161 pub fn is_active_at(&self, line: u32) -> bool {
163 line >= self.start_line && self.end_line.is_none_or(|end| line < end)
164 }
165
166 pub fn end_at(&mut self, line: u32) {
168 self.end_line = Some(line);
169 }
170
171 pub fn conflicts_with(&self, new_kind: BorrowKind, at_line: u32) -> bool {
173 self.is_active_at(at_line) && self.kind.conflicts_with(new_kind)
174 }
175}
176
177#[derive(Debug, Clone, PartialEq, Eq)]
183pub struct BorrowConflict {
184 pub variable: VarId,
186 pub existing: ActiveBorrowV2,
188 pub new_kind: BorrowKind,
190 pub new_line: u32,
192}
193
194impl BorrowConflict {
195 pub fn new(
197 variable: VarId,
198 existing: ActiveBorrowV2,
199 new_kind: BorrowKind,
200 new_line: u32,
201 ) -> Self {
202 Self {
203 variable,
204 existing,
205 new_kind,
206 new_line,
207 }
208 }
209}
210
211impl fmt::Display for BorrowConflict {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 let existing_kind = match self.existing.kind {
214 BorrowKind::Shared => "shared",
215 BorrowKind::Mutable => "mutable",
216 };
217 let new_kind = match self.new_kind {
218 BorrowKind::Shared => "shared",
219 BorrowKind::Mutable => "mutable",
220 };
221
222 if self.existing.kind == BorrowKind::Mutable && self.new_kind == BorrowKind::Mutable {
223 write!(
224 f,
225 "cannot borrow as mutable more than once: \
226 first borrow at line {}, second borrow at line {}",
227 self.existing.start_line, self.new_line
228 )
229 } else {
230 write!(
231 f,
232 "cannot borrow as {} because already borrowed as {}: \
233 existing borrow at line {}, new borrow at line {}",
234 new_kind, existing_kind, self.existing.start_line, self.new_line
235 )
236 }
237 }
238}
239
240#[derive(Debug, Clone, PartialEq, Eq)]
246pub struct MoveError {
247 pub variable: VarId,
249 pub moved_at: u32,
251 pub used_at: u32,
253}
254
255impl fmt::Display for MoveError {
256 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257 write!(
258 f,
259 "use of moved value: moved at line {}, used at line {}",
260 self.moved_at, self.used_at
261 )
262 }
263}
264
265#[derive(Debug, Clone)]
271pub struct BorrowAnalysis {
272 pub conflicts: Vec<BorrowConflict>,
274 pub move_errors: Vec<MoveError>,
276}
277
278impl BorrowAnalysis {
279 pub fn has_issues(&self) -> bool {
281 !self.conflicts.is_empty() || !self.move_errors.is_empty()
282 }
283
284 pub fn issue_count(&self) -> usize {
286 self.conflicts.len() + self.move_errors.len()
287 }
288}
289
290#[derive(Debug, Clone, Default)]
298pub struct BorrowTrackerV2 {
299 borrows: HashMap<VarId, SmallVec<[ActiveBorrowV2; 2]>>,
302}
303
304impl BorrowTrackerV2 {
305 pub fn new() -> Self {
307 Self::default()
308 }
309
310 pub fn with_capacity(capacity: usize) -> Self {
312 Self {
313 borrows: HashMap::with_capacity(capacity),
314 }
315 }
316
317 pub fn add_borrow(
321 &mut self,
322 source: VarId,
323 borrower: VarId,
324 kind: BorrowKind,
325 line: u32,
326 ) -> Vec<BorrowConflict> {
327 let conflicts = self.conflicts(source, kind, line);
328
329 self.borrows
330 .entry(source)
331 .or_default()
332 .push(ActiveBorrowV2::new(borrower, kind, line));
333
334 conflicts
335 }
336
337 pub fn end_borrow(&mut self, borrower: VarId, line: u32) {
339 for borrows in self.borrows.values_mut() {
340 for borrow in borrows.iter_mut() {
341 if borrow.borrower == borrower && borrow.end_line.is_none() {
342 borrow.end_at(line);
343 }
344 }
345 }
346 }
347
348 pub fn conflicts(&self, source: VarId, kind: BorrowKind, at_line: u32) -> Vec<BorrowConflict> {
350 self.borrows
351 .get(&source)
352 .map(|borrows| {
353 borrows
354 .iter()
355 .filter(|b| b.conflicts_with(kind, at_line))
356 .map(|b| BorrowConflict::new(source, b.clone(), kind, at_line))
357 .collect()
358 })
359 .unwrap_or_default()
360 }
361
362 pub fn active_borrows_at(&self, source: VarId, line: u32) -> Vec<&ActiveBorrowV2> {
364 self.borrows
365 .get(&source)
366 .map(|borrows| borrows.iter().filter(|b| b.is_active_at(line)).collect())
367 .unwrap_or_default()
368 }
369
370 pub fn active_borrows(&self, source: VarId) -> &[ActiveBorrowV2] {
372 self.borrows
373 .get(&source)
374 .map(|v| v.as_slice())
375 .unwrap_or(&[])
376 }
377
378 pub fn clear(&mut self) {
380 self.borrows.clear();
381 }
382
383 pub fn has_active_borrows(&self, source: VarId, at_line: u32) -> bool {
385 self.borrows
386 .get(&source)
387 .map(|borrows| borrows.iter().any(|b| b.is_active_at(at_line)))
388 .unwrap_or(false)
389 }
390
391 pub fn has_active_mut_borrow(&self, source: VarId, at_line: u32) -> bool {
393 self.borrows
394 .get(&source)
395 .map(|borrows| {
396 borrows
397 .iter()
398 .any(|b| b.is_active_at(at_line) && b.kind == BorrowKind::Mutable)
399 })
400 .unwrap_or(false)
401 }
402
403 pub fn tracked_var_count(&self) -> usize {
405 self.borrows.len()
406 }
407
408 pub fn total_borrow_count(&self) -> usize {
410 self.borrows.values().map(|v| v.len()).sum()
411 }
412}
413
414#[cfg(test)]
415mod tests {
416 use super::*;
417 use crate::symbol::SymbolId;
418 use slotmap::SlotMap;
419
420 struct TestVars {
422 symbols: SlotMap<SymbolId, &'static str>,
423 mapping: super::super::var_id::VarSymbolMapping,
424 }
425
426 impl TestVars {
427 fn new() -> Self {
428 Self {
429 symbols: SlotMap::with_key(),
430 mapping: super::super::var_id::VarSymbolMapping::new(),
431 }
432 }
433
434 fn var(&mut self, name: &'static str) -> VarId {
435 let sym = self.symbols.insert(name);
436 self.mapping.register(sym)
437 }
438 }
439
440 #[test]
441 fn test_borrow_state_can_read() {
442 let mut vars = TestVars::new();
443 let v = vars.var("v");
444
445 assert!(BorrowStateV2::Owned.can_read());
446 assert!(BorrowStateV2::SharedRef {
447 source: v,
448 start_line: 1
449 }
450 .can_read());
451 assert!(BorrowStateV2::MutRef {
452 source: v,
453 start_line: 1
454 }
455 .can_read());
456 assert!(!BorrowStateV2::Moved { to: v, at_line: 1 }.can_read());
457 assert!(!BorrowStateV2::Dropped { at_line: 1 }.can_read());
458 }
459
460 #[test]
461 fn test_borrow_state_can_mutate() {
462 let mut vars = TestVars::new();
463 let v = vars.var("v");
464
465 assert!(BorrowStateV2::Owned.can_mutate());
466 assert!(BorrowStateV2::MutRef {
467 source: v,
468 start_line: 1
469 }
470 .can_mutate());
471 assert!(!BorrowStateV2::SharedRef {
472 source: v,
473 start_line: 1
474 }
475 .can_mutate());
476 assert!(!BorrowStateV2::Moved { to: v, at_line: 1 }.can_mutate());
477 }
478
479 #[test]
480 fn test_active_borrow_is_active_at() {
481 let mut vars = TestVars::new();
482 let borrower = vars.var("borrower");
483 let mut borrow = ActiveBorrowV2::new(borrower, BorrowKind::Shared, 10);
484
485 assert!(borrow.is_active_at(10));
486 assert!(borrow.is_active_at(15));
487 assert!(borrow.is_active_at(100));
488 assert!(!borrow.is_active_at(5));
489
490 borrow.end_at(20);
491 assert!(borrow.is_active_at(10));
492 assert!(borrow.is_active_at(15));
493 assert!(!borrow.is_active_at(20));
494 assert!(!borrow.is_active_at(25));
495 }
496
497 #[test]
498 fn test_borrow_tracker_no_conflict_shared() {
499 let mut tracker = BorrowTrackerV2::new();
500 let mut vars = TestVars::new();
501 let source = vars.var("source");
502 let b1 = vars.var("b1");
503 let b2 = vars.var("b2");
504
505 let conflicts = tracker.add_borrow(source, b1, BorrowKind::Shared, 10);
507 assert!(conflicts.is_empty());
508
509 let conflicts = tracker.add_borrow(source, b2, BorrowKind::Shared, 15);
510 assert!(conflicts.is_empty());
511 }
512
513 #[test]
514 fn test_borrow_tracker_conflict_mut_mut() {
515 let mut tracker = BorrowTrackerV2::new();
516 let mut vars = TestVars::new();
517 let source = vars.var("source");
518 let b1 = vars.var("b1");
519 let b2 = vars.var("b2");
520
521 let conflicts = tracker.add_borrow(source, b1, BorrowKind::Mutable, 10);
522 assert!(conflicts.is_empty());
523
524 let conflicts = tracker.add_borrow(source, b2, BorrowKind::Mutable, 15);
526 assert_eq!(conflicts.len(), 1);
527 assert_eq!(conflicts[0].existing.borrower, b1);
528 assert_eq!(conflicts[0].new_kind, BorrowKind::Mutable);
529 }
530
531 #[test]
532 fn test_borrow_tracker_conflict_shared_then_mut() {
533 let mut tracker = BorrowTrackerV2::new();
534 let mut vars = TestVars::new();
535 let source = vars.var("source");
536 let b1 = vars.var("b1");
537 let b2 = vars.var("b2");
538
539 let conflicts = tracker.add_borrow(source, b1, BorrowKind::Shared, 10);
540 assert!(conflicts.is_empty());
541
542 let conflicts = tracker.add_borrow(source, b2, BorrowKind::Mutable, 15);
544 assert_eq!(conflicts.len(), 1);
545 }
546
547 #[test]
548 fn test_borrow_tracker_end_borrow() {
549 let mut tracker = BorrowTrackerV2::new();
550 let mut vars = TestVars::new();
551 let source = vars.var("source");
552 let borrower = vars.var("borrower");
553 let b2 = vars.var("b2");
554
555 tracker.add_borrow(source, borrower, BorrowKind::Mutable, 10);
556 tracker.end_borrow(borrower, 20);
557
558 let conflicts = tracker.add_borrow(source, b2, BorrowKind::Mutable, 25);
560 assert!(conflicts.is_empty());
561 }
562
563 #[test]
564 fn test_borrow_tracker_has_active_borrows() {
565 let mut tracker = BorrowTrackerV2::new();
566 let mut vars = TestVars::new();
567 let source = vars.var("source");
568 let borrower = vars.var("borrower");
569
570 assert!(!tracker.has_active_borrows(source, 10));
571
572 tracker.add_borrow(source, borrower, BorrowKind::Shared, 10);
573 assert!(tracker.has_active_borrows(source, 15));
574
575 tracker.end_borrow(borrower, 20);
576 assert!(!tracker.has_active_borrows(source, 25));
577 }
578
579 #[test]
580 fn test_borrow_tracker_stats() {
581 let mut tracker = BorrowTrackerV2::new();
582 let mut vars = TestVars::new();
583 let s1 = vars.var("s1");
584 let s2 = vars.var("s2");
585 let b1 = vars.var("b1");
586 let b2 = vars.var("b2");
587 let b3 = vars.var("b3");
588
589 tracker.add_borrow(s1, b1, BorrowKind::Shared, 10);
590 tracker.add_borrow(s1, b2, BorrowKind::Shared, 15);
591 tracker.add_borrow(s2, b3, BorrowKind::Mutable, 20);
592
593 assert_eq!(tracker.tracked_var_count(), 2);
594 assert_eq!(tracker.total_borrow_count(), 3);
595 }
596}